How to see all your Stripe cancellation reasons in one dashboard
Stripe has no page that lists your cancellation reasons together. Each one sits on the cancellation_details object of the individual subscription it belongs to, visible one record at a time in the Dashboard or one row at a time in a webhook payload. To see them rolled up, by reason and by how much MRR each reason cost you, you have to build the rollup yourself. The fastest way, if you're on a Stripe plan with Sigma, is a SQL query against your own data. If you're not, it's a small script reading the same webhook event everyone already wires up for billing.
Neither path is complicated. Below is the query shape, what it actually shows once you run it, and the point past which grouping by reason stops being enough to explain what happened.
What's on each cancellation, and what a rollup can and can't see
Every canceled subscription's cancellation_details carries a reason field, a system-level enum (cancellation_requested, payment_failed, payment_disputed, canceled_by_retention_policy) that says how the subscription ended. Separately, if the customer canceled through Stripe's hosted customer portal with reason collection turned on, feedback holds one of eight fixed options ("too expensive," "missing features," "found an alternative," and so on) and comment holds whatever free text they typed on top of it.
A rollup groups on feedback, since that's the field that's actually about why the customer left. It can count how many cancellations picked each of the eight options and sum the MRR attached to them. What it can't do on its own is read the comment field for meaning. It can display the text, but grouping stops at the eight fixed buckets. Three customers who all picked "missing features" show up as three rows under the same bucket whether they meant the same missing feature or three unrelated ones. More on the gap where feedback and comment come back empty in the first place is covered in why cancellation_details comes back null.
Why there's no rollup view already
This isn't an oversight you're missing in the Dashboard. A vendor breakdown of the gap puts it plainly: Stripe gives you no single page to view all cancellation reasons, no filter across them, no aggregate breakdown, and no CSV export of the set. The subscription detail page shows one cancellation at a time. Stripe's own reporting surfaces (the Dashboard's subscription list, a Sigma query, or the customer.subscription.updated webhook) all operate at the level of one subscription per row. Rolling those rows up into "which reason, how many, how much MRR" is left to whoever's building the report.
Building the rollup with Sigma
If your Stripe plan includes Sigma, Stripe's SQL reporting environment, this is a query, not a project. The subscriptions table mirrors the same subscription object the API returns, cancellation fields included, so a query grouped on the cancellation feedback gives you counts by reason directly.
For the MRR-lost half, join to subscription_item_change_events_v2_beta, a table built specifically for tracking MRR movement event by event. Filter it to event_type = 'ACTIVE_END', the row Stripe writes the moment a subscription item stops contributing revenue, and its mrr_change column is the exact amount that cancellation removed. Join that to the canceled subscription's reason, and summing mrr_change by reason gives you the number this whole guide is about: which reason cost the most money, not just which reason got picked most often. "Too expensive" and "missing features" can tie on count and still be nowhere close on revenue.
Sigma's results table shows a maximum of 1,000 rows on screen, and only adds a chart visualization when the query returns fewer than 10,000 rows. Neither cap touches the export: clicking Download CSV pulls every row the query returned, so you're not limited to the 1,000 viewable results. That makes the query a saved report anyone on the team can rerun, no rebuilding required.
The version without Sigma
If Sigma isn't on your plan, or you want the rollup somewhere other than the Stripe Dashboard, the same data reaches you through the customer.subscription.deleted and customer.subscription.updated webhook events. A prior guide on routing these webhooks covers wiring the endpoint itself. For the rollup specifically, the extra step is writing each cancellation's feedback, comment, and the subscription's monthly amount into a table of your own, one row per cancellation, instead of just forwarding it to Slack. A weekly GROUP BY feedback against that table, in whatever database already holds it, gets you the same counts and MRR totals Sigma would, just running against data you're storing rather than Stripe's.
What Iben found at Millbrace
Millbrace runs scheduling and invoicing software for pest control and lawn care companies, billed monthly through Stripe Billing across a few hundred accounts. Iben Kristoffersen, who handles revenue operations there, had watched MRR dip for two straight months without a clear story for why. Cancellations were happening; nobody had grouped them.
Iben built the Sigma query above on a Friday afternoon. Sorted by MRR lost rather than by count, the result looked like this.
Iben, in the team Slack: Ran the numbers. "Missing features" is only the second-most-picked reason, but it's the most expensive one by a wide margin, $6,100 of the $8,400 we lost to cancellations last month. "Too expensive" got picked more often but those were almost all $29 accounts. We've been assuming price was the story. It isn't.
That reordering, from "most complaints" to "most money," is what the join to subscription_item_change_events_v2_beta buys you over a plain count. It also reset the team's priority list for the next planning cycle, since the account sizes behind "missing features" turned out to matter more than the frequency of "too expensive."
Where the rollup stops answering the question
The rollup Iben built answers "which reason costs the most MRR." It doesn't answer whether the cancellations sitting under "missing features" are one shared gap or several unrelated ones. Stripe's rollup, however it's built, groups on the fixed feedback enum; it has no way to notice that multiple comments describe the same missing feature, because that comparison requires reading and clustering free text, not summing a column.
That's a real limit, not a build-it-better problem. A wider GROUP BY doesn't fix it; the enum only has eight buckets, and the thing worth knowing (are these the same request) lives in text Sigma can display but not cluster.
Where Modem picks this up
This is the point we build Modem for. Modem's Stripe integration reads the same cancellation events your Sigma query or webhook would, matches the canceling customer to the account's existing record, and clusters the comment text against every other mention of the same request across support tickets, Slack, and sales calls, not just other cancellations. Missing-features comments that turn out to describe the same underlying gap land as one counted topic with every account and the associated MRR attached, instead of a pile of rows tied together only by sharing an enum value. We build Modem, so weigh that against doing the clustering by hand, reading through comment text on the rows a rollup can't collapse further, which is genuinely fine at Millbrace's cancellation volume and gets slower as volume grows.
Start with the query, not the tool
If you haven't built the rollup at all yet, that's the first move regardless of scale: a Sigma query joining subscriptions to subscription_item_change_events_v2_beta, grouped by cancellation feedback, ordered by MRR lost rather than by count. That alone would have told Iben the real story a month earlier than watching MRR drift with no explanation attached. Whether you ever need more than that depends on how often "missing features" turns out to hide more than one request underneath it.
