Docs navigation

Integration levels

The SDK runs at three integration levels. Each adds props on top of the level before.

Open source is free. The component runs in the browser. You provide steps and the handle<Type> callbacks. No account, no network calls.

Analytics is free with a Churnkey account. Pass appId and customer in addition to steps. The SDK reports each session to your dashboard. Your handlers still run billing actions.

Connected requires a paid Churnkey plan (Starter or higher). Pass a server-signed session token and connect a billing provider in your Churnkey dashboard. Churnkey serves the flow config, applies segments and A/B tests, runs billing actions through your provider, and — on the Intelligence tier — picks offers with Adaptive Offers.

The component API is identical at every level; you upgrade by adding props.

Open sourceAnalyticsConnected
stepsrequiredrequiredoptional (dashboard supplies them)
appIdrequiredrequired
customerrequiredoptional (the token's customer ID is authoritative)
sessionrequired
Churnkey accountfreepaid (Starter or higher)
Connected billing providerrequired
Sessions recordednoyesyes
Churnkey applies billing actionsnonoyes (per-offer opt-out via handle<Type>)
Intelligence tierrequired for Adaptive Offers, Feedback AI, and Automatic AI Translations

Open source

Pass steps and the handle<Type> callbacks you need. The SDK runs entirely in the browser — no network calls, no account requirement.

<CancelFlow
  steps={[
    {
      type: 'survey',
      reasons: [
        {
          id: 'expensive',
          label: 'Too expensive',
          offer: {
            type: 'discount',
            couponId: 'STRIPE_SAVE20',
            percentOff: 20,
            durationInMonths: 3,
          },
        },
        { id: 'not-using', label: 'Not using it' },
      ],
    },
    { type: 'feedback' },
    { type: 'confirm' },
  ]}
  handleDiscount={async (offer) => myBilling.applyCoupon(offer.couponId)}
  handleCancel={async () => myBilling.cancel()}
  onClose={() => setOpen(false)}
/>

Use open source while you're prototyping, when you don't need cross-customer analytics, or when you want a polished cancel flow without server-side dependencies. The SDK works indefinitely without a Churnkey account.

Discount offers carry both an identifier (couponId) and display fields (percentOff, durationInMonths). The SDK uses the display fields to render the offer; your handler uses couponId to apply the discount in your billing provider. Keep the two in sync.

Analytics

Sign up for a free Churnkey account and pass your appId and customer. The SDK records each session on completion. Your steps and handlers don't change.

<CancelFlow
  appId="app_xxx"
  customer={{ id: 'cus_123', email: 'jane@acme.com' }}
  subscriptions={[{
    id: 'sub_456',
    start: '2024-06-01',
    status: { name: 'active', currentPeriod: { start: '2025-04-01', end: '2025-05-01' } },
    items: [{ price: { id: 'price_pro', amount: { value: 2999, currency: 'usd' } } }],
  }]}
  steps={steps}
  handleDiscount={async (offer) => myBilling.applyCoupon(offer.couponId)}
  handleCancel={async () => myBilling.cancel()}
/>

The dashboard shows cancel reasons, save rates, offer effectiveness, drop-off funnels, and feedback sentiment. Pass subscriptions to surface revenue numbers alongside session counts.

Handlers are still the only path that changes billing state in analytics mode. To let Churnkey apply discounts, pauses, and cancellations, see Connected.

Test mode

Pass mode="test" to keep staging traffic out of your live analytics.

<CancelFlow
  appId="app_xxx"
  customer={{ id: 'cus_123' }}
  mode={process.env.NODE_ENV === 'production' ? 'live' : 'test'}
  steps={steps}
  handleCancel={handleCancel}
/>

Defaults to 'live'. Test sessions land in a separate dashboard view, excluded from your metrics and Slack notifications.

Connected

Connected requires a paid Churnkey plan (Starter or higher). Connect a billing provider (Stripe, Chargebee, Paddle, Braintree, Maxio) in your Churnkey dashboard. Mint a signed session token on your server. Pass the token to the SDK.

<CancelFlow
  appId="app_xxx"
  session={token}
  onAccept={(offer) => analytics.track('offer_accepted', { type: offer.type })}
  onCancel={() => router.push('/goodbye')}
/>

With a token, Churnkey runs the server-side parts of the flow:

  • Selects which offer to show, applying segments, A/B test variants, and eligibility rules.
  • Enforces cooldowns so the same customer can't repeatedly claim the same offer.
  • Applies the accepted offer through the connected billing provider.
  • Records the outcome with the metadata the dashboard needs.

The Intelligence tier adds three AI features on top of Connected:

  • Adaptive Offers replaces rules-based offer selection with an AI model trained on your data.
  • Feedback AI classifies and themes free-text responses.
  • Automatic AI Translations serves the flow in the customer's language.

The flow configuration also comes from the dashboard. Your team edits reasons, offers, copy, and A/B tests in the No-Code Editor without redeploying. Pass steps in code to override or extend the dashboard config — see Mixing local and server steps.

Direct mode. A connected billing provider is the common path, but the token alone is enough for server-driven flow config and session recording. Mint a token without connecting a provider and pass customer and subscriptions in Direct shape on the component. Billing actions fall back to your handle<Type> callbacks. Use this when your billing system isn't natively supported, or when you want to keep billing entirely in your own code.

Per-offer opt-out

Churnkey applies billing actions by default. Define a handle<Type> to keep one offer type client-side; the rest stay with Churnkey.

<CancelFlow
  session={token}
  // Discount, pause, trial extension: no handlers defined, so Churnkey
  // applies them through the connected billing provider.
 
  // Plan change: kept client-side because our proration logic is
  // custom enough that expressing it server-side isn't worth it.
  handlePlanChange={async (offer) => myBilling.changePlan(offer.plans[0].id)}
 
  onAccept={(offer) => analytics.track('offer_accepted', { type: offer.type })}
/>

See Callbacks.

Mixing local and server steps

Pass steps alongside session and the SDK merges them with the dashboard's flow:

  • A local step whose type matches a server step replaces it.
  • A local step whose type doesn't match any server step is appended.
<CancelFlow
  session={token}
  steps={[
    { type: 'confirm', title: 'Before you cancel' },
    { type: 'nps', title: 'Quick question', data: { scale: 10 } },
  ]}
  customComponents={{ nps: NpsStep }}
/>

The local confirm step replaces whatever confirm step the dashboard ships. The local nps step is appended.

Choosing a level

If you...Use
Are prototyping or have no Churnkey accountOpen source
Want analytics on cancel reasons and offer effectiveness, handling billing yourselfAnalytics
Want Churnkey to apply discounts, pauses, and cancellations through your billing providerConnected
Want the cancel flow editable in the No-Code Editor without redeployingConnected
Want Adaptive Offers, Feedback AI, or Automatic AI TranslationsConnected + Intelligence tier

Next steps