Which Stripe webhooks to track churn, and what each one means
The Stripe webhook events that matter for churn tracking: cancellations, pauses, failed payments, trials, card updates and disputes, and how to store them.
Stripe’s Dashboard shows you subscriptions as they are now. Churn analysis needs the story of how they got there: when a cancellation was requested, when it took effect, whether it followed a failed payment, whether the customer paused first. That story lives in webhook events, and if you do not store them as they arrive, you cannot rebuild it later with much confidence.
This guide lists the Stripe events that matter for churn, what each one means and what to store. It sticks to Stripe’s using webhooks with subscriptions guide, the webhooks overview, the cancel subscription API reference, the Early Fraud Warning reference and Stripe’s support article on the card updater (all accessed November 2026).
The short list
| Event | What it means for churn |
|---|---|
customer.subscription.created |
A subscription started. Not proof of payment. |
customer.subscription.updated |
Something changed: plan, coupon, renewal, a scheduled cancellation, a collection pause |
customer.subscription.deleted |
The subscription ended. This is the churn event. |
customer.subscription.paused |
The status changed to paused |
customer.subscription.resumed |
A paused subscription came back |
customer.subscription.trial_will_end |
A trial ends soon |
invoice.payment_failed |
A payment attempt failed |
invoice.paid |
An invoice was paid, including after a failure |
payment_method.automatically_updated |
The card updater replaced card details |
charge.dispute.created |
A customer disputed a charge |
radar.early_fraud_warning.created |
An issuer flagged a charge as possible fraud |
The rest of this guide goes through them in the order they usually happen.
Starting and changing
customer.subscription.created
Stripe sends this when a subscription is created. It is the start of your cohort data, but it is not proof that the customer paid. On a first invoice that fails, the webhooks guide says the subscription stays incomplete. Store the created date, but count a customer as acquired only once the first invoice is paid.
customer.subscription.updated
Stripe describes this event as sent when a subscription starts or changes: renewing, adding a coupon, applying a discount, adding an invoice item and changing plans all trigger it. For churn, three changes inside it matter most:
- A cancellation is scheduled. When
cancel_at_period_endturns true, the customer has decided to leave, even though the subscription is still active. Store this date. It is when the decision happened, which can be weeks before the subscription ends. - A cancellation is removed. When
cancel_at_period_endgoes back to false, you kept the customer. Store this too, or your saves are invisible. - A downgrade. A plan change to a cheaper price is contraction, the quiet cousin of churn.
Because this event fires for many reasons, compare the new object with previous_attributes in the event to see what actually changed.
Ending
customer.subscription.deleted
The webhooks guide says it plainly: sent when a customer’s subscription ends. This is your churn event. Its timestamp is when revenue stopped.
Read cancellation_details on the subscription, documented in the cancel API reference:
reasontells you why it ended:cancellation_requested,payment_failedorpayment_disputed, among others. This is how you split voluntary from involuntary churn without guessing.feedbackholds the customer’s chosen reason, such astoo_expensive,unused,missing_featuresorswitched_service.commentholds the free text the customer wrote.
If you remember one thing from this guide, store reason and feedback at the moment of deletion. For the metrics built on top, see how to calculate churn rate and involuntary churn.
Pausing
customer.subscription.paused and customer.subscription.resumed
Stripe sends customer.subscription.paused when a subscription’s status changes to paused, including automatically when a trial ends without a payment method if you configured that. It sends customer.subscription.resumed when a paused subscription is resumed, and the guide notes it does not apply when only payment collection is unpaused.
That distinction matters: a pause of payment collection (pause_collection) does not change the status, so you only see it through customer.subscription.updated. If you offer pauses as a retention tool, track both kinds. Details are in how to pause a subscription on Stripe.
A paused customer has not churned. Report them separately, and attribute any later cancellation to the original reason.
Trials
customer.subscription.trial_will_end
Stripe sends this three days before a trial ends, or right away if the trial is shorter than three days. It is your last good moment to remind the customer, check that a payment method is on file, and make sure they reached the part of the product that makes them stay. Trial-to-paid is a churn metric in its own right; see trial to paid conversion.
Payments
invoice.payment_failed
A payment attempt on an invoice failed. On renewals this starts the dunning window. Store the attempt number, the decline code and the advice code from the PaymentIntent’s last_payment_error; our guide to Stripe decline codes explains what each means.
Do not record churn here. A failed payment is a risk, not an exit. The subscription only churns when it is deleted, or when your recovery settings move it to a state where you stop serving the customer.
invoice.paid
The invoice was paid. After a failure, this is your recovery event. Store which attempt succeeded and whether the customer changed their card first. Over time, that tells you whether retries or emails do most of the recovery work.
payment_method.automatically_updated
Stripe’s card updater support article says Stripe creates this event when the card updater changes a card’s details. It explains a renewal that succeeds on a card that would otherwise have expired. Logging it lets you see how much involuntary churn the network prevents for you; more in expiring cards.
Disputes and fraud
charge.dispute.created
A customer disputed a charge with their bank. For churn, a dispute is almost always the end of the relationship, and Stripe records payment_disputed as a cancellation reason when a subscription ends for that cause. Track disputes separately from cancellations: they cost fees, and they need a response.
radar.early_fraud_warning.created
The Early Fraud Warning reference describes it as the issuer notifying Stripe that a charge may be fraudulent. It is not a dispute yet. If you sell subscriptions, decide quickly whether to refund and stop future charges. We cover that decision in Stripe early fraud warnings.
Storing events so the numbers hold up
Stripe’s webhooks overview documents a few behaviors that shape how you store events:
- Events can arrive out of order. Stripe does not guarantee delivery order. Use the event’s
createdtime and the object’s own fields, not arrival order, to build history. - Events can arrive more than once. Deduplicate on the event ID before writing anything.
- Return a 2xx quickly. Store the raw event, acknowledge it, and process it in a background job.
- Verify signatures. Check the
Stripe-Signatureheader with your endpoint secret before trusting a payload.
A simple table works well: event ID, type, created time, customer ID, subscription ID, and the raw payload. Derive your churn tables from it. When a definition changes, you can recompute from the raw events instead of losing history.
What to derive
- Subscription start (first paid invoice), cancellation requested, cancellation removed, ended.
reasonandfeedbackat the end.- Pause start and end.
- Each failed payment with codes, and the recovery that followed.
- Disputes and early fraud warnings by customer.
Doing this with NoChurn
NoChurn connects to Stripe in one click, and reads these events for you, so you do not have to build the storage above yourself. It uses them for smart retries, recovery emails you review, cancel flow answers, win-back emails at day 30, 60 and 90, and a churn score from 0 to 100. You can add your own customer data by API. Stripe stays the source of truth for billing. See NoChurn + Stripe.