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

  1. Add the script to your site (once). This powers both features.
  2. For Why-Not-Buy, fire a high_intent event when a visitor shows buying intent and a converted event when they purchase. Adding an abandoned event when they leave without buying is optional but recommended: it gives the card its most accurate timing.
  3. Turn each feature on or off from your dashboard. No redeploy needed.
The feedback widget needs only the script. Why-Not-Buy needs the script and the event calls, because the card only fires when you signal intent.

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.

Any site
<!-- 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):

app/layout.tsx
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 purchase

React (Vite / CRA):

public/index.html
<!-- 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 -->
No-code platforms: use the platform's custom-code slot. Webflow: Project Settings → Custom Code → Footer. Shopify: 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:

Your pricing / checkout code
// 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, and converted (Pro can rename them).
  • abandoned is 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 context object is optional metadata (e.g. { plan: 'pro' }). It's shown tagged on each response.
  • If converted fires 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:

PricingModal.jsx
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):

success handler
// 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:

SignInButton.jsx
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>
}
This matters most in single-page apps. On a client-side route change the document is never torn down, so the armed timer and exit listeners survive the navigation and the card can surface on your login or signup page seconds later. A full page load doesn't have this problem, since the old document is destroyed along with everything the widget had armed. Don't reach for converted here: it also ends the session, so someone who signs in, comes back to pricing, and then genuinely bails would never be asked.
Good 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.
Don't fire 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 wantFeedback buttonWhy-Not-BuyEvent calls?
Why-Not-Buy onlyOffOnYes
Feedback onlyOnOffNo
BothOnOnYes

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: your abandoned event, 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. converted cancels 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 abandoned event or the tab being hidden.
  • abandoned also works on its own: if high_intent never 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. cancel is different: it only stands the triggers down, so a later high_intent can 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 converted isn't firing too early. If you already answered or dismissed it while testing, clear the feedinbox_intent_ keys from sessionStorage (or use a new tab).
  • Card appears the instant the page loads: something is firing abandoned on arrival, almost always from a useEffect cleanup 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_intent armed on pricing survived the client-side navigation. Call window.feedinbox('cancel') on the sign-in or signup action.
  • Buyers get asked: your converted event 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:

Prompt for your AI assistant
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.