Developer Documentation
Get ChurnRecovery running in your app in under 10 minutes. One package, two function calls, zero backend work.
showCancelFlow() when a user clicks your cancel button. That's it.⚡ 3-Minute Integration
npm install @churnrecovery/sdkimport { ChurnRecovery } from '@churnrecovery/sdk'
// Initialize once (usually in your app entry point)
const cr = ChurnRecovery.init({
apiKey: 'cr_live_xxxxxxxxxxxxxxxx',
stripeCustomerId: user.stripeId, // optional but recommended
})
// When user clicks "Cancel Subscription"
document.getElementById('cancel-btn').addEventListener('click', async () => {
const result = await cr.showCancelFlow({
customerId: user.id,
subscriptionId: user.subscriptionId,
planName: 'Pro Plan',
mrr: 49.00,
})
if (result.saved) {
console.log('Customer saved!', result.offer)
// Update your UI — subscription is still active
} else {
console.log('Customer canceled', result.reason)
// Process the cancellation in your system
}
})That's the entire integration. ChurnRecovery handles the cancel flow UI, reason collection, offer presentation, and analytics — all from that single showCancelFlow() call.
📦 Installation
Choose your preferred method. The npm package includes TypeScript types and tree-shakes to ~8KB gzipped.
npm / yarn / pnpm
# npm
npm install @churnrecovery/sdk
# yarn
yarn add @churnrecovery/sdk
# pnpm
pnpm add @churnrecovery/sdkCDN Script Tag
<script src="https://cdn.churnrecovery.com/sdk/v1.js"></script>
<script>
const cr = ChurnRecovery.init({ apiKey: 'cr_live_xxx' })
</script>🚪 Cancel Flow
The cancel flow is the core of ChurnRecovery. It intercepts the cancel action, collects the reason, presents a personalized retention offer, and reports the outcome.
How it works
showCancelFlow() Options
interface CancelFlowOptions {
// Required
customerId: string // Your internal customer ID
subscriptionId: string // Subscription to cancel
// Recommended
planName?: string // e.g. "Pro Plan" — shown in the modal
mrr?: number // Monthly revenue — used for analytics
stripeCustomerId?: string // Enables auto-apply offers in Stripe
// Customization
reasons?: CancelReason[] // Override default cancel reasons
offers?: OfferConfig[] // Override default offers per reason
theme?: ThemeConfig // Custom colors, fonts, branding
locale?: string // 'en' | 'es' | 'fr' | 'de' | 'pt' | 'ja'
// Callbacks
onReasonSelected?: (reason: CancelReason) => void
onOfferPresented?: (offer: Offer) => void
onSave?: (result: SaveResult) => void
onCancel?: (result: CancelResult) => void
}
interface CancelFlowResult {
saved: boolean
reason: string
offer?: {
type: 'discount' | 'pause' | 'human' | 'feedback'
accepted: boolean
details: Record<string, any>
}
feedback?: string
sessionId: string // For analytics correlation
}Custom cancel reasons
const result = await cr.showCancelFlow({
customerId: user.id,
subscriptionId: user.subId,
reasons: [
{
id: 'too-expensive',
label: 'Too expensive',
icon: '💰',
offer: { type: 'discount', percent: 30, duration: 3 }
},
{
id: 'not-using',
label: "I'm not using it enough",
icon: '😴',
offer: { type: 'pause', months: 2 }
},
{
id: 'switching',
label: 'Switching to a competitor',
icon: '👋',
offer: { type: 'discount', percent: 50, duration: 6 }
},
{
id: 'missing-feature',
label: 'Missing a feature I need',
icon: '🔧',
offer: { type: 'human', message: 'Let us know — we might already be building it.' }
},
{
id: 'other',
label: 'Something else',
icon: '💬',
offer: { type: 'feedback' }
},
]
})⚙️ Configuration
Configure ChurnRecovery globally at initialization or per cancel flow call.
const cr = ChurnRecovery.init({
// Required
apiKey: 'cr_live_xxxxxxxxxxxxxxxx',
// Optional: Stripe integration
stripeCustomerId: user.stripeId,
// Optional: Theme customization
theme: {
primaryColor: '#6B4FA0', // Your brand color
backgroundColor: '#FFFFFF', // Modal background
fontFamily: 'Inter, sans-serif', // Your brand font
borderRadius: '12px', // Modal corners
logo: 'https://yourapp.com/logo.svg',
},
// Optional: Behavior
locale: 'en', // UI language
testMode: false, // true = no real actions, console logs
closeOnOverlayClick: true, // Allow dismiss by clicking outside
showPoweredBy: true, // "Powered by ChurnRecovery" badge
// Optional: Analytics
onEvent: (event) => {
// Forward all events to your analytics
analytics.track(event.type, event.data)
},
})Environment variables
# Your API key (get it from the dashboard)
CHURNRECOVERY_API_KEY=cr_live_xxxxxxxxxxxxxxxx
# Optional: Stripe secret for server-side operations
CHURNRECOVERY_STRIPE_SECRET=sk_live_xxx
# Optional: Webhook signing secret
CHURNRECOVERY_WEBHOOK_SECRET=whsec_xxx🎁 Offer Types
Four built-in offer types handle the vast majority of cancel scenarios.
Discount
Reduce the subscription price for a set number of months. Best for price-sensitive customers.
{ type: 'discount', percent: 30, duration: 3 } // 30% off for 3 monthsPause
Pause the subscription instead of canceling. Best for customers who plan to return.
{ type: 'pause', months: 2 } // Pause for 2 months, auto-resumeHuman Escalation
Route the customer to live chat or support. Best for complex cases or enterprise accounts.
{ type: 'human', url: '/support/chat', message: 'Talk to us first' }Feedback Only
Just collect the feedback, no counter-offer. Best for customers you know won't stay.
{ type: 'feedback', prompt: 'Any feedback for us?' }📊 Analytics API
Query your churn recovery data programmatically.
// Save rate over the last 30 days
const stats = await cr.analytics.getSaveRate({
period: '30d',
groupBy: 'reason', // or 'plan', 'country', 'week'
})
// → { overall: 0.34, byReason: { 'too-expensive': 0.52, ... } }
// Revenue recovered
const revenue = await cr.analytics.getRevenueRecovered({
period: '30d',
})
// → { total: 12450, byMonth: [...], bySaveType: { discount: 8200, pause: 4250 } }
// Churn reason breakdown
const reasons = await cr.analytics.getChurnReasons({
period: '90d',
plan: 'pro',
})
// → [{ reason: 'too-expensive', count: 142, pct: 0.38 }, ...]🔗 Webhooks
Receive real-time notifications when customers interact with the cancel flow.
Events
cancel_flow.startedCustomer opened the cancel flowcancel_flow.reason_selectedCustomer selected a cancel reasoncancel_flow.offer_presentedA retention offer was showncancel_flow.offer_acceptedCustomer accepted the offer (saved!)cancel_flow.completedFlow ended (saved or canceled)winback.email_sentWin-back email was sent to a churned customerwinback.reactivatedChurned customer reactivated via win-backimport { verifyWebhook } from '@churnrecovery/sdk'
app.post('/webhooks/churnrecovery', (req, res) => {
const event = verifyWebhook(req.body, req.headers, process.env.WEBHOOK_SECRET)
switch (event.type) {
case 'cancel_flow.offer_accepted':
// Customer was saved — update your records
db.subscriptions.update(event.data.subscriptionId, { status: 'active' })
slack.notify(`🎉 Saved ${event.data.customerEmail} — ${event.data.offer.type}`)
break
case 'cancel_flow.completed':
if (!event.data.saved) {
// Customer churned — trigger your internal offboarding
offboarding.start(event.data.customerId)
}
break
}
res.json({ received: true })
})🌐 REST API
Full REST API for server-side integrations. All endpoints are versioned and return JSON.
https://api.churnrecovery.com/v1Authentication
curl https://api.churnrecovery.com/v1/analytics/save-rate \
-H "Authorization: Bearer cr_live_xxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json"Key Endpoints
/v1/analytics/save-rateGet save rate statistics/v1/analytics/revenueRevenue recovered data/v1/analytics/reasonsChurn reason breakdown/v1/customersList customers with churn data/v1/customers/:idGet customer churn profile/v1/cancel-flowsTrigger a cancel flow (server-side)/v1/offersList configured offers/v1/offers/:idUpdate an offer configuration/v1/webhooksRegister a webhook endpoint💳 Stripe Integration
Connect Stripe in 30 seconds. ChurnRecovery reads your plans, customers, and subscriptions — and automatically applies retention offers (discounts, pauses) directly in Stripe.
// Option A: OAuth (recommended — connect in your dashboard)
// Just click "Connect Stripe" in your ChurnRecovery dashboard
// Option B: API key (for server-side setups)
const cr = ChurnRecovery.init({
apiKey: 'cr_live_xxx',
stripe: {
secretKey: process.env.STRIPE_SECRET_KEY,
webhookSecret: process.env.STRIPE_WEBHOOK_SECRET,
}
})// ChurnRecovery automatically handles dunning if Stripe is connected
// But you can also set up manual webhook forwarding:
import { handleStripeWebhook } from '@churnrecovery/sdk'
app.post('/webhooks/stripe', async (req, res) => {
await handleStripeWebhook(req.body, req.headers['stripe-signature'])
res.json({ ok: true })
})
// ChurnRecovery will automatically:
// 1. Detect invoice.payment_failed events
// 2. Send smart dunning emails with card update links
// 3. Retry payment at optimal intervals
// 4. Track recovery in your analytics⚛️ React SDK
First-class React support with hooks and components.
import { ChurnRecoveryProvider, useCancelFlow } from '@churnrecovery/react'
// Wrap your app
function App() {
return (
<ChurnRecoveryProvider apiKey="cr_live_xxx">
<YourApp />
</ChurnRecoveryProvider>
)
}
// Use the hook in any component
function SubscriptionSettings({ user }) {
const { showCancelFlow, isLoading } = useCancelFlow()
const handleCancel = async () => {
const result = await showCancelFlow({
customerId: user.id,
subscriptionId: user.subscriptionId,
planName: user.planName,
mrr: user.mrr,
})
if (result.saved) {
toast.success('Welcome back! Your offer has been applied.')
} else {
router.push('/goodbye')
}
}
return (
<button onClick={handleCancel} disabled={isLoading}>
{isLoading ? 'Loading...' : 'Cancel Subscription'}
</button>
)
}import { CancelButton } from '@churnrecovery/react'
// Drop-in button — handles everything
<CancelButton
customerId={user.id}
subscriptionId={user.subId}
planName="Pro Plan"
mrr={49}
onSave={() => toast.success('Subscription saved!')}
onCancel={() => router.push('/goodbye')}
className="your-button-class"
>
Cancel Subscription
</CancelButton>❓ Frequently Asked Questions
What does ChurnRecovery cost?
ChurnRecovery is $20/month — flat. No per-subscriber fees, no per-recovery fees, no usage limits. Start with a 30-day free trial, no credit card required.
What billing providers do you support?
Stripe is natively integrated with automatic offer application. Paddle support is in beta. For other providers (Chargebee, Recurly, Braintree), you can use our REST API and webhooks for manual integration.
Does it work with server-side rendering?
Yes. The SDK detects the environment automatically. On the server, initialization is a no-op. The cancel flow modal only renders client-side. Full support for Next.js, Nuxt, Remix, and SvelteKit.
How does the A/B testing work?
Define multiple offers per cancel reason, and ChurnRecovery automatically splits traffic and tracks acceptance rates. Results are statistically validated — we'll tell you when a variant reaches significance.
Can I customize the cancel flow UI?
Fully. Pass a theme object with your brand colors, fonts, logo, and border radius. For deeper customization, use the headless mode — we handle the logic, you handle the UI.
What about GDPR?
ChurnRecovery is GDPR-compliant. We process data as your data processor, store minimal customer data, and provide full data export and deletion APIs. We don't sell data. Period.
Ready to integrate?
Sign up to get your API key. Most teams are live in under an hour.