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 source | Analytics | Connected | |
|---|---|---|---|
steps | required | required | optional (dashboard supplies them) |
appId | – | required | required |
customer | – | required | optional (the token's customer ID is authoritative) |
session | – | – | required |
| Churnkey account | – | free | paid (Starter or higher) |
| Connected billing provider | – | – | required |
| Sessions recorded | no | yes | yes |
| Churnkey applies billing actions | no | no | yes (per-offer opt-out via handle<Type>) |
| Intelligence tier | – | – | required 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 usescouponIdto 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
customerandsubscriptionsin Direct shape on the component. Billing actions fall back to yourhandle<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
typematches a server step replaces it. - A local step whose
typedoesn'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 account | Open source |
| Want analytics on cancel reasons and offer effectiveness, handling billing yourself | Analytics |
| Want Churnkey to apply discounts, pauses, and cancellations through your billing provider | Connected |
| Want the cancel flow editable in the No-Code Editor without redeploying | Connected |
| Want Adaptive Offers, Feedback AI, or Automatic AI Translations | Connected + Intelligence tier |
Next steps
- Quickstart — render a working flow.
- A connected flow — server-side wiring end-to-end.
- Concepts — steps, offers, handlers, and listeners.