Docs
Install Feedinbox
Feedinbox is two features on one script: a feedback widget (a floating button) and Why-Not-Buy (a one-question card that fires when a visitor abandons your pricing or checkout). You add one script, then fire a few events for the Why-Not-Buy part.
Overview
- Add the script to your site (once). This powers both features.
- For Why-Not-Buy, fire a
high_intentevent when a visitor shows buying intent and aconvertedevent when they purchase. Adding anabandonedevent when they leave without buying is optional but recommended: it gives the card its most accurate timing. - Turn each feature on or off from your dashboard. No redeploy needed.
1. Add the script
Paste this before the closing </body> tag on your site. Replace YOUR_PROJECT_KEY with your key from Dashboard → Project → Settings → Project Key.
<!-- Add before </body> -->
<script>window.feedinbox=window.feedinbox||function(){(window.feedinbox.q=window.feedinbox.q||[]).push(arguments)}</script>
<script async src="https://feedinbox.com/widget.js" data-project-key="YOUR_PROJECT_KEY"></script>Framework snippets
Next.js (App Router):
import Script from 'next/script'
// Add to your app/layout.tsx
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
{children}
{/* Queue stub so events fired before the widget loads aren't lost */}
<Script id="feedinbox-stub" strategy="beforeInteractive">
{`window.feedinbox=window.feedinbox||function(){(window.feedinbox.q=window.feedinbox.q||[]).push(arguments)}`}
</Script>
<Script
src="https://feedinbox.com/widget.js"
data-project-key="YOUR_PROJECT_KEY"
strategy="lazyOnload"
/>
</body>
</html>
)
}
// Then, from your pricing/checkout code, fire the events:
// window.feedinbox('event', 'high_intent', { plan: 'pro' }) // e.g. pricing modal opened
// window.feedinbox('event', 'abandoned') // pricing/checkout closed without buying
// window.feedinbox('event', 'converted') // on successful purchaseReact (Vite / CRA):
<!-- Add to your public/index.html before </body> -->
<script>window.feedinbox=window.feedinbox||function(){(window.feedinbox.q=window.feedinbox.q||[]).push(arguments)}</script>
<script async src="https://feedinbox.com/widget.js" data-project-key="YOUR_PROJECT_KEY"></script>
<!-- Then fire the events from your pricing/checkout code:
window.feedinbox('event', 'high_intent', { plan: 'pro' }) // pricing modal opened
window.feedinbox('event', 'abandoned') // pricing/checkout closed without buying
window.feedinbox('event', 'converted') // on successful purchase -->theme.liquid before </body>. Framer: Site Settings → Custom Code → End of <body>. Google Tag Manager: a Custom HTML tag firing on all pages. The stub line matters: it queues events fired before the script finishes loading.2. Fire the events (Why-Not-Buy)
From your own pricing / checkout code, tell Feedinbox when a visitor shows intent and when they convert:
// Fire when a visitor reaches a buying surface
// (opens your pricing modal, starts checkout, clicks "Upgrade")
window.feedinbox('event', 'high_intent', { plan: 'pro' })
// Fire when they close that surface without buying (recommended:
// the most accurate trigger; shows the card right away)
window.feedinbox('event', 'abandoned')
// Fire when they actually buy. This cancels the pending question,
// so people who purchased are never asked.
window.feedinbox('event', 'converted')
// Stand down because they're progressing, not leaving (signing in,
// starting signup). A command, not an event, and unlike 'converted'
// it doesn't end the session.
window.feedinbox('cancel')- Event names must match your dashboard config. Defaults are
high_intent,abandoned, andconverted(Pro can rename them). abandonedis optional but recommended: without it, the card relies on automatic exit signals (the cursor leaving through the top of the viewport, or the tab being hidden) and a fallback timer. With it, the card appears at the exact moment they pass on buying, and readers are never interrupted mid-scroll.- The
contextobject is optional metadata (e.g.{ plan: 'pro' }). It's shown tagged on each response. - If
convertedfires within the configured delay, the card is cancelled. This is why firing it accurately matters: it stops you asking buyers.
Where to fire them
Fire high_intent at the exact moment of intent, and abandoned when the same surface is closed without buying. A React example:
function PricingModal() {
const openPricing = () => {
setPricingOpen(true)
// tell Feedinbox this is a high-intent moment
window.feedinbox('event', 'high_intent', { plan: 'pro' })
}
const closePricing = () => {
setPricingOpen(false)
// they left without buying: ask why, right now
window.feedinbox('event', 'abandoned')
}
// ...
}Fire converted on your success step (payment confirmed, thank-you page):
// On your payment-success / thank-you step
window.feedinbox('event', 'converted')Call cancel when a visitor moves forward instead of leaving, such as clicking Sign in or starting a signup:
function SignInButton() {
const signIn = () => {
// they're moving forward, not abandoning: don't ask them why
window.feedinbox('cancel')
router.push('/login')
}
return <button onClick={signIn}>Sign in</button>
}converted here: it also ends the session, so someone who signs in, comes back to pricing, and then genuinely bails would never be asked.high_intent moments: pricing modal opens, pricing page loads, checkout starts, "Upgrade" clicked. Good abandoned moments: pricing modal closed, checkout cancelled, "Maybe later" clicked. Good converted moments: payment success callback, subscription-created webhook echoed to the client, thank-you page.abandoned from an unmount. Not a useEffect cleanup, onDestroy, router-leave hook, or beforeunload. React Strict Mode (on by default in Next.js dev) double-invokes effects as mount, unmount, mount, so a cleanup-based abandoned fires milliseconds after high_intent and the card appears the instant the page loads. The same misfire happens in production any time the component remounts. It's also unnecessary: the widget already detects page-level exits by itself, so navigating away or closing the tab is covered. Fire abandoned only from a deliberate user action like a close button.Feature combinations
| You want | Feedback button | Why-Not-Buy | Event calls? |
|---|---|---|---|
| Why-Not-Buy only | Off | On | Yes |
| Feedback only | On | Off | No |
| Both | On | On | Yes |
Toggle the feedback button in Widget editor and Why-Not-Buy in the Why-Not-Buy tab → Configure. Both toggles are free, as is switching the fallback timer on or off.
How the card behaves
- After
high_intent, the card appears on the first exit signal: yourabandonedevent, the cursor leaving through the top of the viewport, or the tab being hidden. Visitors who are still reading are left alone. - If no exit signal fires, a fallback timer (default 30 seconds, configurable) shows the card anyway.
convertedcancels everything. - You can switch that timer off entirely, so the card waits for a real exit signal and nobody is interrupted mid-read. Worth knowing: touch devices have no cursor signal, so with the timer off mobile relies on your
abandonedevent or the tab being hidden. abandonedalso works on its own: ifhigh_intentnever fired, it arms and shows the card in a single call, so you can wire just that one event if it suits your flow better.- Once a visitor answers, converts, or dismisses the card, they aren't asked again for the rest of that session.
cancelis different: it only stands the triggers down, so a laterhigh_intentcan still ask. - Responses land in your dashboard (Why-Not-Buy tab), tagged by plan and country, plus an email the moment each one arrives. You can switch that to a weekly digest, or turn it off, in the project's Why-Not-Buy settings.
Troubleshooting
- Card never appears: confirm Why-Not-Buy is enabled, your event names match the dashboard, and that
convertedisn't firing too early. If you already answered or dismissed it while testing, clear thefeedinbox_intent_keys from sessionStorage (or use a new tab). - Card appears the instant the page loads: something is firing
abandonedon arrival, almost always from auseEffectcleanup or other unmount hook that React Strict Mode triggers immediately. Remove it; page-level exits are detected for you. - Card interrupts people still reading: the fallback timer is too short. Raise the delay, or switch the timer off so the card only appears on a real exit signal.
- Card appears on your login or signup page: a
high_intentarmed on pricing survived the client-side navigation. Callwindow.feedinbox('cancel')on the sign-in or signup action. - Buyers get asked: your
convertedevent isn't firing. Fire it on the real success step. - Nothing loads: check the project key and that the script tag is on the page. Keep the stub line so early events aren't dropped.
Install with your AI agent
Using Cursor, Claude Code, or another AI assistant? Point it at our machine-readable guide and it can add Feedinbox for you:
Read https://feedinbox.com/llms.txt and add Feedinbox Why-Not-Buy to my pricing page. My project key is YOUR_PROJECT_KEY.The guide at /llms.txt is a complete, plain-text version of this page written for AI agents.