Net Revenue Retention is the metric that actually tells you if your business is growing or shrinking at the unit level. Stripe gives you the raw subscription data, but you have to do the math yourself—there's no pre-built NRR report. This guide walks you through extracting subscription changes, calculating NRR, and visualizing it so you can see month-over-month retention trends.
Pull subscription data with the Stripe API
You'll need to fetch all active subscriptions and their invoice history to calculate baseline revenue and detect expansions or churn.
Fetch active subscriptions for your cohort
Use stripe.subscriptions.list() to get all subscriptions for a given month. Filter by created date to define your cohort. This gives you the starting MRR baseline.
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
const startDate = Math.floor(new Date('2025-03-01').getTime() / 1000);
const endDate = Math.floor(new Date('2025-03-31').getTime() / 1000);
const subscriptions = await stripe.subscriptions.list({
created: {
gte: startDate,
lte: endDate
},
status: 'all',
limit: 100
});
let startingMRR = 0;
subscriptions.data.forEach(sub => {
if (sub.items.data[0]) {
startingMRR += sub.items.data[0].price.unit_amount / 100;
}
});Track subscription updates and upgrades
Query customer.subscription.updated events to find price changes. Filter by previous_attributes to identify which subscriptions had plan changes. This reveals expansion vs. contraction.
const events = await stripe.events.list({
type: 'customer.subscription.updated',
created: {
gte: startDate,
lte: endDate
},
limit: 100
});
let expansion = 0;
let contraction = 0;
events.data.forEach(event => {
const prevAttrs = event.data.previous_attributes || {};
const currSub = event.data.object;
if (prevAttrs.items && currSub.items.data[0]) {
const prevAmount = prevAttrs.items.data[0]?.price?.unit_amount || 0;
const currAmount = currSub.items.data[0].price.unit_amount || 0;
const delta = (currAmount - prevAmount) / 100;
if (delta > 0) expansion += delta;
if (delta < 0) contraction += Math.abs(delta);
}
});Calculate churned revenue from cancellations
Pull customer.subscription.deleted events for the same period. For each cancellation, add the subscription's monthly amount to your churn total.
const cancelEvents = await stripe.events.list({
type: 'customer.subscription.deleted',
created: {
gte: startDate,
lte: endDate
},
limit: 100
});
let churn = 0;
cancelEvents.data.forEach(event => {
const sub = event.data.object;
if (sub.items.data[0]) {
churn += sub.items.data[0].price.unit_amount / 100;
}
});
const nrr = ((startingMRR + expansion - churn) / startingMRR) * 100;
console.log(`NRR: ${nrr.toFixed(2)}%`);event.id and timestamps to avoid double-counting the same change.Store and visualize monthly trends
Once you have monthly NRR calculations, persist them so you can track retention over time.
Build a monthly cohort table
Create a rollup with starting MRR, expansion, churn, and NRR for each month. Store this in a database, JSON file, or CSV. This becomes your source of truth for dashboards.
const monthlyMetrics = [
{
cohort_month: '2025-03',
starting_mrr: 50000,
expansion: 8000,
churn: 3000,
nrr: 120,
timestamp: new Date().toISOString()
}
];
// Append to persistent storage
const fs = require('fs');
const existing = JSON.parse(fs.readFileSync('nrr_cohorts.json', 'utf8') || '[]');
existing.push(...monthlyMetrics);
fs.writeFileSync('nrr_cohorts.json', JSON.stringify(existing, null, 2));Visualize in your BI tool
Export the cohort table to Metabase, Looker, Tableau, or Google Sheets. Plot NRR % over time with months on the X-axis. Color-code above/below 100% to spot retention problems instantly. Add a secondary axis for MRR to show absolute growth alongside retention health.
Common Pitfalls
- Mixing annual and monthly subscriptions without normalizing. A $1200/year plan is $100/month, not $1200. Normalize everything to a single period before calculating NRR.
- Including trial subscriptions in starting MRR. They're not revenue yet. Filter by
status: 'active'and excludetrial_enddates in the future. - Ignoring
charge.refundedevents. A refund reduces MRR, so subtract it from your baseline or you'll report NRR that's artificially high. - Using the wrong date range for cohorts. If you measure starting MRR in March but include April expansion, your NRR will be off by a full month. Always match the event period to your cohort window.
Wrapping Up
NRR in Stripe requires combining subscription data with event history—there's no single number Stripe hands you. Extract starting MRR, measure expansions and churn through events, and calculate month-over-month. Once you have the math working, visualize it in a dashboard so you can spot retention trends before they become problems. If you want to track this automatically across tools, Product Analyst can help.