He had been running his SaaS for 8 months.
$6k MRR. Growing steadily. No obvious problems.
I asked him one question.
"What happens in your app when invoice.payment_failed fires?"
He said "we handle it."
I asked him to open his webhook handler and find that case while we were on the call.
He went quiet for about 30 seconds.
Then he said "it logs the event and returns 200."
That was it.
No access revoked. No database updated. No user notified.
Just a log statement and a 200 response.
Stripe saw the 200 and considered the event handled. His app had no idea the payment failed. The user kept full access.
We pulled his Stripe event history right there on the call.
invoice.payment_failed had fired 31 times in the last 90 days.
In 26 of those cases — the downstream subscription showed no corresponding state change within 48 hours.
26 users whose payments failed. Still active. Still using the product.
We estimated the monthly impact at $480.
8 months of compounding.
He had not changed that handler since he shipped.
The part that hit him hardest was not the dollar amount.
It was that everything looked fine the entire time.
MRR was growing. Dashboard looked normal. Stripe showed payments coming in from other customers.
There was no signal. No alert. No threshold crossed.
The leak survived because it never looked like a leak.
It looked like normal.
Here is the thing I keep coming back to after running these audits.
The bug is almost never the hard part to fix.
18 lines of code inside that case statement. An hour of work.
The hard part is knowing it exists.
And you cannot know it exists by looking at your growth dashboard. Growth dashboards show you what is going right. They have no visibility into the intersection of who failed to pay in Stripe and who still has active access in your database.
That intersection does not have a dashboard.
You have to look for it deliberately.
If you shipped a paid product in the last year and have never specifically tested what happens when invoice.payment_failed fires —
Open your webhook handler right now. Find that case. Look at what is actually inside it.
If it is logging and returning 200 without touching your database or your user's access state — you have this bug running right now.
Comment "checklist" below and I will DM you the 7-event audit checklist I use for every manual audit.
Free. No pitch. Just the checklist.