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.
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
const subscriptions = await stripe.subscriptions.list({
status: 'active',
limit: 100 // Paginate if you have more
});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.
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)}`);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.
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.
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.
// Your webhook signing secret (from Stripe Dashboard > Webhooks)
// Store in environment variables, never hardcoded
const endpointSecret = process.env.STRIPE_WEBHOOK_SECRET;
// Format: whsec_xxxxx...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.
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.
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`);
}
}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.