The 12 Stripe Webhook Events That Decide Whether Your SaaS Gets Paid
Here is something you might not know: there are roughly 250 event types that Stripe can send you. They determine payment, churn rate, proration behavior to subscription failure. Each signal tells you about the health of your billing system.
Most apps subscribe only to a few of these:
- checkout.session.completed — tells you that a client has paid
- invoice.payment_failed — tells you a payment has not gone through
- customer.subscription.created — tells you a subscription is now live
That's about it.
But there's more.
To run a reliable SaaS billing architecture, you need to track at least 12 Stripe events. I have gone over the entire Stripe Docs to fish these out.
I promise you will understand why your SaaS needs to track them once you are done reading. If you don't believe me, consider that Stripe stores them for a reason.
This is the list I track in RevEng. It is a working answer to "what do I need to handle to run reliable SaaS billing." I also tell you what breaks when you don't track each one of them.
The Four Jobs These 12 Events Do
Before the list, the grouping matters more than the individual events. Every event below exists to serve one of four jobs:
- Getting paid the first time — checkout and initial subscription creation
- Staying paid — subscription lifecycle and renewal
- Recovering when payment fails — the dunning surface
- Knowing when you're about to lose money — disputes and early warning signals
Job 1: Getting Paid the First Time
checkout.session.completed
This webhook fires when a customer finishes Stripe Checkout. All Stripe integrations capture this, because the data it provides is pivotal to running a successful SaaS operation.
When creating a billing system, keep in mind that this event fires on successful checkout completion, not successful payment. For some payment methods, the payment can still be processing or even fail after this event. So, as a rule, provision access only after also confirming payment status, not just when this event succeeds.
customer.subscription.created
Once payment is confirmed, the subscription object now exists. This is worth tracking separately from checkout completion because subscriptions can also be created directly via the API, outside a Checkout flow. So, if you only listen for checkout.session.completed, API-created subscriptions slip past your provisioning logic entirely.
payment_intent.succeeded
When Stripe sends you this webhook, it means the actual charge succeeded. This is the signal you should always rely on for gating access to your product or service. In short, this is the webhook that tells you there is money in your account.
With these three events, you will have a running SaaS that can receive payments, and grant access to a paying customer.
Job 2: Staying Paid
customer.subscription.updated
This webhook deals with changes to subscription status — quantity changes, trial-to-paid conversion, and status transitions (active → past_due → unpaid). This is the highest-risk event on the whole list because it fires for many different reasons. If your billing architecture doesn't capture the nuance, you will have confused customers asking what happened to their subscription status, and your proration will fail miserably.
customer.subscription.deleted
This webhook tells you that a subscription is gone — canceled, either by the customer, by you, or by Stripe after exhausting retries. The thing to watch out for with this event is order of arrival. If subscription.updated events arrive after this one (given webhook delivery isn't strictly ordered), a naive handler can accidentally resurrect a canceled subscription.
customer.subscription.trial_will_end
This webhook fires a few days before a trial converts to paid. If you miss this, you lose the single best moment to reduce trial-to-paid churn. I advise you to create a dunning system that sends a warning email to your customers, or at least a reminder.
invoice.upcoming
Fires before a renewal invoice is finalized, while you can still react — add a discount, flag a known problem, or warn a customer whose card you already know is expiring. Smart SaaS operations listen for this because you're able to react to billing problems before the payment date.
Job 3: Recovering When Payment Fails (the Dunning Surface)
invoice.payment_succeeded
The renewal actually got paid. Sounds obvious, but this is the event that has to fire correctly for a dunning system to know when to stop — a dunning engine that doesn't listen for this exits late, and a customer who already paid keeps getting "your payment failed" emails. RevEng's dunning engine treats this as the hard exit condition for every active sequence.
invoice.payment_failed
All dunning systems, including RevEng, are built to react to this Stripe event. Again, you need nuance here. A card_declined from insufficient funds and an expired_card decline need completely different recovery sequences — one benefits from retry timing, the other needs a card-update email. The solution is to route on decline code, not just on the event firing.
I will create a separate article for this so you can see which decline codes matter more than others.
invoice.payment_action_required
The charge needs customer authentication (3D Secure/SCA) before it can succeed — common for EU/UK cards, and increasingly common elsewhere. This gets missed constantly because it isn't a decline in the traditional sense; it's a stall. Systems that don't specifically listen for this event and email the customer with an authentication link will miss subscription revenue that's easily recoverable.
charge.refunded
This webhook keeps your numbers clean because without it, you cannot calculate an honest MRR. Without this, a refunded charge still shows in your system as "recovered revenue" when it isn't, which quietly corrupts every dashboard number downstream of it.
Job 4: Knowing When You're About to Lose Money
charge.dispute.created
Obviously, you will run into customers who dispute charges — that's why you need this webhook. When the event arrives, you typically have a matter of days to submit evidence. Keep in mind that Stripe's own dispute-fee structure means a small-ticket dispute can cost more in fees than the original charge was worth, regardless of outcome. RevEng's dispute-rate signal exists specifically because most founders find out about a dispute problem from their monthly Stripe summary, weeks after the window to respond usefully has already closed.
What Breaks When You Skip the Rest
The obvious four (checkout.session.completed, invoice.payment_succeeded, invoice.payment_failed, customer.subscription.deleted) get handled by many SaaS integrations. A step-up system also includes invoice.upcoming, invoice.payment_action_required, customer.subscription.trial_will_end, and charge.dispute.created. None of them are hard to listen for. They're just easy to ignore. As a result, revenue leaks quietly.
What I Don't Know Yet
In the spirit of not overstating this: I don't have a large cross-customer dataset yet showing which of these 12 events correlates most strongly with actual recovered revenue versus which ones are more "good practice" than "measurably load-bearing." I expect to figure this out as I work with more SaaS operations on their revenue recovery.
I will also dig deep into Stripe data and see what the guys over there have figured out.
Should You Build This Yourself?
Certainly. If you run a SaaS, any dollar lost to involuntary churn is an enemy to your dream of getting rich. Even if you don't dream of getting loads of money and just want to solve a real problem through, say, receiving donations, you still need every dollar that wants to come your way. Building one will take you anywhere from a few days to a couple of weeks depending on how much of the dunning logic (decline-code routing, Klaviyo/email integration, retry scheduling) you want custom versus minimal.
Not to state the obvious, a DIY project also requires constant vigilance. Decline codes and card network behavior shift, and a dunning system that isn't actively maintained slowly drifts out of date with how banks actually respond to retries.
What Can I Help With?
A Stripe Audit is the best place to begin. The report it generates will tell you everything about your system.
If you want a second pair of eyes on which of these 12 your own integration is actually handling — not guessing at — that's the exact gap-analysis a Stripe audit closes. Get in touch.
Hi, I'm Mathew — I build Stripe billing infrastructure, dunning systems, and payment integrations that keep SaaS revenue from leaking. If this kind of thing is useful to you, I write more of it over on Medium and post updates on LinkedIn.
I help SaaS founders recover up to 50-60% of their “failed payment” churn through technical Stripe & Klaviyo optimization.