Stripe doesn't calculate churn rate for you—you need to pull subscription data and do the math. If you're bleeding customers and don't know why, you can't fix it. Here's how to set up churn tracking in Stripe and get actual numbers.
Set Up Webhooks for Subscription Events
The cleanest way to track churn is to listen for when customers cancel. Stripe sends webhook events for every subscription state change.
Create a webhook endpoint in Stripe
Go to Developers > Webhooks in your Stripe Dashboard. Click Add an endpoint and point it to a publicly accessible URL on your server (e.g., https://your-api.com/webhooks/stripe). Select the events you want to listen for: customer.subscription.deleted, customer.subscription.updated, and charge.failed.
const express = require('express');
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
const app = express();
app.post('/webhooks/stripe', express.raw({type: 'application/json'}), (req, res) => {
const sig = req.headers['stripe-signature'];
let event;
try {
event = stripe.webhooks.constructEvent(
req.body,
sig,
process.env.STRIPE_WEBHOOK_SECRET
);
} catch (err) {
console.error(`Webhook signature verification failed: ${err.message}`);
return res.sendStatus(400);
}
res.sendStatus(200);
});
app.listen(3000, () => console.log('Webhook server running'));Handle the subscription.deleted event
When a subscription is canceled, Stripe sends a customer.subscription.deleted event. Capture this in your webhook handler and log it to your database or analytics tool. Include the subscription.id, customer.id, and the canceled_at timestamp.
if (event.type === 'customer.subscription.deleted') {
const subscription = event.data.object;
console.log(`Churn detected:`);
console.log(` Customer: ${subscription.customer}`);
console.log(` Subscription: ${subscription.id}`);
console.log(` Canceled at: ${new Date(subscription.canceled_at * 1000)}`);
console.log(` Reason: ${subscription.cancellation_details?.reason}`);
// Write to your database for analysis
await db.insert('churn_events', {
customer_id: subscription.customer,
subscription_id: subscription.id,
churned_at: new Date(subscription.canceled_at * 1000),
reason: subscription.cancellation_details?.reason
});
}stripe listen --forward-to localhost:3000/webhooks/stripe before deploying.Query Subscription Data from Stripe
To calculate churn retrospectively, you need to pull subscription snapshots from Stripe. Use the Subscriptions API with time-based filters.
List all subscriptions in a date range
Use stripe.subscriptions.list() to fetch subscriptions created in a specific period. Stripe returns paginated results (100 max per request), so loop through all pages. Include the status parameter set to all to see active, canceled, and past-due subscriptions.
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
const startOfMonth = Math.floor(new Date(2024, 0, 1).getTime() / 1000);
const endOfMonth = Math.floor(new Date(2024, 0, 31).getTime() / 1000);
let allSubscriptions = [];
let hasMore = true;
let startingAfter = null;
while (hasMore) {
const subscriptions = await stripe.subscriptions.list({
limit: 100,
status: 'all',
created: { gte: startOfMonth, lte: endOfMonth },
starting_after: startingAfter
});
allSubscriptions = allSubscriptions.concat(subscriptions.data);
hasMore = subscriptions.has_more;
startingAfter = subscriptions.data[subscriptions.data.length - 1]?.id;
}
console.log(`Found ${allSubscriptions.length} subscriptions`);Calculate churn rate from subscription statuses
Count subscriptions with status: 'active' and status: 'canceled'. Churn rate is canceled subscriptions divided by total subscriptions at the start of your period, multiplied by 100. Subscriptions with status: 'past_due' are at risk but not yet churned.
const activeCount = allSubscriptions.filter(sub => sub.status === 'active').length;
const canceledCount = allSubscriptions.filter(sub => sub.status === 'canceled').length;
const totalAtStart = activeCount + canceledCount;
const churnRate = (canceledCount / totalAtStart) * 100;
console.log(`Active subscriptions: ${activeCount}`);
console.log(`Canceled subscriptions: ${canceledCount}`);
console.log(`Churn rate: ${churnRate.toFixed(2)}%`);
// Example output:
// Active subscriptions: 950
// Canceled subscriptions: 50
// Churn rate: 5.00%Track Cancellation Reasons
Knowing why customers cancel is as important as knowing how many. Stripe's Billing Portal prompts customers to pick a reason when they opt out.
Enable cancellation feedback in Billing Portal
In Billing Portal Settings, enable Cancellation options. This prompts customers to pick a reason when they cancel (e.g., 'Too expensive', 'Not using it', 'Switching services'). Stripe stores this in subscription.cancellation_details.reason.
const reason = subscription.cancellation_details?.reason;
const feedback = subscription.cancellation_details?.feedback;
switch (reason) {
case 'cancellation_requested':
console.log(`Customer canceled: "${feedback}"`);
break;
case 'billing_cycle_anchor_change':
console.log('Subscription ended due to billing cycle change');
break;
case 'dunning_failure':
console.log('Payment failed and dunning exhausted all retries');
break;
case 'upgrade':
console.log('Customer upgraded to different plan');
break;
}
// Group churn by reason
await db.insert('churn_reasons', {
customer_id: subscription.customer,
reason: reason,
feedback: feedback,
churned_at: new Date(subscription.canceled_at * 1000)
});Common Pitfalls
- Confusing 'canceled' with 'past_due'. A past-due subscription might still pay. Define churn explicitly: only canceled subscriptions count.
- Mixing subscription cohorts. Count subscriptions that *started* in the period and stayed or churned. Don't include ones created and canceled in the same month without context.
- Forgetting to paginate. Stripe's list endpoint maxes at 100 items. If you have 500 subscriptions and only fetch once, your churn rate will be 5x too high.
- Ignoring involuntary churn. If dunning fails and Stripe auto-cancels, that's churn too—but different from a customer choosing to leave. Track both.
Wrapping Up
Now you have real churn data in Stripe. Use webhooks to catch cancellations in real-time and the Subscriptions API to calculate historical rates by month. Pay attention to cancellation reasons—they're your north star for retention fixes. If you want to track this automatically across tools and correlate it with your product metrics, Product Analyst can help.