Docs navigation
Build a connected flow
Connected mode runs the SDK against a server-minted token. With session set, the SDK fetches the dashboard-configured flow from Churnkey and applies billing actions through your connected provider on accept. The component API is the same as open-source mode — you add session and appId.
Before you begin
- A Churnkey account with your billing provider connected (Stripe, Braintree, Chargebee, Paddle, Maxio). Without a connected provider, connected mode records sessions but can't apply billing actions.
- Your
appIdand an API key from the Churnkey dashboard. The API key never leaves your server.
Direct mode. The token alone is enough for server-driven flow config and session recording. You can mint a token without connecting a provider and pass
customer+subscriptionsdirectly on the component; billing actions fall back to yourhandle<Type>callbacks. See Server-side tokens.
1. Install the server SDK
npm install @churnkey/node@churnkey/node has no runtime dependencies. Token signing is a local HMAC computation, so minting doesn't hit the network.
2. Mint a token in an authenticated route
// server.ts
import { Churnkey } from '@churnkey/node'
import express from 'express'
const ck = new Churnkey({
appId: process.env.CHURNKEY_APP_ID!,
apiKey: process.env.CHURNKEY_API_KEY!,
})
const app = express()
app.post('/api/cancel-flow/session', requireAuth, async (req, res) => {
const token = ck.createToken({
customerId: req.user.customerId,
subscriptionId: req.user.subscriptionId, // optional
mode: process.env.NODE_ENV === 'production' ? 'live' : 'test',
})
res.json({ token })
})The token is a short-lived signed credential. Churnkey trusts the signed customerId over anything the client sends, so re-authenticate before issuing one. Treat the token as a bearer credential — anyone who holds it can take billing actions on the customer's behalf until it expires.
3. Fetch the token on demand from the client
import { useState } from 'react'
import { CancelFlow } from '@churnkey/react'
import '@churnkey/react/styles.css'
export function CancelButton() {
const [token, setToken] = useState<string | null>(null)
const [open, setOpen] = useState(false)
async function startCancel() {
const res = await fetch('/api/cancel-flow/session', { method: 'POST' })
const { token } = await res.json()
setToken(token)
setOpen(true)
}
return (
<>
<button onClick={startCancel}>Cancel subscription</button>
{open && token && (
<CancelFlow
appId="app_xxx"
session={token}
onClose={() => setOpen(false)}
/>
)}
</>
)
}The SDK fetches the flow config from Churnkey, renders it, and applies accepted offers through your connected provider.
4. Keep one action client-side (optional)
Defining a handle<Type> callback opts that offer type out of Churnkey's server action. Use it when your billing action needs logic that has to stay in your code — custom proration, writes to sibling systems, audit-log requirements.
<CancelFlow
appId="app_xxx"
session={token}
// Churnkey applies discount, pause, trial_extension via the connected provider
// (no handlers defined for those)
handlePlanChange={async (offer, customer) => {
// Our plan change has custom proration logic. Keep it.
await myBilling.changePlan({
customerId: customer?.id,
planId: offer.result.planId,
proration: 'create_invoice',
})
}}
onAccept={(offer) => analytics.track('offer_accepted', { type: offer.type })}
onCancel={() => router.push('/goodbye')}
/>The decision is per offer type: define a handler for any offer you want to own, and the rest stay with Churnkey.
Next steps
- Server-side tokens — token minting and Direct mode in detail.
- Connecting billing providers — Stripe, Braintree, Chargebee, Paddle, Maxio setup.