Stripe gives you charge and customer data, but doesn't automatically flag when your average revenue per user dips. By combining Stripe's API with a scheduled job, you can calculate ARPU on demand and alert your team when it falls below your target.
Fetch Revenue and Customer Data from Stripe API
Start by pulling the raw numbers—total revenue and active customer count—directly from Stripe's API.
Install the Stripe Node SDK
Use the official stripe npm package to interact with Stripe's API. This gives you clean access to customers, charges, and invoices.
npm install stripeFetch active customers and calculate ARPU
Call stripe.customers.list() to get your customer base, then stripe.charges.list() to sum revenue. Divide total revenue by customer count to get ARPU.
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
const thirtyDaysAgo = Math.floor(Date.now() / 1000) - 30 * 24 * 60 * 60;
const customers = await stripe.customers.list({
limit: 100,
created: { gte: thirtyDaysAgo }
});
const charges = await stripe.charges.list({
limit: 100,
created: { gte: thirtyDaysAgo },
status: 'succeeded'
});
const totalRevenue = charges.data.reduce((sum, c) => sum + c.amount, 0) / 100;
const arpu = totalRevenue / (customers.data.length || 1);
console.log(`ARPU: $${arpu.toFixed(2)}`);Handle pagination for large datasets
If you have thousands of charges, pagination limits you to 100 items per request. Use a loop to fetch all pages. For high-volume businesses, set a longer date range or use filtering.
async function getAllCharges(thirtyDaysAgo) {
let allCharges = [];
let params = {
created: { gte: thirtyDaysAgo },
status: 'succeeded',
limit: 100
};
for await (const charge of stripe.charges.list(params)) {
allCharges.push(charge);
}
return allCharges;
}
const allCharges = await getAllCharges(thirtyDaysAgo);
const totalRevenue = allCharges.reduce((sum, c) => sum + c.amount, 0) / 100;Set Up Scheduled Checks and Alerts
Alerts only work if you check regularly. Use a cron job to calculate ARPU and compare it to your threshold, then notify your team.
Create a scheduled function with node-cron
Set up a Node.js function that runs daily using node-cron. This function calculates current ARPU and compares it to your target threshold.
const cron = require('node-cron');
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
async function checkArpuAlert() {
const thirtyDaysAgo = Math.floor(Date.now() / 1000) - 30 * 24 * 60 * 60;
const [customers, charges] = await Promise.all([
stripe.customers.list({ created: { gte: thirtyDaysAgo } }),
stripe.charges.list({ created: { gte: thirtyDaysAgo }, status: 'succeeded' })
]);
const totalRevenue = charges.data.reduce((sum, c) => sum + c.amount, 0) / 100;
const arpu = totalRevenue / (customers.data.length || 1);
const threshold = 50; // $50 per user
if (arpu < threshold) {
console.log(`Alert: ARPU is $${arpu.toFixed(2)}, below threshold of $${threshold}`);
return arpu;
}
}
// Run daily at 9 AM UTC
cron.schedule('0 9 * * *', checkArpuAlert);Send alerts to Slack or email
When ARPU breaches your threshold, post to a Slack channel or send an email to your team. Use a webhook URL or mail service for instant notifications.
async function sendSlackAlert(message) {
const slackWebhookUrl = process.env.SLACK_WEBHOOK_URL;
await fetch(slackWebhookUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
text: `⚠️ Revenue Alert: ${message}`,
channel: '#revenue-alerts'
})
});
}
// In checkArpuAlert, after detecting low ARPU:
if (arpu < threshold) {
await sendSlackAlert(`ARPU dropped to $${arpu.toFixed(2)}, below target of $${threshold}`);
}Track ARPU in Real Time with Webhooks
For near-instantaneous visibility, listen to Stripe webhook events instead of polling the API. This reduces latency and API calls.
Set up a webhook endpoint
Create an Express server that listens for Stripe charge.succeeded and charge.refunded events. Stripe will POST events to your endpoint whenever a charge completes or is refunded.
const express = require('express');
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
const app = express();
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const sig = req.headers['stripe-signature'];
try {
const event = stripe.webhooks.constructEvent(
req.body,
sig,
process.env.STRIPE_WEBHOOK_SECRET
);
if (event.type === 'charge.succeeded') {
console.log(`Charge: $${event.data.object.amount / 100}`);
updateArpuCache(event.data.object);
}
if (event.type === 'charge.refunded') {
console.log(`Refund: $${event.data.object.amount_refunded / 100}`);
updateArpuCache(event.data.object);
}
res.json({ received: true });
} catch (err) {
res.status(400).send(`Webhook error: ${err.message}`);
}
});
app.listen(3000);Register your endpoint in Stripe Dashboard
Go to Developers > Webhooks in the Stripe Dashboard. Click Add an endpoint and enter your webhook URL. Select charge.succeeded and charge.refunded events.
// Webhook endpoint URL format:
// https://yourapp.com/webhook
// Events to subscribe to in Stripe Dashboard:
// - charge.succeeded
// - charge.refunded
// - customer.created (optional, to track new cohorts)
// - customer.deleted (optional, to update active customer count)Common Pitfalls
- Forgetting to account for refunds—filter for
status: 'succeeded'in charges and subtract refunds, or your ARPU will be inflated. - Treating all customers equally—if you have trial users with $0 revenue, they skew your ratio downward. Consider filtering for customers with at least one successful charge.
- Missing paginated results—
stripe.charges.list()caps at 100 items per page. Use a loop to fetch all charges or you'll undercount revenue. - Alerting on noise—daily ARPU fluctuates, especially for small businesses. Alert on 20%+ changes or weekly averages instead of daily swings.
Wrapping Up
You now have a working ARPU alert system tied directly to Stripe's data. By running scheduled checks against the Stripe API, you can catch revenue drops before they become problems. Pair this with webhooks for real-time tracking, and you'll have full visibility into your unit economics. If you want to track this automatically across tools—combining Stripe with analytics platforms—Product Analyst can help.