Stripe doesn't calculate churn rate for you—it gives you the raw subscription events. To catch churn early, you need to set up webhooks that listen for cancellations, build the math to calculate your rate, and fire alerts when it crosses your threshold.
Capture Cancellation Events with Webhooks
Every time a customer cancels, Stripe sends a customer.subscription.deleted event. Hook into this to start tracking churn.
Create a webhook endpoint in your app
In Stripe Dashboard, go to Developers > Webhooks. Click Add endpoint and enter your URL (e.g., https://yourapp.com/api/stripe/webhooks). Select customer.subscription.deleted and customer.subscription.updated events. Stripe will sign requests with a secret key—keep it safe.
const express = require('express');
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
const endpointSecret = process.env.STRIPE_WEBHOOK_SECRET;
app.post('/api/stripe/webhooks', express.raw({type: 'application/json'}), async (req, res) => {
const sig = req.headers['stripe-signature'];
let event;
try {
event = stripe.webhooks.constructEvent(req.body, sig, endpointSecret);
} catch (err) {
return res.status(400).send(`Webhook Error: ${err.message}`);
}
if (event.type === 'customer.subscription.deleted') {
const subscription = event.data.object;
console.log(`Subscription ${subscription.id} was cancelled.`);
// Store this event for churn calculation
}
res.json({received: true});
});Verify webhook authenticity
Always verify the webhook signature using stripe.webhooks.constructEvent(). This prevents fake requests from triggering false alerts. Stripe provides the signing secret in your webhook settings.
Calculate Your Churn Rate
Churn rate = (cancellations this period ÷ active subscriptions at period start) × 100. Query Stripe's API to get these numbers.
Query active subscriptions at period start
Use the Stripe API to fetch your subscription snapshot at the beginning of your measurement window. Filter by status active to get the baseline count.
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
async function getActiveSubscriptionsAtDate(startDate) {
const subscriptions = [];
let hasMore = true;
let startingAfter = null;
while (hasMore) {
const params = {
limit: 100,
status: 'active',
created: { gte: Math.floor(startDate.getTime() / 1000) }
};
if (startingAfter) params.starting_after = startingAfter;
const result = await stripe.subscriptions.list(params);
subscriptions.push(...result.data);
hasMore = result.has_more;
if (hasMore) startingAfter = result.data[result.data.length - 1].id;
}
return subscriptions.length;
}Count cancellations in your time window
Query subscriptions with status: 'canceled' within your period. Divide this count by the active baseline to get churn rate. This gives you a percentage you can alert on.
async function calculateMonthlyChurnRate(year, month) {
const startDate = new Date(year, month, 1);
const endDate = new Date(year, month + 1, 1);
// Get active subs at period start
const activeAtStart = await stripe.subscriptions.list({
limit: 1,
status: 'active',
created: { gte: Math.floor(startDate.getTime() / 1000) }
}).then(result => result.total_count);
// Get cancelled subs in period
const churned = await stripe.subscriptions.list({
limit: 1,
status: 'canceled',
created: {
gte: Math.floor(startDate.getTime() / 1000),
lte: Math.floor(endDate.getTime() / 1000)
}
}).then(result => result.total_count);
const churnRate = (churned / activeAtStart) * 100;
return churnRate.toFixed(2);
}Set Up Alerts and Notifications
Once you have the churn rate, trigger alerts when it hits your threshold. Use webhooks, email, or Slack to notify the right team.
Set a churn threshold and run a scheduled check
Define what counts as high churn for your business (e.g., >5% monthly). Run a scheduled job daily or weekly that calculates current churn and compares it to your threshold.
const cron = require('node-cron');
const axios = require('axios');
const CHURN_THRESHOLD = 5.0; // Alert if > 5%
// Run daily at 9 AM
cron.schedule('0 9 * * *', async () => {
const today = new Date();
const year = today.getFullYear();
const month = today.getMonth();
const churnRate = await calculateMonthlyChurnRate(year, month);
if (churnRate > CHURN_THRESHOLD) {
// Send to Slack
await axios.post(process.env.SLACK_WEBHOOK, {
text: `⚠️ High Churn Alert: ${churnRate}% (threshold: ${CHURN_THRESHOLD}%)`
});
// Log to monitoring
console.log(`Churn alert triggered: ${churnRate}%`);
}
});Route alerts to the right team
Integrate with Slack, email, or your monitoring tool. Keep alerts actionable—include the rate, the threshold, and a link to your Stripe dashboard for context.
Common Pitfalls
- Not verifying webhook signatures—a common source of false alerts and a security vulnerability.
- Counting all cancellations equally—payment failures, voluntary cancellations, and refund-related ones have different meanings. Filter for what you actually care about.
- Using overlapping time windows—ensure your baseline (active at start) and churn window don't overlap, or you'll double-count.
- Forgetting Stripe's pagination limit—the API returns max 100 items per request, so you'll miss subscriptions if you don't loop through all pages.
Wrapping Up
Stripe gives you the pieces—webhooks, API queries, and events—but doesn't calculate churn for you. Build a webhook listener, query your subscription data, and alert when you hit your threshold. If you want to track this automatically across tools, Product Analyst can help.