Docs navigation
Concepts
The SDK is built around five concepts: steps, offers, handlers, listeners, and integration levels. The rest of the documentation builds on these.
Steps
A cancel flow is an ordered list of steps. Each step has a type that decides what gets rendered. The SDK ships five built-in types and accepts any other string as a custom type.
| Type | What it renders |
|---|---|
survey | A list of cancellation reasons. Reasons can carry an attached offer. |
offer | A value proposition: discount, pause, plan change, trial extension, contact, redirect, or custom. |
feedback | A freeform text input. |
confirm | The final cancellation prompt. |
success | The terminal state, either saved or cancelled. |
Any type the SDK doesn't recognize is a custom step. The SDK navigates through it like a built-in and looks up your renderer in customComponents keyed by that string.
const steps = [
{ type: 'survey', reasons: [/* ... */] },
{ type: 'nps', data: { scale: 10 } }, // custom step
{ type: 'feedback' },
{ type: 'confirm' },
]The SDK walks steps in order, with two exceptions. Picking a survey reason with an attached offer routes the flow to that offer next, skipping any intervening steps. Accepting an offer advances directly to success.
Offers
An offer is a value proposition shown during the flow to keep the customer. Attach one to a survey reason so it appears when the customer picks that reason, or declare an OfferStep directly in steps to show one up front.
| Type | What it captures |
|---|---|
discount | A coupon: percent or amount off, fixed-duration or recurring. |
pause | A subscription hold for a fixed window. |
plan_change | A step-down to a smaller plan. |
trial_extension | Additional trial days. |
contact | A link to support: email, phone, or chat. |
redirect | A link to a custom URL. |
| Custom | Any string type paired with a customComponents entry. |
Discount offers have two kinds of fields. Identity fields like couponId are what your billing system uses to apply the discount. Display fields like percentOff and durationInMonths are what the SDK shows the customer. The SDK doesn't verify the two against each other, so you can either reference an existing coupon (set couponId) or build it on the fly inside handleDiscount from the display fields (omit couponId).
{
type: 'survey',
reasons: [{
id: 'expensive',
label: 'Too expensive',
offer: {
type: 'discount',
couponId: 'STRIPE_SAVE20', // identity: passed to handleDiscount
percentOff: 20, // display: shown to the customer
durationInMonths: 3, // display: shown to the customer
},
}],
}Handlers and listeners
The SDK separates the code that performs an action from the code that reacts to it.
| Prefix | Purpose | Runs |
|---|---|---|
handle<Type> | Apply the discount, pause the subscription, cancel the customer. | When the customer accepts. |
on<Type> | Side effects: analytics, toasts, route changes. | After the action commits. |
handleDiscount applies the discount in your billing system — either your code in open source and analytics modes, or Churnkey's server in connected mode against a connected provider. onDiscount runs after the action commits, for follow-ups like refreshing the customer's billing page or firing an analytics event.
Putting analytics inside handleDiscount would delay every transition behind a slow third-party request. Putting the billing call inside onDiscount would let the success screen render before the discount applied, and a failure would leave the flow inconsistent with billing state. Keeping them separate prevents both.
<CancelFlow
session={token} // connected mode
handleDiscount={undefined} // Churnkey applies the discount
handlePlanChange={async (offer, customer) => // your code applies plan change
myBilling.changePlan(offer.plans[0].id)
}
onAccept={(offer) =>
analytics.track('cancel_flow.offer_accepted', { type: offer.type })
}
/>A handler that throws aborts the transition. The customer stays on the offer step with error set and can press the action again. No listener fires, since no action committed.
Integration levels
The SDK runs at three integration levels, selected by which props you pass. Each level is a superset of the one below.
| Level | Required props | Sessions recorded | Churnkey applies actions |
|---|---|---|---|
| Open source | steps | no | no |
| Analytics | steps, appId, customer | yes | no |
| Connected | appId, session | yes | yes (per-offer opt-out via handle<Type>) |
At the open-source level, the SDK runs entirely in your app. Your code defines every step, your handlers run every action, and nothing leaves the client.
Analytics adds two props: appId (your Churnkey workspace, free account) and customer (the user). The SDK reports every session to Churnkey. The dashboard shows reasons picked, offers converted, and save rate across the population. Your handlers still do all the billing work.
Connected adds a server-signed session token and a billing provider (Stripe, Chargebee, etc.) connected in the Churnkey dashboard. The SDK fetches the flow configuration from Churnkey — so the No-Code Editor can change the flow without a redeploy — and runs billing actions through the connected provider on accept. The Intelligence tier adds Adaptive Offers, an AI model that picks the offer for each session in place of static rules. Defining handle<Type> for a specific offer overrides Churnkey's server action for that type only.
See Integration levels for the upgrade walkthrough.
What runs where
The SDK is intentionally thin. Adaptive Offers, Feedback AI, Automatic AI Translations, segment-specific variants, and A/B tests all run on Churnkey's servers, gated behind the session token.
| Capability | Client (SDK) | Server (connected mode) |
|---|---|---|
| Step navigation and rendering | yes | — |
| Capturing reasons and feedback | yes | — |
| Recording session outcomes | yes | — |
| Offer selection (segments, A/B, eligibility, cooldowns) | — | yes |
| Adaptive Offers (Intelligence tier) | — | yes |
| Applying billing actions | optional via handle<Type> | yes by default |
| Feedback AI, webhooks | — | yes |
Upgrading from open source to connected adds the session token on <CancelFlow> and a token-minting endpoint on your server. The component API doesn't change.
Next steps
- Quickstart — render a working flow.
- Integration levels — the open-source → analytics → connected ladder, with code for each.
- Showcase — every built-in step type and the drop-in recipes.