Stripe's Customer Portal Finally Lets You Write Your Own Cancellation Reasons. It Still Won't Route Any of Them to an Offer.
Stripe's new Feedback Option API lets you write your own portal cancel reasons. It still won't route any of them to a save offer.
On September 30, Stripe added a resource most merchants have been missing for years: the ability to write your own cancellation reasons instead of picking from a list Stripe wrote for you. billing.feedback_option is a small object — one required field, five endpoints — but it quietly admits something about how little control merchants had over their own portal's cancel flow until now.
Until this release, if you used Stripe's hosted customer portal and wanted subscribers to pick a reason when they cancelled, you got five options, written by Stripe, that you could only turn on or off as a block: too_expensive, missing_features, switched_service, unused, and other. You couldn't reword them, split one into two, or add a sixth. If none of the five fit your product, your only option was the free-text field, which is exactly the format every piece of survey research says performs worst.
What actually shipped
The new billing.feedback_option resource gives you five endpoints: create, retrieve, update, list, and deactivate. Creating one is a single API call:
curl https://api.stripe.com/v1/billing/feedback_options -d "description=Too expensive for the value I'm getting"
Stripe returns an object with an id (prefixed fo_), a status of active, and a status_transitions.deactivated_at field that stays null until you deactivate it — the same active/inactive pattern Stripe already uses for coupons and prices, where retiring an option doesn't delete the historical record of who picked it. The description field is capped at 100 characters and is, as of this release, the only thing you can set on the object. No category, no internal key separate from the display text, no weighting or ordering field documented. It's a label, not a taxonomy.
Once you've created the set of reasons you want, you attach them to a customer portal configuration, and Stripe presents them to subscribers who cancel through the hosted portal. Stripe's own description of the point of this is direct: it's meant to let you "collect structured cancellation data and understand why customers are leaving" — in your own words, not a vendor's.
What this does and doesn't replace
The portal's existing subscription_cancel.cancellation_reason setting — the fixed five-option list — is still documented as its own thing, with its own enabled flag and its own options array. Stripe's changelog doesn't say that setting is going away, so the honest read right now is that you have two parallel mechanisms, not an upgrade path from one to the other.
| Mechanism | Who writes the reason text | How many reasons | Lifecycle |
|---|---|---|---|
| Old: subscription_cancel.cancellation_reason | Stripe (fixed five: too_expensive, missing_features, switched_service, unused, other) | 5, enable/disable as a block | No per-option control |
| New: billing.feedback_option | You, via the description field (100 char max) | As many as you create | Active/deactivated per option, old selections stay attached to past cancellations |
The practical upgrade is obvious: a developer tools company can now offer "the API doesn't do what I need" instead of forcing that subscriber into Stripe's generic missing_features. A fitness app can offer "I stopped going to the gym" instead of the closest-fit unused. That specificity matters more than it sounds like it should — a reason that actually matches what the subscriber is thinking gets picked instead of other, and other climbing past 15% of responses is usually a sign your options don't match your subscribers' reality, something we've written about in how to write a cancel survey that gets honest answers.
Structured beats open text, which is exactly why this matters
The reason a fixed list of buttons was always going to beat free text isn't specific to cancellation flows — it's a general finding about how people answer questions. SurveyMonkey's analysis of its own survey platform, presented at the American Association for Public Opinion Research, found that surveys opening with a multiple-choice question averaged an 89% completion rate, against 83% for surveys that opened with an open-ended comment box. The gap compounds: surveys with ten open-ended questions saw completion fall more than ten points below surveys with just one.
Source: SurveyMonkey analysis of 25,000+ surveys, presented at AAPOR
That's the gap Stripe's old five-option enum was built to close, and it closed it badly for anyone whose product didn't map cleanly onto those five labels. A custom feedback_option list closes the same gap without forcing subscribers into a reason that doesn't fit, which should mean fewer blank-feeling other selections and a reason distribution you can actually act on.
What Stripe still hasn't built
Here's the part worth being precise about before you treat this as a finished cancellation flow. A feedback option is a string and an id. Nothing in the object, and nothing in the customer portal configuration schema, connects a chosen reason to a consequence. Stripe's portal can show a pause option or a plan-switch option as part of subscription_update, but there's no documented hook that says "if the subscriber picked this reason, show that offer instead of just letting them finish cancelling." The reason capture and the retention offer are two separate systems that happen to share a cancel button.
That gap is the entire reason a cancellation flow that intercepts before the portal still earns its keep even after this release. The flow's job was never just to ask why — it's to look at the answer and immediately show a pause offer to someone who said they're not using it enough, or a discount to someone who said it's too expensive, in the same screen, before they've clicked away. A well-matched offer is what gets you into the 25–40% save-rate range a decent cancellation flow produces. Stripe's new API gets you better data about who's leaving and why; it doesn't get you any closer to keeping them.
Worth noting alongside this: the same release added a new invalid_canceled_subscription_fields error code, surfacing on invoice and payment-intent error objects. Stripe's changelog doesn't spell out exactly what triggers it, only where it appears — which reads less like a documentation gap and more like Stripe tightening validation around subscription cancellation parameters generally, the kind of change that's easy to miss until a previously-accepted request starts failing.
What to actually do with this now
- Write reasons that match your actual cancel data — not a generic template. If you've been running a cancel survey for a while (see our guide on writing one that works), your existing free-text responses are the best source for which specific phrases to turn into feedback options.
- Keep the count tight. The research behind structured questions outperforming open text assumes a short list. Five to seven feedback options, matching the sweet spot that's worked for cancel surveys generally, beats a sprawling menu that just recreates the decision fatigue of free text.
- Deactivate, don't delete, reasons that stop being relevant. Since deactivation preserves the historical link on past cancellations, you can retire a reason as your product changes without breaking your own churn-reason trend lines.
- Don't treat this as your retention flow. If you want the reason a subscriber picks to actually change what they see next, that logic still has to live in front of Stripe's portal, not inside it. Run your current cancel-reason mix through a churn calculator before deciding whether building that routing logic is worth the engineering time — for most subscription businesses losing more than a handful of accounts a month, it is.
Stripe closing the gap between "five reasons we picked" and "reasons that match your product" is a real improvement, and if you're not currently capturing any structured cancel data, wiring up a handful of feedback options this week is a low-effort way to start. It just isn't the same job as a cancellation flow. CancelFlow's entire premise is that the reason and the offer have to be connected in the same moment — ask why, then immediately act on the answer — and that connection is still something you have to build, whether you use Stripe's new reasons, your own, or both.
Frequently asked questions
What is Stripe's Billing Feedback Option API?+
It's a new resource Stripe added in the September 30, 2026 Endive API release — billing.feedback_option — that lets you define your own cancellation reasons for subscriptions instead of relying on Stripe's fixed list. You create one with a single required description field (max 100 characters), and you can retrieve, update, list, and deactivate them through the API. Once created, you attach them to a customer portal configuration so they appear as selectable reasons when a subscriber cancels through the portal.
Does the Feedback Options API replace Stripe's old cancellation_reason options?+
Stripe's existing customer portal configuration still documents a cancellation_reason object with a fixed set of five options — too_expensive, missing_features, switched_service, unused, and other — toggled on or off as a block. The Feedback Options API is a separate, newer resource for defining custom text instead of picking from that fixed list. Stripe's changelog doesn't say the old enum is being removed, so treat the two as sitting alongside each other for now rather than one replacing the other outright.
Can I route a Stripe feedback option to a specific retention offer automatically?+
Not through anything Stripe documents. A Billing Feedback Option is a label — an id and a description string. Nothing in the object or in the customer portal configuration schema connects a chosen reason to a specific action, like showing a discount to someone who picked a cost-related reason and a pause offer to someone who picked an inactivity-related one. If you want that kind of routing, you still need to build it yourself, typically by intercepting the cancellation before the portal rather than relying on the portal's built-in flow.
How do I create a custom cancellation reason in Stripe's customer portal?+
Call POST /v1/billing/feedback_options with a description parameter — for example, 'Too expensive for the value I'm getting.' Stripe returns an object with an id prefixed fo_, a status of active, and a status_transitions.deactivated_at field that stays null until you deactivate it. Then reference the feedback options you want to offer on your customer portal configuration so they show up when a subscriber cancels through the portal rather than through your own in-app flow.
Stop losing subscribers today
One script tag. One function call. A live cancellation flow in under 10 minutes.
Start free trial →