7 min read

How to Set Up Alerts for ARPU in Stripe

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.

javascript
npm install stripe
Install the Stripe SDK

Fetch 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.

javascript
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)}`);
Calculate ARPU from customer and charge data

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.

javascript
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;
Fetch all charges across multiple pages
Watch out: Pagination can be slow for months of data. For annual ARPU checks, cache results and only fetch new charges since your last calculation.

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.

javascript
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);
Daily ARPU check with alert threshold

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.

javascript
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}`);
}
Post alerts to Slack webhook
Tip: Store your previous ARPU and only alert on significant changes (e.g., >15% drop week-over-week) to reduce noise.

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.

javascript
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);
Listen to charge webhooks and update ARPU cache

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.

javascript
// 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)
Webhook configuration reference
Watch out: Webhooks can retry up to 3 times if your endpoint is slow. Return a 2xx status code quickly and process events asynchronously.

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.

Track these metrics automatically

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

Try Product Analyst — Free