stripewebhooksthin eventssubscription eventsinvoluntary churn

Stripe Added 51 New Webhook Events on Oct 28. Most Subscription Code Still Infers Them From One.

Stripe's Oct 28 release ships ~51 named webhook events for subscriptions and invoices, replacing the diff-it-yourself model most dunning code runs on.

XY
30 October 2026 · 8 min read

Right now, almost every integration that reacts to a Stripe subscription change is listening for one event — customer.subscription.updated — and then writing code to figure out what actually happened inside it. Did the subscription just go past due? Did a seat get removed? Did the customer's trial end? Stripe fires the same broad event for all of it and hands you a previous_attributes diff to sort out the rest yourself. On October 28, 2026, Stripe shipped a fix for that specific problem, and it's the kind of release that matters more to dunning and churn tooling than most Stripe announcements do, even though it never mentions churn once.

Key stat
51
new named webhook events Stripe added for subscriptions, invoicing, and schedules on Oct 28, most replacing a diff against one broad event
Source: Stripe Changelog, 2026-10-28.endive, "Adds semantic thin events for subscriptions, invoicing, and Financial Connections"

What actually shipped on October 28

Stripe's webhook system has long had two event flavors. A snapshot event — the kind almost every integration still runs on — sends the entire resource object in the payload along with previous_attributes, a field that holds only the old values of whatever properties changed. To know what actually happened to a subscription, your handler has to read the current object, cross-reference it against those old values, and reconstruct the business meaning: "status flipped from trialing to active" or "an item disappeared from the items array." That reconstruction logic lives in production code at thousands of SaaS companies, and it's fragile in exactly the way any hand-rolled diff is fragile — it breaks quietly when Stripe adds a field, and it treats a dozen genuinely different business events as the same notification with different shapes.

The October release doesn't just make events thinner — Stripe already had thin equivalents of snapshot events for that. It adds new event types that didn't exist before, each one named for a single business fact instead of a generic "something updated" signal. On the subscription side alone: v1.customer.subscription.active, past_due, trialing, unpaid, incomplete, and incomplete_expired cover status transitions directly. item_added, item_removed, item_price_changed, and item_quantity_changed cover what happened to a specific line item inside the subscription, rather than leaving you to diff the whole items array. default_payment_method_changed and cancel_at_changed cover two of the most operationally important subscription fields in isolation. On the invoicing side, v1.invoice.discount_added, discount_removed, and three line-item events do the same job for invoices, and v1.promotion_code.fully_redeemed fires the moment a promo code runs out of uses.

What you had to do beforeWhat fires nowWhat it tells you without guessing
Listen for subscription.updated, check if status === "past_due"v1.customer.subscription.past_dueThis subscription just entered dunning — trigger your failed-payment sequence now, not on the next poll
Diff the items array between old and new objectv1.customer.subscription.item_removedA seat or line item was dropped — a contraction signal your MRR math never sees until the next invoice
Check previous_attributes.trial_end against the current valuev1.customer.subscription.trialingThe subscription is in trial right now — no object diffing required to know it
Diff invoice.discount against the prior invoicev1.invoice.discount_removedA discount just came off this invoice — the moment to check if the customer notices the new price
Poll the PromotionCode object or wait for a support ticketv1.promotion_code.fully_redeemedA retention offer code just ran out of redemptions and silently stopped saving anyone

Why the seat and discount events matter more than the status ones

The status events get the attention because dunning is the obvious use case — a direct past_due notification is a cleaner trigger than parsing it out of Stripe's dunning retry schedule indirectly. But the quieter wins are item_removed, item_quantity_changed, and discount_removed, because those are exactly the signals that seat-based churn and discount-driven churn hide from most monitoring today. A team plan that drops from 40 seats to 32 doesn't cancel anything — nobody hits a cancel button, nothing shows up in a churn survey — but it's contraction revenue that most dashboards only catch at the next invoice reconciliation, if they catch it at all. An item_quantity_changed event firing the moment it happens gives you a same-day signal instead of a 30-day-later one.

The discount events do something similar for a problem we've covered before: the discount cliff, where a subscriber churns the moment their promotional price reverts to full price. Today, catching that requires knowing your own discount schedule and watching the calendar. A v1.invoice.discount_removed event fires at the exact moment the price reverts on a real invoice, which means you can fire a retention touchpoint — an email, an in-app notice, a one-time offer — timed to the actual billing event instead of an internal reminder that might be a day or two off from what Stripe actually billed. v1.promotion_code.fully_redeemed solves a related and more embarrassing gap: if you're running a win-back or retention-offer promo code with a redemption cap, this is the first native signal that tells you the code is dead before a customer tries to use it and hits an error.

Share of the 51 new event types, by product area
Subscriptions & schedules35%
Invoicing22%
Financial Connections20%
Pricing & catalog16%
Entitlements & promotions8%

Source: Stripe Changelog, 2026-10-28.endive release notes (event type counts by object)

What it actually takes to receive one of these

This isn't a flag you flip on an existing webhook endpoint. Thin events — semantic or otherwise — are delivered through a separate API v2 concept called an Event Destination, not the classic webhook endpoints most integrations already have configured. To receive v1.customer.subscription.past_due, you create a new destination with event_payload: "thin" and subscribe it to that specific type, either in the Dashboard under Developers → Webhooks with "Use thin events" toggled on, or via a POST to /v2/core/event_destinations. The notification you receive is deliberately minimal — the event type and a reference to the related object, not the object itself — so your handler fetches the current state with fetchEvent() or fetchRelatedObject() when it needs more than "this happened."

Stripe's own migration guidance is explicit that you shouldn't cut over all at once. The recommended path is to stand up the new destination alongside your existing one, run it in shadow mode — logging what you'd do without writing anything — for 24 to 48 hours, then cut over with idempotency keys to handle the brief overlap window where both destinations fire for the same underlying change. That overlap matters operationally: it's the same at-least-once delivery problem we've written about before, just now running across two event systems simultaneously instead of one, which makes the deduplication discipline in our webhook reliability piece more relevant during migration, not less.

The part this doesn't fix

Naming the event correctly doesn't eliminate the extra API call. A thin event still only tells you what kind of thing happened and where to look — you fetch the related object separately to read its current state, which means every one of these new signals costs you a round trip you didn't need with a snapshot event's full payload already sitting in the request body. For a handler that already fetches fresh state anyway (which is the correct pattern, since a snapshot payload can be stale by the time you process it), this is a wash. For a thin, high-volume handler that was leaning on the snapshot payload to avoid an API call entirely, it's a real latency and cost tradeoff worth measuring before you migrate every event type at once — which is exactly why Stripe recommends migrating type by type rather than flipping the whole integration over in one deploy.

None of this replaces the reconciliation job you should already be running against the Stripe API independent of any webhook, thin or snapshot — a more precise event name tells you faster that something happened, but it still depends on delivery succeeding, and at-least-once delivery can still duplicate or delay a notification regardless of which payload format carries it. What it does change is how much custom diffing logic has to exist between "Stripe fired an event" and "my product knows what that event meant," which for the seat, discount, and status signals above is exactly the gap that turns a delayed churn or dunning reaction into an on-time one. Getting that upstream signal right before it reaches a churn calculation or a health score is worth more than most teams credit it — and for the subscribers who do make it to a cancel screen, having clean, current subscription state is also what lets CancelFlow decide which save offer to show, instead of guessing from data that was already a few hours stale.

Frequently asked questions

What is the difference between a Stripe thin event and a snapshot event?+

A snapshot event (the classic kind, like customer.subscription.updated) carries the full resource object plus a previous_attributes field showing only the old values of whatever changed. A thin event carries a compact notification — the event type and a reference to the related object — and you fetch the current state separately via the API. Thin events stay stable across API version upgrades; snapshot event payloads change shape when you upgrade your API version, which is why Stripe recommends thin events for new integrations.

Do I have to migrate my existing Stripe webhooks to use the new semantic events?+

No. The October 28, 2026 release is additive — your existing snapshot webhook endpoints keep working exactly as they do today. The new semantic thin events require setting up a separate Event Destination configured for thin payloads; you can adopt them for specific signals (like subscription.past_due or subscription.item_removed) without touching your current webhook handler at all.

What are Stripe's new semantic subscription events?+

They're named, single-purpose webhook events that fire for one specific kind of change instead of one broad event firing for all of them. Examples include v1.customer.subscription.past_due, v1.customer.subscription.trialing, v1.customer.subscription.item_removed, v1.customer.subscription.default_payment_method_changed, and v1.invoice.discount_removed. Roughly 51 of these shipped across subscriptions, invoicing, pricing, and Financial Connections in the 2026-10-28.endive release.

How do I receive a Stripe thin event like v1.customer.subscription.past_due?+

You create a new Event Destination (via the Dashboard under Developers > Webhooks, or the /v2/core/event_destinations API) with event_payload set to "thin", then subscribe it to the specific v1.-prefixed event type you want. This is a separate destination from your classic webhook endpoint — Stripe's own migration guide recommends running both in parallel for a short overlap window rather than cutting over all at once.

Try CancelFlow

Stop losing subscribers today

One script tag. One function call. A live cancellation flow in under 10 minutes.

Start free trial →
← All postsHome