6 min read

How to Visualize Net Revenue Retention in Stripe

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.

javascript
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;
  }
});
Fetch March cohort subscriptions and sum monthly amounts

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.

javascript
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);
  }
});
Identify monthly expansion and contraction from subscription updates

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.

javascript
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)}%`);
Sum churned revenue and calculate final NRR
Watch out: Stripe events can duplicate or arrive out of order. Build idempotent logic using 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.

javascript
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));
Persist monthly NRR calculations for dashboard queries

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.

Tip: Automate this daily. Use a scheduled job (cron, GitHub Actions, AWS Lambda) to run the event queries and append fresh metrics every 24 hours. You'll catch churn spikes before they become problems.

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 exclude trial_end dates in the future.
  • Ignoring charge.refunded events. 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.

Track these metrics automatically

Product Analyst connects to your stack and surfaces the insights that matter.

Try Product Analyst — Free