Build a cancellation flow
The screen a user sees when they hit "cancel subscription" is one of the highest-leverage revenue surfaces in a subscription app. A well-designed flow saves 10–35% of cancellations (offer acceptance 15–25%, pause reactivation 60–80%); even a simple survey + one offer saves 10–15% over an instant cancel. This skill designs it.
The canonical structure
textTrigger (user taps Cancel) → Survey (why are you leaving?) → Reason-matched save offer → Accept (saved) | Defer (pause) | Confirm cancel (graceful exit) → Post-cancel: win-back eligibility + email capture
The exit survey
5–8 reason options + free text. Framing matters: "Help us improve" beats "Why are you leaving?" — it reduces defensiveness and raises completion.
Typical reasons (match these to the product):
- Too expensive
- Not using enough
- Missing a feature
- Found an alternative
- Technical issues / bugs
- Just needed it temporarily
- Hard to use
- Other (free text)
Capture the reason in your analytics (instrument-growth-funnel) tied to the user. The reason is also the input to the save offer and to product roadmap prioritization.
Reason-matched save offers
Don't show every canceler the same offer. Match the offer to the reason:
| Cancel reason | Primary offer | Fallback |
|---|---|---|
| Too expensive | 20–30% off for 2–3 months | Downgrade to a cheaper tier |
| Not using enough | Pause 1–3 months (keeps access, stops billing) | Free onboarding / usage nudge |
| Missing feature | Roadmap + timeline for the requested feature | A workaround or alternative path |
| Technical issues | Escalate to support immediately; credit + priority fix | Free month while they wait |
| Found alternative | Differentiated value recap + offer | Graceful exit (don't badger) |
| Temporary use | Pause or annual at a discount for next cycle | Graceful exit |
Discount guardrails (avoid training cancel-for-deals)
- Sweet spot: 20–30% off for 2–3 months. Enough to matter, short enough to not devalue.
- Avoid routine >50% off. Trains users to cancel expecting a deal; they'll repeat the cycle.
- Show the dollar amount, not just the percentage. "$4 off for 3 months" lands better than "25% off."
- Time-limit the offer. "Claim by [date]" — but only a real deadline, not a fake countdown (Apple rejects fake urgency).
- One offer per cancel session. Don't cascade discounts if they reject the first.
Pause as an alternative to cancel
A "pause for 1–3 months" option (billing stops, access preserved or reduced) reactivates at 60–80% vs much lower reactivation after a full cancel. Where the billing systems support it, offer pause prominently. StoreKit 2 doesn't have a native "pause," but you can implement it via a free-trial/promotional-offer bridge or by letting the subscription lapse and re-engaging. Google Play supports pausing natively in some configurations.
Graceful exit (non-negotiable for your rating)
Keep "Continue cancelling" visible and one tap away at every step. No dark patterns. The FTC's Click-to-Cancel rule requires cancellation to be at least as easy as sign-up. A user who feels trapped by a cancellation flow leaves a 1-star review. See build-review-routing — the same unhappy-user principle applies.
After confirming cancel:
- Capture an email (if not already on file) for win-back. See
build-win-back-flow. - Keep premium access until the period ends (they paid for it).
- Subscribe to server notifications (
SUBSCRIPTION_CANCELED) to distinguish cancel-but-active from genuinely lapsed.
Implementation (RevenueCat)
RevenueCat exposes customer info and entitlement state. Build the survey + offer UI yourself; present the offer as a StoreKit promotional offer or a RevenueCat-managed discount:
tsximport Purchases from 'react-native-purchases'; // On cancel-reason selected, present the matched offer async function presentSaveOffer(reason: CancelReason) { const offer = matchOfferToReason(reason); // your mapping // present the offer as a promotional offer / discounted package const offerings = await Purchases.getOfferings(); const pkg = offerings.all?.[`save_${reason}`]; // show pkg in a save-offer sheet; if accepted: // const { customerInfo } = await Purchases.purchasePackage(pkg); }
Track the funnel: cancel_started, cancel_survey_completed {reason}, save_offer_presented {type}, save_offer_accepted, cancel_confirmed, cancel_paused.
What not to do
- No multi-step mazes that make cancel hard. FTC Click-to-Cancel + App Review both penalize this.
- No fake countdowns on the save offer.
- No guilt-trip copy ("Are you sure you want to give up on your goals?"). Confirmshaming is a dark pattern and a rating killer.
- No repeated discount escalation if they reject the first offer.
- No blocking the cancel behind a support chat (chat is fine as one option, not a gate).
Pair with
integrate-revenuecat-rn— entitlement state and promotional offers.app-store-server-notifications/app-store-server-notifications-android— cancel vs lapsed signals.build-win-back-flow— the post-cancel recovery arm.pricing-strategy— discount guardrails.instrument-growth-funnel— measuring save rate by reason.


