Developer Documentation
Get ChurnRecovery running in your app in under 10 minutes. One package, two function calls, zero backend work.
💡Fastest path: Install the SDK, initialize with your API key, and call showCancelFlow() when a user clicks your cancel button. That's it.
⚡ 3-Minute Integration
1. Install the SDKbash
npm install @churnrecovery/sdk
2. Initialize + trigger cancel flowjavascript
import { 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
Package managerbash
# npm
npm install @churnrecovery/sdk
# yarn
yarn add @churnrecovery/sdk
# pnpm
pnpm add @churnrecovery/sdk
CDN Script Tag
Script tag (no build step)html
<script src="https://cdn.churnrecovery.com/sdk/v1.js"></script>
<script>
const cr = ChurnRecovery.init({ apiKey: 'cr_live_xxx' })
</script>
ℹ️The CDN version is identical to the npm package. Use whichever fits your stack. The SDK auto-detects your environment and loads the minimal CSS needed for the cancel flow modal.
🚪 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
1
User clicks cancel
Your app calls showCancelFlow()
2
Reason picker
Customer selects why they're leaving
3
Smart offer
Personalized offer based on the reason
4
Outcome
Customer saved or cancellation confirmed
showCancelFlow() Options
Full options interfacetypescript
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
Custom reasons + offer routingjavascript
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.
Full initialization optionsjavascript
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
.envbash
# 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 months⏸️
Pause
Pause the subscription instead of canceling. Best for customers who plan to return.
{ type: 'pause', months: 2 } // Pause for 2 months, auto-resume💬
Human 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?' }💡Pro tip: Combine offers with A/B testing. Route 50% of "too expensive" customers to 20% off and 50% to 30% off — then measure which save rate is better.
📊 Analytics API
Query your churn recovery data programmatically.
Fetch analytics from the SDKjavascript
// 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 flow
cancel_flow.reason_selectedCustomer selected a cancel reason
cancel_flow.offer_presentedA retention offer was shown
cancel_flow.offer_acceptedCustomer accepted the offer (saved!)
cancel_flow.completedFlow ended (saved or canceled)
winback.email_sentWin-back email was sent to a churned customer
winback.reactivatedChurned customer reactivated via win-back
Webhook handler (Node.js / Express)javascript
import { 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.
Base URLbash
https://api.churnrecovery.com/v1
Authentication
API key headerbash
curl https://api.churnrecovery.com/v1/analytics/save-rate \
-H "Authorization: Bearer cr_live_xxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json"
Key Endpoints
GET/v1/analytics/save-rateGet save rate statistics
GET/v1/analytics/revenueRevenue recovered data
GET/v1/analytics/reasonsChurn reason breakdown
GET/v1/customersList customers with churn data
GET/v1/customers/:idGet customer churn profile
POST/v1/cancel-flowsTrigger a cancel flow (server-side)
GET/v1/offersList configured offers
PUT/v1/offers/:idUpdate an offer configuration
POST/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.
Connect Stripejavascript
// 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,
}
})
ℹ️When Stripe is connected, ChurnRecovery automatically applies accepted discount offers as Stripe coupons and pause offers as subscription pauses. No additional code needed.
Stripe webhook handler for failed paymentsjavascript
// 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.
React integrationjsx
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>
)
}
Pre-built CancelButton componentjsx
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
Is it really free?
Yes. ChurnRecovery is completely free — no usage limits, no feature gates, no hidden fees. We monetize through optional premium add-ons (custom branding removal, SLA guarantees, dedicated support) that most teams won't need.
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?
Join the waitlist to get your API key. Most teams are live in under an hour.