You're tracking revenue, but you're missing the real story—how much existing customers are actually expanding versus churning. Net Revenue Retention tells you if your existing customer base is getting healthier or smaller. In Stripe, this means tying subscription events to cohorts and doing math on the data you already have.
Capture subscription events with webhooks
To calculate NRR, you need to know when customers upgrade, downgrade, or cancel. Stripe webhooks deliver these events in real time.
Set up a webhook endpoint for subscription events
Create an endpoint that listens to Stripe's customer.subscription.updated and customer.subscription.deleted events. In your Node.js server, use the Stripe SDK to verify the webhook signature and process the event.
const stripe = require('stripe')('sk_live_...');
const express = require('express');
const app = express();
app.post('/webhook', express.raw({type: 'application/json'}), (req, res) => {
const sig = req.headers['stripe-signature'];
const event = stripe.webhooks.constructEvent(
req.body,
sig,
'whsec_test_secret'
);
if (event.type === 'customer.subscription.updated') {
const subscription = event.data.object;
console.log(`Subscription ${subscription.id} updated for customer ${subscription.customer}`);
// Store this event in your database
}
if (event.type === 'customer.subscription.deleted') {
const subscription = event.data.object;
console.log(`Customer ${subscription.customer} churned`);
}
res.json({received: true});
});Store metadata on subscriptions to identify cohorts
When creating subscriptions, add metadata to tag the customer's cohort (sign-up month, plan type, company segment). This lets you later group customers by when they started.
const subscription = await stripe.subscriptions.create({
customer: 'cus_123',
items: [{price: 'price_monthly_pro'}],
metadata: {
cohort_month: '2024-01',
plan_type: 'pro',
company_segment: 'mid-market'
}
});Query Stripe to calculate NRR by cohort
Once you have subscription events logged, query Stripe's API or your database to calculate NRR for each cohort.
Pull subscription history for a cohort using the API
Use the Stripe API to list all subscriptions with the cohort metadata, then filter by start date. The starting_after parameter helps you paginate through large result sets.
const subscriptions = await stripe.subscriptions.list({
limit: 100,
created: {
gte: Math.floor(new Date('2024-01-01').getTime() / 1000),
lt: Math.floor(new Date('2024-02-01').getTime() / 1000)
}
});
const cohortRevenue = subscriptions.data.reduce((sum, sub) => {
if (sub.status === 'active' || sub.status === 'past_due') {
return sum + (sub.items.data[0].price.unit_amount / 100);
}
return sum;
}, 0);Calculate NRR by comparing MRR across time periods
NRR formula: (Ending MRR - Starting MRR + Churn) / Starting MRR × 100. Get the MRR at the start of a period, then again at the end. Separately track downgrades and cancellations as churn.
// Example: Calculate NRR for Jan 2024 cohort, measured in March
const startingMRR = 50000; // Jan 1
const endingMRR = 55000; // Mar 31
const churnValue = 5000; // Lost to downgrades + cancellations
const nrr = ((endingMRR - startingMRR + churnValue) / startingMRR) * 100;
console.log(`NRR: ${nrr.toFixed(1)}%`); // Output: NRR: 120.0%Monitor NRR over time
Calculate NRR monthly and track it in a dashboard so you see trends.
Automate NRR calculation in a scheduled job
Run a cron job each month-end that queries Stripe subscriptions and writes NRR metrics to your database. Use the Stripe API with date filters to get only active subscriptions at the start and end of each period.
const schedule = require('node-schedule');
const stripe = require('stripe')('sk_live_...');
schedule.scheduleJob('0 2 1 * *', async () => {
const lastMonthStart = new Date();
lastMonthStart.setMonth(lastMonthStart.getMonth() - 1);
lastMonthStart.setDate(1);
lastMonthStart.setHours(0, 0, 0, 0);
const subscriptions = await stripe.subscriptions.list({
limit: 100,
expand: ['data.items.data.price'],
created: {gte: Math.floor(lastMonthStart.getTime() / 1000)}
});
// Calculate NRR and save to your database
console.log(`Processed ${subscriptions.data.length} subscriptions`);
});Common Pitfalls
- Mixing new customer MRR with existing customer MRR. NRR only measures retention and expansion of the base you started with—new sales are separate.
- Forgetting to account for plan changes in the same subscription. A customer who upgrades doesn't churn—Stripe keeps the subscription ID the same, just updates the amount.
- Not handling test mode subscriptions. Stripe separates test and live data. Make sure your queries filter to
livemode: trueif you're pulling production metrics. - Assuming Stripe's Reports feature calculates NRR for you. It doesn't. You have to export the data and do the math yourself, or build it in your dashboard.
Wrapping Up
You now have a way to measure if your existing customers are actually getting more valuable or leaving. NRR above 100% means you're winning at expansion, while below 100% means churn is outpacing growth. If you want to track this automatically across tools, Product Analyst can help.