6 min read

How to Set Up Alerts for ARR in Stripe

Stripe doesn't give you ARR alerts out of the box. You can see monthly recurring revenue in the dashboard, but spotting when ARR shifts—from churn, upgrades, or new customers—requires querying the API yourself. We'll build a simple alert system using webhooks and subscription queries to catch every revenue movement in real-time.

Calculate Your Current ARR

ARR in Stripe is straightforward: sum all recurring charges from active subscriptions and multiply monthly amounts by 12.

Query all active subscriptions

Use the List all subscriptions API endpoint with status: 'active' to exclude canceled subscriptions. This gives you every active recurring charge.

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

const subscriptions = await stripe.subscriptions.list({
  status: 'active',
  limit: 100  // Paginate if you have more
});
Fetch all active subscriptions from Stripe

Calculate ARR from subscription items

Loop through each subscription's line items. Multiply monthly recurring amounts by 12. Leave annual subscriptions as-is (already represents 1 year). Account for quantity and avoid double-counting discounts in the recurring price.

javascript
let totalARR = 0;

subscriptions.data.forEach(sub => {
  sub.items.data.forEach(item => {
    const price = item.price;
    // Monthly subscriptions: multiply by 12
    if (price.recurring?.interval === 'month') {
      const monthlyAmount = (price.unit_amount * item.quantity) / 100;
      totalARR += monthlyAmount * 12;
    }
    // Annual subscriptions: add as-is
    if (price.recurring?.interval === 'year') {
      const yearlyAmount = (price.unit_amount * item.quantity) / 100;
      totalARR += yearlyAmount;
    }
  });
});

console.log(`Current ARR: $${totalARR.toFixed(2)}`);
Watch out: Stripe returns amounts in cents. Divide unit_amount by 100 to get dollars.

Monitor Subscription Changes with Webhooks

Instead of polling Stripe hourly, listen for subscription events in real-time. When a customer upgrades, downgrades, or churns, you get an instant webhook notification.

Create a webhook endpoint

Set up an HTTPS endpoint (e.g., /webhooks/stripe) that accepts POST requests. Keep the raw request body before parsing—you need it to verify Stripe's signature.

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

app.post('/webhooks/stripe', express.raw({type: 'application/json'}), (req, res) => {
  const sig = req.headers['stripe-signature'];
  const endpointSecret = process.env.STRIPE_WEBHOOK_SECRET;
  
  let event;
  try {
    event = stripe.webhooks.constructEvent(req.body, sig, endpointSecret);
  } catch (err) {
    console.error('Webhook signature verification failed');
    return res.sendStatus(400);
  }
  
  // Process event here
  res.json({received: true});
});

Listen for subscription updates and deletions

Handle customer.subscription.updated when a customer changes their plan, and customer.subscription.deleted when they churn. Compare the old and new subscription data to calculate the ARR delta.

javascript
function calculateSubscriptionARR(subscription) {
  let arr = 0;
  subscription.items.data.forEach(item => {
    const price = item.price;
    if (price.recurring?.interval === 'month') {
      const monthly = (price.unit_amount * item.quantity) / 100;
      arr += monthly * 12;
    }
    if (price.recurring?.interval === 'year') {
      arr += (price.unit_amount * item.quantity) / 100;
    }
  });
  return arr;
}

if (event.type === 'customer.subscription.updated') {
  const subscription = event.data.object;
  const oldARR = calculateSubscriptionARR(event.data.previous_attributes);
  const newARR = calculateSubscriptionARR(subscription);
  const change = newARR - oldARR;
  
  if (change !== 0) {
    console.log(`Subscription ${subscription.id}: ARR changed by $${change.toFixed(2)}`);
    await handleARRChange(subscription, change);
  }
}

if (event.type === 'customer.subscription.deleted') {
  const subscription = event.data.object;
  const lostARR = calculateSubscriptionARR(subscription);
  console.log(`Customer churned: Lost $${lostARR.toFixed(2)} ARR`);
}

Register the webhook in Stripe Dashboard

Go to Developers > Webhooks in your Stripe account. Click Add endpoint. Paste your webhook URL (e.g., https://yourdomain.com/webhooks/stripe). Select events: customer.subscription.created, customer.subscription.updated, customer.subscription.deleted. Stripe generates a signing secret—save it as STRIPE_WEBHOOK_SECRET in your environment.

javascript
// Your webhook signing secret (from Stripe Dashboard > Webhooks)
// Store in environment variables, never hardcoded
const endpointSecret = process.env.STRIPE_WEBHOOK_SECRET;
// Format: whsec_xxxxx...
Tip: Always verify the signature with stripe.webhooks.constructEvent(). This proves the webhook came from Stripe, not an attacker.

Set Up Threshold-Based Alerts

Now you're tracking ARR changes. Send alerts when ARR moves significantly—big wins, churn warnings, or cumulative daily losses.

Define alert thresholds

Decide what warrants an alert: a single large customer upgrade, individual churn, or cumulative daily losses. Store your current ARR in a database or cache to compare against.

javascript
const ARR_THRESHOLDS = {
  singleUpgrade: 5000,      // Alert if one customer adds >$5k ARR
  singleChurn: 1000,        // Alert if one customer churns >$1k ARR
  dailyNetLoss: 2000        // Alert if total ARR drops >$2k in a day
};

async function checkThresholds(change, eventType, subscription) {
  if (eventType === 'customer.subscription.updated' && change > ARR_THRESHOLDS.singleUpgrade) {
    const customer = await stripe.customers.retrieve(subscription.customer);
    await notifySlack(`🚀 Large upgrade from ${customer.name}: +$${change.toFixed(2)} ARR`);
  }
  
  if (eventType === 'customer.subscription.deleted' && Math.abs(change) > ARR_THRESHOLDS.singleChurn) {
    const customer = await stripe.customers.retrieve(subscription.customer);
    await notifySlack(`⚠️ Major churn from ${customer.name}: -$${Math.abs(change).toFixed(2)} ARR`);
  }
}

Send real-time alerts to Slack

When a threshold is crossed, post to Slack via webhook. Include the customer name, ARR amount, and event type so your team can take immediate action.

javascript
async function notifySlack(message) {
  const slackWebhook = process.env.SLACK_WEBHOOK_URL;
  
  await fetch(slackWebhook, {
    method: 'POST',
    headers: {'Content-Type': 'application/json'},
    body: JSON.stringify({text: message})
  });
}

// In your webhook handler
if (event.type === 'customer.subscription.created') {
  const sub = event.data.object;
  const customer = await stripe.customers.retrieve(sub.customer);
  const arr = calculateSubscriptionARR(sub);
  
  if (arr > ARR_THRESHOLDS.singleUpgrade) {
    await notifySlack(`✅ New customer ${customer.name}: +$${arr.toFixed(2)} ARR`);
  }
}
Watch out: Stripe retries webhooks for up to 3 days if you don't return 200 OK. Process idempotently by storing event IDs—check if you've already handled this event before alerting again.

Common Pitfalls

  • Forgetting to multiply monthly recurring by 12, but leaving annual recurring as-is—the math differs by interval
  • Ignoring item.quantity—a customer with quantity: 5 should count 5× the price in ARR
  • Counting paused or canceled subscriptions—only include status: 'active' in your calculations
  • Skipping webhook signature verification—always validate with stripe.webhooks.constructEvent() or you'll process fake alerts

Wrapping Up

You now have real-time ARR monitoring tied directly to Stripe. Catch churn the moment it happens, celebrate upgrades instantly, and never miss revenue changes. If you want to track this automatically across tools and get deeper insights into your revenue streams, Product Analyst can help.

Track these metrics automatically

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

Try Product Analyst — Free