6 min read

How to Set Up Alerts for Expansion Revenue in Stripe

Expansion revenue — when existing customers pay more — is your highest-margin growth lever. But Stripe doesn't surface this automatically. You need to build alerts that catch subscription increases, new product adds, and larger invoices so you know when a customer is growing with you.

Capture Expansion Events with Webhooks

Stripe webhooks let you listen for the moments expansion happens in real time.

Enable webhook endpoints in Stripe Dashboard

Go to Developers > Webhooks and create an endpoint. Point it to your server (e.g., https://yourapi.com/webhooks/stripe). Subscribe to these events: customer.subscription.updated, invoice.payment_succeeded, and invoice.finalized. Store the endpoint signing secret — you'll need it to verify requests.

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

app.post('/webhooks/stripe', express.raw({type: 'application/json'}), (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.updated') {
    handleSubscriptionUpdate(event.data.object, event.data.previous_attributes);
  }

  res.json({received: true});
});
Verify and process Stripe webhooks using the SDK

Identify expansion in subscription changes

When customer.subscription.updated fires, compare the new amount to the old. The event.data.previous_attributes object contains what changed — check subscription amount, item quantity, or added line items. Only alert if the total recurring revenue increased.

javascript
const isExpansion = (current, previous) => {
  // Get amounts from current subscription
  const newMRR = current.items.data.reduce((sum, item) => {
    return sum + (item.price.recurring.amount * (item.quantity || 1) / 100);
  }, 0);

  // Get previous amounts
  const oldMRR = previous.items?.data?.reduce((sum, item) => {
    return sum + (item.price.recurring.amount * (item.quantity || 1) / 100);
  }, 0) || 0;

  return newMRR > oldMRR ? newMRR - oldMRR : null;
};

const handleSubscriptionUpdate = (subscription, previousAttributes) => {
  const expansionAmount = isExpansion(subscription, previousAttributes);
  if (expansionAmount) {
    console.log(`Expansion detected: +$${expansionAmount.toFixed(2)} MRR`);
    // Trigger alert
  }
};
Calculate MRR change to detect expansion
Watch out: customer.subscription.updated fires for all changes — downgrades, trial resets, date shifts. Always compare amounts before alerting. A subscription update that decreases MRR is churn, not expansion.

Track Expansion Revenue with Historical Context

Knowing expansion happened is half the battle. You also need to know the magnitude and compare it to typical customer behavior.

Query subscription history and calculate expansion impact

Use the Stripe API to fetch the customer's current subscription details. Calculate what their MRR was before the change (from previous_attributes) and what it is now. Store this in your database so you can rank customers by expansion size and identify patterns.

javascript
const recordExpansion = async (subscriptionId, previousAttributes, event) => {
  const subscription = await stripe.subscriptions.retrieve(subscriptionId);
  
  const newMRR = subscription.items.data.reduce((sum, item) => {
    return sum + (item.price.recurring.amount * (item.quantity || 1));
  }, 0) / 100;

  const oldMRR = previousAttributes.items?.data?.reduce((sum, item) => {
    return sum + (item.price.recurring.amount * (item.quantity || 1));
  }, 0) / 100 || 0;

  const expansion = {
    customerId: subscription.customer,
    subscriptionId: subscriptionId,
    previousMRR: oldMRR,
    newMRR: newMRR,
    expansionAmount: newMRR - oldMRR,
    percentageIncrease: ((newMRR - oldMRR) / oldMRR * 100).toFixed(1),
    timestamp: new Date(event.created * 1000),
    changeType: identifyChangeType(previousAttributes, subscription)
  };

  // Store in your database
  await db.expansionEvents.insert(expansion);
  return expansion;
};

const identifyChangeType = (prev, current) => {
  if (prev.items?.data?.length < current.items.data.length) return 'added_product';
  if (current.items.data[0].quantity > prev.items?.data?.[0]?.quantity) return 'increased_seats';
  return 'plan_upgrade';
};
Record expansion details for analysis and alerts

Send alerts when expansion exceeds thresholds

Store all expansion events in your database, then trigger alerts (Slack, email, webhook) when the expansion hits meaningful thresholds. For example, notify your sales team when a customer expands by $500+ MRR, but only notify executives for $5,000+ expansions.

javascript
const sendExpansionAlert = async (expansion, customer) => {
  const thresholds = [
    { amount: 5000, channel: 'exec-channel', label: 'Enterprise expansion' },
    { amount: 500, channel: 'sales-channel', label: 'Customer expansion' }
  ];

  for (const threshold of thresholds) {
    if (expansion.expansionAmount >= threshold.amount) {
      const message = `${threshold.label}: ${customer.email} → +$${expansion.expansionAmount.toFixed(2)} MRR (${expansion.changeType})`;
      
      await fetch(process.env[`SLACK_WEBHOOK_${threshold.channel.toUpperCase()}`], {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          text: message,
          attachments: [{
            color: '#36a64f',
            fields: [
              { title: 'Previous MRR', value: `$${expansion.previousMRR.toFixed(2)}`, short: true },
              { title: 'New MRR', value: `$${expansion.newMRR.toFixed(2)}`, short: true },
              { title: 'Increase', value: `${expansion.percentageIncrease}%`, short: true }
            ]
          }]
        })
      });
      break; // Only alert at the highest relevant threshold
    }
  }
};
Send tiered Slack alerts based on expansion size
Tip: Track expansion in your own database, not just Stripe. Stripe webhooks give you the raw event, but analysis lives in your app. Query your expansion table to answer: Which customers expand repeatedly? What's the average time between expansion? Which change type drives the most revenue?

Monitor Invoice Amounts for Expansion Signals

Not all expansion flows through subscription changes. Catch it in invoice payments too — customers adding one-time charges, larger prorated amounts, or mid-cycle upgrades.

Compare invoice amounts to customer baseline

When invoice.payment_succeeded fires, check the paid amount against the customer's typical monthly invoice. This catches expansion from added line items, pro-rated upgrades, or one-time usage overage. A 20%+ increase from the customer's baseline is worth flagging.

javascript
const checkInvoiceExpansion = async (invoice, customerId) => {
  // Get customer's recent paid invoices (exclude failed, draft, or voided)
  const recentInvoices = await stripe.invoices.list({
    customer: customerId,
    status: 'paid',
    limit: 6
  });

  if (recentInvoices.data.length < 2) {
    return { isNewCustomer: true };
  }

  // Calculate baseline from previous invoices (exclude current)
  const baseline = recentInvoices.data
    .filter(inv => inv.id !== invoice.id)
    .reduce((sum, inv) => sum + inv.amount_paid, 0) / (recentInvoices.data.length - 1);

  const currentAmount = invoice.amount_paid;
  const percentageIncrease = ((currentAmount - baseline) / baseline) * 100;

  return {
    currentAmount: currentAmount / 100,
    baseline: baseline / 100,
    percentageIncrease: percentageIncrease.toFixed(1),
    isExpansion: percentageIncrease >= 20
  };
};

app.post('/webhooks/stripe', express.raw({type: 'application/json'}), async (req, res) => {
  // ... webhook setup ...
  
  if (event.type === 'invoice.payment_succeeded') {
    const invoice = event.data.object;
    const expansion = await checkInvoiceExpansion(invoice, invoice.customer);
    if (expansion.isExpansion) {
      await recordInvoiceExpansion(invoice, expansion);
    }
  }
});
Detect invoice-level expansion by comparing to customer baseline

Common Pitfalls

  • Confusing subscription updates with expansion — every change (plan switch, trial reset, date shift) triggers customer.subscription.updated. Only expansion actually increases MRR. Build a filter function and use it everywhere.
  • Using a single invoice amount without context — a large invoice might be a failed retry reattempt, refund reversal, or legitimate one-time charge. Compare to customer's rolling average, not just last month, and use a reasonable threshold (20%+).
  • Missing small expansion events — a customer adding a low-cost add-on while keeping their main plan is still expansion. Your tier-based alert system should catch it, even if you don't page sales.
  • Ignoring idempotency in webhook processing — if your endpoint fails mid-alert, Stripe retries for 3 days. Store an event_id in your database and skip re-processing. You'll alert twice on the same expansion otherwise.

Wrapping Up

Expansion revenue is often hiding in plain sight in Stripe. By listening to webhooks, calculating MRR increases against baseline, and setting thresholds, you get real-time visibility into which customers are growing with you — and which expansion signals you're missing. If you want to track this automatically across tools and your entire customer base, Product Analyst can help.

Track these metrics automatically

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

Try Product Analyst — Free