TL;DR: A cancellation survey is only useful if answers land as coded data: one required multiple-choice question plus optional open text, reason and comment in separate fields, request versus effective cancellation tracked, and stated reasons validated against behavior.
Your cancel flow probably already asks why the customer is leaving. The problem is what happens next: the answer lands in a dropdown label and a text blob, someone exports it once a quarter, and the "why people cancel" conversation stays a wall of text. This guide is for lifecycle, product, and subscription operations leads at mobile and consumer subscription businesses who need to design subscription cancellation surveys you can use for reporting, save offers, and product decisions — not just for optics.
The fix is not a longer survey. It is a measurement design: the right questions, the right fields, and a coding rubric that turns answers into rows you can query. Teams usually justify this kind of lifecycle instrumentation with business process automation ROI; a cancellation survey earns it when every reason maps to an action you can measure.
Which cancellation survey questions to ask
The answer: one required multiple-choice question — "What is your main reason for cancelling?" — plus at most one optional open-text field. That is the whole form for most products. The customer came to cancel; every extra required question is another chance to abandon the flow, and the person answering has already decided to leave, as cancellation-survey practitioners like AcornRetain and ChurnIntel both advise.
Build the reason list from what your customers actually say, not from what you imagine. Most cancellation survey examples stop at the question list; the list is the easy part, and getting it roughly right matters less than keeping it short. Start with four to six options and cap the list around eight to ten. A working starter set, grouped the way operators group them:
| Reason group | Example options customers understand |
|---|---|
| Price and value | Too expensive for what I get; found a cheaper alternative; need to cut spend |
| Product fit and usage | Not using it enough; missing a feature I need; too hard to use |
| Onboarding and support | Never got set up properly; support did not resolve my issue; bugs or reliability problems |
| Competition | Switching to a different service |
| Business context | Project or season ended; no longer need this |
Then the rules that keep answers usable:
- Keep the reason list short and scannable. Refine it from what people type into the open field — the free text is where tomorrow's new codes come from.
- Make the follow-up optional and conditional. After "too expensive," ask what they would pay. After "missing a feature," ask which one. Leave the box off answers you would do nothing with.
- Route every option to an action. Price earns a discount or plan-change path, a temporary need earns a pause, a missing feature earns a roadmap note, and a payment problem earns a card-update link — not a discount, per Swipe Again's exit-survey design rules. An answer you cannot act on is a question you should not ask.
- Keep a visible exit on every screen. "Continue cancelling" must be one tap away at all times. A survey that blocks the exit collects the fastest available answer — garbage data — and, in the US, a hard-to-cancel flow still carries legal exposure even after the FTC's click-to-cancel rule was vacated in July 2025, because ROSCA and state auto-renewal laws remain in force, per Latham & Watkins' analysis of the ruling. That is a compliance boundary, not legal advice — confirm your own counsel's read.
If you run your cancellation survey through the Stripe customer portal, the same structure applies — a Stripe cancellation survey there collects one fixed reason list plus an optional comment, which is exactly the one-question-plus-text pattern.
Separate the selected reason from the free text
The answer: store two fields, never one. The multiple-choice answer is a coded enum — countable, groupable, safe to chart. The open-text answer is a comment — qualitative context you read, not a column you average. The moment you parse prose to fill a report, you are back to the wall of text.
Stripe's own data model is the cleanest public example of this split. When a subscription is canceled, the Subscription object carries a cancellation_details object with three parts, per the Stripe API reference:
feedback— the customer-submitted reason, a fixed enum:too_expensive,missing_features,switched_service,unused,customer_service,too_complex,low_quality, orother.comment— free text the customer added, populated only when they bother to write it.reason— why the subscription was canceled at the system level:cancellation_requested,payment_disputed,payment_failed, orcanceled_by_retention_policy.
Notice what Stripe separates that a naive survey does not: the customer's stated reason (feedback), the customer's own words (comment), and the system's reason (reason). A card that failed three times and auto-canceled never saw your survey at all — it lands in reason, not feedback. Mirror that split in your own table and your voluntary-churn reporting stops lying about payment churn.
One more practical detail: nothing routes these answers anywhere by default. Stripe stores the feedback on the subscription object until something reads it — your backend should listen for customer.subscription.updated and copy cancellation_details into your analytics pipeline, as Modem's routing guide describes. The same is true of any survey tool: capture is a data decision, routing is a separate build.
Track the cancel request, not just the cancellation
The answer: a voluntary cancellation has two timestamps — the moment the customer asked to leave and the moment access actually ends — and your dataset needs both. They are different events, sometimes weeks apart, and the gap between them is where save offers and win-back timing live.
Both billing platforms in a typical subscription stack expose this split:
- Stripe: setting
cancel_at_period_endfirescustomer.subscription.updated; the subscription actually ends later, whencustomer.subscription.deletedfires, per Stripe's subscription webhook docs. An updated-with-pending-cancel is a request. A deleted is an effective cancellation. - RevenueCat: a
CANCELLATIONwebhook fires when the customer cancels or a refund lands, but the user keeps access untilEXPIRATIONat period end — and anUNCANCELLATIONevent fires if they re-enable auto-renew before then, per the RevenueCat event reference. A request that gets reversed is not churn at all; it is a save.
Two consequences for the dataset. First, count survey responses against requests, not effective cancels: the customer answered at request time, and a later UNCANCELLATION or a successful save does not erase what they told you. Second, involuntary cancels never pass through your survey. Stripe marks them in reason as payment_failed or payment_disputed; RevenueCat sends BILLING_ISSUE plus a CANCELLATION with cancel_reason=BILLING_ERROR, per the RevenueCat webhook flow docs. Keep those out of the survey denominator — your "top cancel reasons" chart describes voluntary exits only. The involuntary side is a different problem with its own machinery, covered in failed payment recovery.
Timing caveat worth encoding: RevenueCat says most webhooks arrive within 5–60 seconds, but cancellation events can take up to about 2 hours after the user cancels, per its webhooks overview. If your save offer or Slack alert keys off the survey click rather than the webhook, it will often beat the billing event.
Validate stated reasons with behavior
The answer: a selected reason is a hypothesis, not a fact. Exit answers are filled in by people who are already leaving — they have every incentive to pick the fastest socially acceptable option, which is why "too expensive" tops almost every churn survey, as Startup Fortune's churn-reason tracking guide points out. One public Indie Hackers post describes a SaaS founder finding the same gap in his own data: users selected "long file processing failed" while their task history showed dozens of successful jobs — the stated reason was whatever got them out of the modal fastest, per his write-up. One anecdote is not a benchmark, but the mechanism is common enough that you should design for it.
Cross-check each stated reason against what the customer actually did:
| Stated reason | Behavioral signature that should co-occur | If the signature is missing |
|---|---|---|
| Too expensive / not enough value | Usage declining before the request; price-sensitive plan tier | Often an engagement problem wearing a price excuse — check last-active date first |
| Not using it enough | Low sessions or stalled activation milestones | Genuine low usage; look at onboarding drop-off, not features |
| Missing a feature | Searched for or attempted the workflow; support tickets naming it | Vague "missing feature" with no search or ticket may mean wrong-product-fit acquisition |
| Poor support / bugs | Recent unresolved tickets or error-heavy sessions | No contact history means the complaint is probably proxy for something else |
| Switching to a competitor | Tenure, prior engagement, pricing-page visits | Confirm with the optional "what are you switching to" follow-up |
The point is not to call customers liars. It is to route each reason to the right fix — a discount cannot repair an onboarding gap, and a feature sprint cannot fix a customer who never activated. Compare stated reasons across cohorts before acting on them; the cohort view in subscription cohort LTV analysis is what turns "price is our top cancel reason" into "price is our top cancel reason in month-one paid cohorts," which is a different decision.
Watch the "other" bucket as your taxonomy health check. When "other" climbs to one of your top reasons, the list failed — mine the free text for the missing code, add it, and stop shipping surveys whose most common answer is a shrug.
The reason coding rubric
This is the working asset: a field spec plus coding rules that turn survey answers into queryable rows. The example rows are a synthetic illustration built on documented platform fields — a planning model, not a measured customer result.
Fields to store per response:
| Field | Type | Rule |
|---|---|---|
selected_reason |
enum | One of 8–10 internal codes; required; mapped from the survey option |
free_text |
string | Optional comment; never parsed into reports |
follow_up_answer |
string | Optional conditional detail (which feature, what price) |
cancel_requested_at |
timestamp | When the customer asked to cancel (survey submit / pending-cancel event) |
effective_end_at |
timestamp | When access actually ends (period end); null while pending |
voluntary |
boolean | False when the cancel is payment-driven (Stripe payment_failed/payment_disputed, RevenueCat BILLING_ERROR) |
outcome |
enum | canceled, saved, rescinded (UNCANCELLATION), expired involuntary |
save_offered / save_accepted |
enum | Which offer branched on the reason, if any |
survey_version |
string | Reason lists change; version them or trends break silently |
Coding rules:
- Map survey options to a fixed internal enum — not to raw display labels, which drift when copy changes. Stripe's
feedbackvalues are a reasonable seed list; add only codes you will act on. - Free text informs, never overrides,
selected_reason. A comment saying "actually I loved it" does not recode atoo_expensiveselection; it annotates it. - The voluntary flag comes from billing events, not the survey. Payment-driven cancels have no survey answer; forcing one contaminates the data.
- Outcome is written by the lifecycle events, not the survey: request sets it to pending,
UNCANCELLATIONor a save setssaved/rescinded, effective end setscanceled. - Re-validate codes quarterly against behavior. Retire reasons nobody picks; split reasons whose behavioral signature is inconsistent.
Synthetic worked rows (labeled synthetic — a format example, not measured data):
| selected_reason | free_text | voluntary | outcome | Validation flag |
|---|---|---|---|---|
too_expensive |
"love it but can't justify $30/mo" | true | saved (discount) | Check usage — if low, flag as engagement churn |
missing_feature |
"needs offline mode" | true | canceled | Confirmed: 3 support tickets naming offline mode |
| — | — | false (payment_failed) | expired involuntary | No survey row; routes to dunning, not the survey report |
other |
"moving abroad, app not available" | true | canceled | Free-text mined → candidate new code availability |
With these rows, the weekly question "why did people cancel" becomes three better questions: which voluntary reasons are growing, which stated reasons fail their behavioral check, and how much churn was never the survey's to measure. That is the difference between a dataset and a wall of text — the same discipline that turns closed-lost CRM reasons into follow-up actions in a reason taxonomy, or return codes into fixes in return reason analysis.
Where to place the survey in the cancel flow
The answer: timing is a tradeoff, so pick by what you will do with the answer. Ask at cancel intent — inside the flow, before the final confirm — if the reason should branch a save offer, because you cannot branch an offer on an answer you do not yet have. Ask after the cancellation is confirmed if you want purer research, because nothing rides on the answer and people respond more freely. Both are defensible; AcornRetain's guidance is explicit that they serve different purposes.
Two placement rules are not a tradeoff:
- Do not hard-block the exit. One required tap-through question is fine; a required essay or a hidden "continue cancelling" link is not. Beyond the data-quality problem (trapped users pick the fastest answer), US auto-renewal rules — ROSCA, FTC Act Section 5, and state laws — still govern cancel-flow obstruction even though the FTC's standalone click-to-cancel rule was vacated on July 8, 2025, days before it was to take effect, as Reuters reported.
- Survey once, store forever. The response row you write at request time should survive the outcome — save, uncancel, or effective end. The dataset outlives the save offer.
If your cancel reasons today live in a dropdown export nobody opens, a bounded audit of the flow — fields, events, routing — is usually a one-week fix. That'sGonnaHelp runs that kind of automation audit for subscription teams; the output is the coding rubric above wired to your actual stack.
FAQ
What is a churn survey?
A churn survey — also called a customer churn survey, cancellation survey, or customer exit survey — is the short questionnaire shown when a customer cancels a subscription. Its job is to capture the stated reason for leaving as structured data, so the business can route saves, fix causes, and report trends. It is not the same as an HR exit survey for departing employees, which is what most generic "exit survey" advice covers.
How many cancellation survey questions should you ask?
One required question plus one optional open-text field is enough for most subscription products — a single-select reason list of four to ten options, optionally followed by a conditional detail prompt on the answers you can act on. Every additional required question trades response rate for little extra signal.
What if most people pick "other"?
Then the reason list failed, not the customers. A top-ranked "other" means your options do not match the reasons people actually have; read the free-text comments, extract the missing code, add it as a real option, and re-run. Treat the "other" share as the survey's own quality metric.
If Stripe already collects a cancellation reason, do we still need a survey?
The Stripe customer portal's reason list is a fixed enum with an optional comment — useful as a baseline, but it has no conditional follow-ups, no product context, and only covers cancels that happen through that portal. Keep it as a supplementary source and mirror cancellation_details into your dataset; run your own survey if you need richer or branched answers.
Can we require an answer before the customer can cancel?
You can require a single selection, but pair it with a permanently visible "continue cancelling" path. Trapping people behind multi-step forms degrades the data — they pick the first option to escape — and keeps your flow on the wrong side of US auto-renewal and deceptive-practices rules.
Answer clarity notes
- Dates: this article's publish date is August 11, 2025. Vendor documentation is living documentation checked on November 10, 2026 — field names, enum values, and webhook behavior can change; verify against current Stripe and RevenueCat docs before building. The FTC click-to-cancel vacatur (July 8, 2025) predates this article; the remaining legal framework described is accurate as of that ruling's reporting.
- Scope: written for lifecycle, product, and subscription operations leads at mobile and consumer subscription businesses. It covers survey measurement design — questions, fields, event tracking, and validation — not B2B cancellation interviews, legal compliance advice, or a full cancel-flow UX spec. US legal references are planning context, not legal advice.
- Evidence: platform behavior (Stripe
cancellation_details, Stripe subscription events, RevenueCat event types, flows, and delivery timing) is verified against public vendor documentation. Survey-design practices (one required question, option counts, timing tradeoff, reason-to-action routing) come from linked practitioner and vendor guides. The Indie Hackers example is a public community post cited as illustration, not a That'sGonnaHelp customer result. - Synthetic examples: the rubric's worked rows are explicitly synthetic — a format model showing how fields should look populated, not measured data and not guarantees of what your data will show.
- Do not infer: option lists, validation checks, and thresholds here are starting points to calibrate on your own data, not guarantees. Demand estimates for this topic (a US provider estimate of roughly 10 monthly searches for the exact phrase, versus a broader ~320 for "cancellation survey") are directional planning inputs, not traffic promises.
Sources
- Stripe API reference: the Subscription object
- Stripe: using webhooks with subscriptions
- RevenueCat: webhook event types and fields
- RevenueCat: common webhook flows
- RevenueCat: webhooks overview
- Modem: route Stripe portal cancellation reasons to product
- AcornRetain: cancellation survey best practices
- ChurnIntel: cancellation survey questions for SaaS
- Swipe Again: exit survey question bank
- Startup Fortune: how to track churn reason codes
- Indie Hackers: my cancellation survey was lying to me
- Latham & Watkins: Eighth Circuit vacates FTC click-to-cancel rule
- Reuters: US click-to-cancel rule blocked by appeals court