Ask why customers cancel, and read the answer
Turn on Stripe's own cancellation survey first, because it is free and most people have it switched off. Then cover what it cannot see: cancellations made outside the billing portal. Ask one question, only when Stripe did not already get an answer, and sort every reply into a fixed set of categories so the numbers add up.
Your analytics tell you how many people left and when. Only the customer can tell you why, and cancellation is the one moment they will answer honestly.
Check what Stripe already does before you build anything
Stripe's billing portal can collect a cancellation reason, and the setting is often just switched off. Turn it on before you write a line of anything. What you get:
- A short list of preset reasons at the point of cancelling.
- Free text, but only under the Other option.
- An optional retention coupon offered before they confirm.
- The answer on the subscription as
cancellation_details, visible in the dashboard, in Sigma and on the webhook.
That covers a real slice of the problem. Three gaps remain:
- It only sees the portal. Cancel in your own app, email support, or run it through the API, and nobody is asked anything. For a lot of products that is most cancellations.
- The reasons are fixed. You cannot edit the preset list or collect free text on anything but Other, so the answers never quite match your product.
- Nobody opens the dashboard to read them. The data exists and goes uncounted, which is the same reason error alerts get muted.
So the job is not building a survey. It is covering the cancellations Stripe never asks about, and moving every answer somewhere you will actually see it.
What to ask, and when
One structured question, one optional open box. The structured answer is what makes the data countable six months later. The open box is where the useful detail turns up. Anyone leaving will abandon a longer form, so that is the whole survey.
Ask after the cancellation is done, not during it. A survey inside the cancel flow competes with the thing the person is trying to do, so they click whatever gets them out fastest. Send it once the cancellation is confirmed and say so in the first line. Fewer replies, truer ones.
Do not ask someone who already told you. Read the Stripe reason first and only send the email when it is empty:
const cd = subscription.cancellation_details || {};
const reason = [FEEDBACK[cd.feedback] || '', cd.comment || '']
.filter(Boolean).join('. ');
if (reason) -> skip the survey, go straight to categorising
else -> send one short email with one questionKeep the reason list short and stable. Price, missing feature, switched to something else, no longer needed, too hard to use, only needed it temporarily, something else. Seven options is enough, and changing them later breaks your own trend data.
Categories, not anecdotes
A single cancellation reason is a story. Twelve of them in the same bucket is a decision. That only works if the categories are fixed in advance and nothing is allowed to drift outside them, which is the one job worth handing to an AI step:
category one of a fixed enum, never free-form
preventable could you plausibly have changed this outcome?
save_worth_trying is a human reply likely to help right now?
summary under 25 words
suggested_action one sentence, aimed at you, not the customerTwo constraints matter more than the prompt around them. The model must never infer a reason nobody gave, so an empty or useless answer becomes other and says so. And if the model returns nothing usable, the raw reason still has to reach your inbox and your sheet. A churn record you lose is worse than one you never categorised.
preventable and save_worth_trying are deliberately separate. A customer whose company shut down is not preventable and not worth chasing. A pricing objection is both. A missing feature is preventable but often not worth a reply today. Splitting those two decides whether anyone does anything, which matters more than the category itself.
Who this is for, and what to change
Self-serve SaaS
The problem. Most cancellations happen in the portal, so Stripe covers them, and the reasons pile up unread in the dashboard while the same complaint recurs monthly.
What to change. You barely need the survey half. Route the reasons Stripe already has into a sheet and an email, and put the monthly category counts in front of whoever decides the roadmap.
Sales-led or higher-touch B2B
The problem. Almost nothing goes through the portal. Cancellations arrive by email to an account manager, get handled in a call, and the reason lives in one person's memory.
What to change. This is where the survey half earns its place, because Stripe sees none of it. Trigger on the subscription being cancelled however it happened, and send the ask from the account manager's address rather than a generic one.
Agencies and retainers
The problem. A retainer ending is rarely about price, but that is what gets said, because it is the easiest answer to give someone you like.
What to change. Delay the ask by a week or two. The honest answer to why a retainer ended tends to arrive after the awkwardness has passed, not on the day.
Ecommerce subscriptions
The problem. Cancellation and a failed payment look identical in your numbers, and they need opposite responses.
What to change. Keep this workflow strictly for voluntary cancellations and let a separate recovery flow handle failed cards. Mixing them produces a churn category called payment that hides two unrelated problems.
The shape of it
Stripe Trigger: customer.subscription.deleted
-> Stripe: look up the customer for their email
-> Code: read Stripe's own cancellation reason, if there is one
\
Form Trigger: the exit survey -> Code: same record shape
/
-> Set config
-> IF we already know why
yes -> Code + AI: categorise, judge, suggest
-> Gmail: email you, flagged by urgency
-> Sheets: append the churn row
no -> Gmail: one question, one linkTwo triggers in one workflow, meeting at a single node so both paths produce an identical record. This is a free n8n template you can import and point at your own Stripe account.
Exit survey questions, straight answers.
Does Stripe already collect a cancellation reason?
Yes, if you enable it. The billing portal can show a short list of preset reasons at cancellation, with free text only under the Other option, and it can offer a retention coupon. The answer lands on the subscription as cancellation_details and shows up in the dashboard, in Sigma and on the webhook. Check that before building anything, because a lot of what people build is already switched off in their settings.
So what does Stripe not cover?
Three things. It only sees cancellations made in the portal, so anything cancelled in your own app, by your support team or through the API is never asked. You cannot change the preset reasons or collect free text on anything except Other. And nobody opens the dashboard to read cancellation reasons, so the data sits there uncounted.
What should a cancellation survey ask?
One structured question and one optional open box. Someone who is leaving will not fill in ten fields. The structured question is what makes the answers countable later, and the open box is where the useful detail actually appears, so you want both but you want them short.
Should you ask before or after the cancellation goes through?
After, if you want an honest answer. Asking during the flow mixes the survey up with the act of leaving, and people click whatever gets them out fastest. Sending it once the cancellation is confirmed, and saying so in the first line, gets a slower reply but a truer one.
How do you stop asking someone who already told you?
Read the Stripe cancellation reason first and only send the survey when it is empty. Asking again after someone has explained themselves is how an exit email starts to feel like a retention trap, and it is the fastest way to get a reply you did not want.
Why categorise the answers instead of just reading them?
Because individual reasons are anecdotes and categories are evidence. Twelve people saying slightly different things about price is a pricing problem only once they are in the same bucket. Use a fixed set of categories and never let them drift, or six months of data will not add up.
Should you try to win the customer back?
Sometimes, and the survey is what tells you which. A missing feature or a pricing objection is worth a reply from a human. A business closing, or a genuinely temporary need, is not. Flagging that judgement automatically is more useful than the reason itself, because it decides whether anyone does anything.
Is an exit survey worth it for a small number of cancellations?
It is more useful when volume is low, not less. At ten cancellations a month you can read every one, and the categories still tell you where the pattern is. What you cannot do at that volume is spot the pattern from memory, which is what happens when the reasons stay in the dashboard.
Do you know why last month's churn left?
30 minutes, free. You leave with a plan for capturing every cancellation reason and turning them into categories you can act on, whether you build it or we do.
Keep reading.
Win back the churn nobody chose
Stripe retries the card but sends one generic notice. Branch the email on attempt number, keep the amount and payment link exact, and get told before a subscription is written off.
See every thread waiting on them
Has this thread had a reply is the wrong test and it hides your best leads. Compare sent against received per thread, then draft the nudge instead of sending it.
Stop finding out from the client
One error workflow for everything you run. Suppresses repeat alerts so a broken workflow sends one email not sixty, and sends the raw error even when the diagnosis fails.
Rather have it built for you?
Skip the build. A free 30-minute call and we set this up in your stack, live in a week or two.