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.
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});
});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.
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
}
};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.
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';
};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.
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
}
}
};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.
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);
}
}
});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_idin 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.