6 min read

How to Monitor ARR in Stripe

ARR tells you how much revenue you're generating annually from subscriptions, but Stripe doesn't calculate it for you automatically. You need to extract subscription data and aggregate it yourself. Here's how to build reliable ARR monitoring in Stripe without external tools.

Calculate ARR from Active Subscriptions

The foundation of ARR monitoring is querying your active subscriptions and summing their annual value. Stripe's API lets you paginate through subscriptions and filter by status.

List all active subscriptions and sum annual value

Use the subscriptions.list() method with status: 'active' filter. For each subscription, iterate through its line items and multiply the price by the annual frequency. Handle both monthly and annual billing intervals by normalizing to months first.

javascript
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);

async function calculateARR() {
  let arr = 0;
  let hasMore = true;
  let startingAfter = undefined;

  while (hasMore) {
    const subscriptions = await stripe.subscriptions.list({
      status: 'active',
      limit: 100,
      starting_after: startingAfter,
    });

    subscriptions.data.forEach((sub) => {
      sub.items.data.forEach((item) => {
        if (item.price.recurring) {
          const monthlyAmount = item.price.recurring.interval === 'month' 
            ? item.price.unit_amount 
            : item.price.unit_amount / 12;
          arr += monthlyAmount * 12 / 100; // Convert from cents to dollars
        }
      });
    });

    hasMore = subscriptions.has_more;
    startingAfter = subscriptions.data[subscriptions.data.length - 1]?.id;
  }

  return arr;
}

const currentARR = await calculateARR();
console.log(`Current ARR: $${currentARR.toFixed(2)}`);
Paginate through all active subscriptions and sum their annual value

Track MRR as a foundation for ARR

Monthly Recurring Revenue (MRR) is easier to validate and multiply by 12 to approximate ARR. Query active subscriptions, sum their monthly amounts, and store this daily. The difference month-over-month tells you growth or churn instantly.

javascript
async function calculateMRR() {
  let mrr = 0;
  const subscriptions = await stripe.subscriptions.list({
    status: 'active',
    limit: 100,
  });

  subscriptions.data.forEach((sub) => {
    sub.items.data.forEach((item) => {
      if (item.price.recurring) {
        const monthlyAmount = item.price.recurring.interval === 'month'
          ? item.price.unit_amount
          : item.price.unit_amount / 12;
        mrr += monthlyAmount / 100;
      }
    });
  });

  return mrr;
}

const currentMRR = await calculateMRR();
console.log(`Current MRR: $${currentMRR.toFixed(2)}`);
console.log(`Estimated ARR: $${(currentMRR * 12).toFixed(2)}`);
Calculate MRR and multiply by 12 for estimated ARR
Watch out: Always normalize billing intervals (monthly vs. annual) to a common period before summing. Also exclude paused and past_due subscriptions unless you're tracking at-risk revenue separately.

Monitor ARR Changes with Webhooks

Real-time ARR updates beat nightly batch jobs. Stripe sends webhooks whenever subscriptions change—new signups, plan upgrades, and cancellations. Listen for these events and update your ARR tracking instantly.

Set up webhook handlers for subscription lifecycle events

Configure a webhook endpoint to receive customer.subscription.created, customer.subscription.updated, and customer.subscription.deleted events. Use webhooks.constructEvent() to verify the signature and parse the event.

javascript
const express = require('express');
const app = express();

app.post('/webhook', express.raw({type: 'application/json'}), async (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) {
    return res.status(400).send(`Webhook Error: ${err.message}`);
  }

  if (event.type === 'customer.subscription.created') {
    const subscription = event.data.object;
    let newARRValue = 0;
    subscription.items.data.forEach((item) => {
      if (item.price.recurring) {
        const monthlyAmount = item.price.recurring.interval === 'month'
          ? item.price.unit_amount
          : item.price.unit_amount / 12;
        newARRValue += monthlyAmount * 12 / 100;
      }
    });
    console.log(`Subscription created: +$${newARRValue.toFixed(2)} ARR`);
    // Increment your ARR total here
  }

  if (event.type === 'customer.subscription.deleted') {
    const subscription = event.data.object;
    let canceledARRValue = 0;
    subscription.items.data.forEach((item) => {
      if (item.price.recurring) {
        const monthlyAmount = item.price.recurring.interval === 'month'
          ? item.price.unit_amount
          : item.price.unit_amount / 12;
        canceledARRValue += monthlyAmount * 12 / 100;
      }
    });
    console.log(`Subscription canceled: -$${canceledARRValue.toFixed(2)} ARR`);
    // Decrement your ARR total here
  }

  res.status(200).json({received: true});
});

app.listen(3000, () => console.log('Webhook endpoint listening'));
Handle subscription events and update ARR in real time

Capture plan changes and upgrades via subscription.updated

When a customer upgrades or downgrades their plan, Stripe fires a customer.subscription.updated event. Check the previous_attributes field to see what changed and calculate the ARR delta.

javascript
if (event.type === 'customer.subscription.updated') {
  const subscription = event.data.object;
  const previous = event.data.previous_attributes;

  // Check if items (plan/price) changed
  if (previous.items) {
    const oldARR = calculateSubscriptionARR(previous.items.data);
    const newARR = calculateSubscriptionARR(subscription.items.data);
    const delta = newARR - oldARR;
    
    console.log(`Subscription updated: ${delta > 0 ? '+' : ''}$${delta.toFixed(2)} ARR`);
    // Update your ARR total by the delta
  }
}

function calculateSubscriptionARR(items) {
  let arr = 0;
  items.forEach((item) => {
    if (item.price.recurring) {
      const monthlyAmount = item.price.recurring.interval === 'month'
        ? item.price.unit_amount
        : item.price.unit_amount / 12;
      arr += monthlyAmount * 12 / 100;
    }
  });
  return arr;
}
Detect plan upgrades and downgrades by comparing previous_attributes
Tip: Register webhooks in the Stripe Dashboard > Developers > Webhooks section. Use stripe listen --forward-to localhost:3000/webhook with the Stripe CLI to test locally.

Store and Trend ARR Over Time

Point-in-time ARR is useful, but trends matter more. Store daily snapshots of your ARR so you can track month-over-month growth and detect churn early.

Schedule daily ARR snapshots in your database

Run a scheduled job (cron job, Lambda, or similar) once per day to calculate total ARR and insert it into your database with a timestamp. This creates a historical record you can query for trend analysis.

javascript
const cron = require('node-cron');
const db = require('./db'); // Your database client

// Run daily at 2 AM UTC
cron.schedule('0 2 * * *', async () => {
  try {
    const arr = await calculateARR();
    const today = new Date().toISOString().split('T')[0];
    
    await db.query(
      'INSERT INTO arr_snapshots (date, arr, created_at) VALUES ($1, $2, $3)',
      [today, arr, new Date()]
    );
    
    console.log(`ARR snapshot stored: $${arr.toFixed(2)} on ${today}`);
  } catch (err) {
    console.error('Failed to store ARR snapshot:', err);
  }
});
Schedule daily ARR calculations and persist historical data

Calculate growth metrics from historical snapshots

Query your snapshots table to compute month-over-month growth rate, churn rate, and net ARR expansion. These metrics drive business decisions and help you spot trends early.

javascript
async function calculateGrowthMetrics() {
  const today = new Date().toISOString().split('T')[0];
  const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000)
    .toISOString().split('T')[0];
  
  const currentSnapshot = await db.query(
    'SELECT arr FROM arr_snapshots WHERE date = $1',
    [today]
  );
  
  const previousSnapshot = await db.query(
    'SELECT arr FROM arr_snapshots WHERE date = $1',
    [thirtyDaysAgo]
  );
  
  if (currentSnapshot.rows.length && previousSnapshot.rows.length) {
    const current = currentSnapshot.rows[0].arr;
    const previous = previousSnapshot.rows[0].arr;
    const momGrowth = ((current - previous) / previous) * 100;
    
    console.log(`Current ARR: $${current.toFixed(2)}`);
    console.log(`ARR 30 days ago: $${previous.toFixed(2)}`);
    console.log(`MoM Growth: ${momGrowth.toFixed(2)}%`);
  }
}

await calculateGrowthMetrics();
Calculate month-over-month growth from historical snapshots
Tip: Stripe's Reporting API (beta) can export subscription data in bulk, but you'll still calculate ARR yourself—Stripe doesn't provide a built-in ARR metric. Use your snapshots table as the source of truth for business reviews.

Common Pitfalls

  • Forgetting to normalize billing intervals—annual subscriptions are worth 1/12th per month, always convert to a common period before summing
  • Including paused and past_due subscriptions in ARR—track these separately as at-risk revenue, not core ARR
  • Working with amounts as integers instead of dividing by 100—Stripe stores amounts in cents, always convert to dollars before reporting
  • Running ARR calculations synchronously during request handling—offload to a background job to avoid API timeouts

Wrapping Up

ARR monitoring in Stripe requires three steps: query active subscriptions and sum their annual value, listen to webhooks for real-time updates, and store daily snapshots for trend analysis. This gives you reliable revenue visibility without external tools. 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