CHURN ANALYSISSTRIPE

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.

Published 6 min read

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:

  1. A cancellation is scheduled. When cancel_at_period_end turns 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.
  2. A cancellation is removed. When cancel_at_period_end goes back to false, you kept the customer. Store this too, or your saves are invisible.
  3. 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:

  • reason tells you why it ended: cancellation_requested, payment_failed or payment_disputed, among others. This is how you split voluntary from involuntary churn without guessing.
  • feedback holds the customer’s chosen reason, such as too_expensive, unused, missing_features or switched_service.
  • comment holds 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:

  1. Events can arrive out of order. Stripe does not guarantee delivery order. Use the event’s created time and the object’s own fields, not arrival order, to build history.
  2. Events can arrive more than once. Deduplicate on the event ID before writing anything.
  3. Return a 2xx quickly. Store the raw event, acknowledge it, and process it in a background job.
  4. Verify signatures. Check the Stripe-Signature header 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.
  • reason and feedback at 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.

PROMPT

Read https://www.nochurn.sh/llms.txt,
then follow https://www.nochurn.sh/docs/quickstart/
to add NoChurn to this app. Use a test key
(nc_test_…) until I say we go live.

Stop leaking revenue.

  • Free until $300 MRR
  • No card needed
  • Live in 10 minutes
  • Never a cut of what we recover