"What did the outage cost us?" gets a different answer from everyone in the room, because it is three questions wearing one coat. Separate them and each is easy to measure:
- MRR exposed: what the customers who were affected pay you. The size of what was at risk.
- Revenue blocked: money that tried to move during the incident and could not. Failed payments, checkouts that did not complete.
- Revenue lost: customers who left, or paid less, because of it. Known only later, and never for certain.
The first two you can have within an hour of the fix. The third takes a month and a judgment call.
1. Fix the window
Every number below is "between these two times", so decide them first. The start is the deploy that caused it, from your release log, GitHub deployments or the CI run, not the first alert (alerts lag). The end is the deploy that fixed it, or the rollback. Write both down with a timezone.
If you do not know which release started it, the error tracker does: look at the hourly error count and find the hour it left the baseline, then the last deploy before that hour.
2. Find who was affected
Three sources, in order of how much they can tell you:
- Errors. The users on the Sentry issues that first appeared in the window, joined to customers by company domain. How to do that join.
- Analytics. The users who reached the broken step and did not get past it: for a broken checkout, everyone who fired
checkout_startedin the window and nocheckout_completed. In PostHog, a funnel over the window with the two events; export the people at the drop-off. - Support. Everyone who wrote in during the window about the symptom. Tag the conversations as you go; you will want the list.
Union the three into one list of companies. Split it into paying and not; both matter, but they go in different numbers.
3. MRR exposed
Sum the MRR of the paying companies on the list. In Stripe, active subscriptions per customer, normalised to a month (annual ÷ 12). That is the exposure: the recurring revenue that saw the product broken.
Say it as a share too: exposure of €798 a month means little until you add "of €9,400", or "8% of MRR, including two of the top five accounts".
4. Revenue blocked
Two parts. Payments that failed in the window, where the failure was the incident and not a declined card: in Stripe, invoice.payment_failed events between the two times, and read the failure reason; a processing_error during a checkout outage is yours, a card_declined is theirs. Sum the invoice amounts, then check which have since been paid; blocked revenue that later cleared cost you days, not euros.
Checkouts that did not happen. This one needs a baseline, and the baseline needs care. Do not compare the incident hours with the hours just before: a checkout at 14:00 on a Tuesday is not a checkout at 03:00. Compare each hour in the window with the same hour of the same weekday the week before (or the average of the last four). The difference is the missing checkouts; times the average order value, it is the revenue that did not arrive.
window 14:00–16:59 Tue
last week 14:00–16:59 Tue: 52 checkouts
this week 15 checkouts
missing 37 × €49 average = €1,813
Some of those 37 will come back tomorrow. Some will not. You will not know which, so report the number with the word "missing", not "lost".
5. Revenue lost
Watch the affected list for 30 days. Cancellations, downgrades and failed renewals among them, against the same rate in the rest of the base over the same month, is the attributable loss. It is an estimate, and honest reporting says so: "three of the eleven affected accounts cancelled within 30 days, against a base rate of one in fifty".
For a B2B product, add the deals: any open opportunity with an affected company that slipped or was lost in the month after.
A worked example
From Vesqo's demo workspace, a job board called Farol Jobs. Release v2.14.0 at 12:15; rolled back at 12:52. In the 37 minutes:
| Affected, from errors and support | Cortex (€499), Acme Labs (€299), Fjord (€99), three free accounts |
| MRR exposed | €897 a month, 3 of 8 paying accounts |
Payments failed, processing_error |
Cortex €499, Acme Labs €299: €798 blocked, both cleared the next day |
| Checkouts, against last Tuesday's window | 15 against 52: 37 missing |
| Revenue lost | Nothing in 30 days; Cortex's renewal, 25 days out, was already at risk for other reasons |
The one-line version for the incident report: "37 minutes; €897 MRR exposed across three paying accounts; €798 in payments blocked and recovered next day; 37 checkouts missing against baseline; no attributable churn."
The template
Keep one, and fill it every time, so the numbers are comparable across incidents:
Incident: checkout regression, v2.14.0
Window: 2026-09-19 12:15–12:52 UTC (37 min)
Affected: 3 paying (Cortex, Acme Labs, Fjord), 3 free
MRR exposed: €897 / €9,400 (9.5%)
Payments failed: €798 (2), recovered 2026-09-20
Checkouts: 15 vs 52 baseline (−37)
Support: 6 conversations
Lost (30 days): —
What Vesqo does with this
Vesqo writes the first four rows itself, the minute the release turns bad. It dates the window to the release from GitHub, reads the errors in Sentry and the key event in PostHog against the same hours the week before, joins the users hit to the paying accounts from Stripe, reads the payments that failed in the window and what support heard, and writes one signal with the MRR at stake and the first thing to do. The revenue impact use case shows the card; the demo has this afternoon in it.