Build a win-back flow
Win-back is recovering users who have lapsed — expired trials, canceled subscriptions, or gone inactive. It is one of the highest-ROI growth motions because reactivating a known user is far cheaper than acquiring a new one, and the data shows most churn is recoverable if you act on it.
Segment first — different lapsed states need different offers
Never treat all churn the same. The message and offer must match the segment:
| Segment | Definition | Best offer |
|---|---|---|
| Expired trial | Started a trial, never converted | Discounted first period; surface the value they didn't see; remove friction |
| Lapsed subscriber | Paid, then canceled or let it expire | Win-back offer (StoreKit 2); "we miss you" + what's new since they left |
| Inactive free user | Was active, hasn't opened in 10+ days | Re-engagement push/email with a specific reason to return; no paywall |
| Involuntary churn | Payment failed | Dunning/retry — see below; this is the most recoverable of all |
The Apple/Google win-back primitives
Apple StoreKit 2 win-back offers (iOS 18+)
Configured in App Store Connect, target subscribers whose subscription has expired (lapsed, not active). RevenueCat and Superwall both expose them. The user sees the offer on the App Store product page, in App Store emails, and you can present it in-app.
- Up to a limited number of win-back offers per subscription group.
- Discounted price for a set duration (e.g., 50% off for 3 months).
- Presented via StoreKit; RevenueCat surfaces them in the offerings/entitlements API.
tsx// RevenueCat: detect eligibility and present a win-back offering import Purchases from 'react-native-purchases'; const offerings = await Purchases.getOfferings(); const winback = offerings.all['winback_expired']; // offering configured for lapsed segment
Apple promotional offers
Target existing or previous subscribers with a discounted price for a set duration. More flexible than win-back offers (you control eligibility in your backend) and can target currently-active subscribers too.
Google Play
No equivalent first-class "win-back offer" primitive, but you can use promo codes and in-app messaging to the same effect. Subscribe to Real-Time Developer Notifications (RTDN) for SUBSCRIPTION_CANCELED, SUBSCRIPTION_EXPIRED, and SUBSCRIPTION_ON_HOLD to trigger flows. See app-store-server-notifications-android.
Involuntary churn is the easy win
30–50% of total churn is involuntary (failed payments), and it's the most recoverable. On Google Play, nearly a third of cancellations are involuntary billing failures vs just 14% on Apple. (RevenueCat State of Subscription Apps)
- Dunning emails ("your payment failed — update your card") + smart retries recover an estimated 50–60% of failed payments.
- Apple/Google handle billing retries on their side, but surface grace-period and account-on-hold states to the user in-app with a clear "update payment method" CTA.
- Subscribe to server notifications (
app-store-server-notifications,app-store-server-notifications-android) to react toBILLING_RETRY,GRACE_PERIOD,ACCOUNT_HOLD.
The win-back campaign structure (RevenueCat web checkout)
RevenueCat supports routing win-back offers through a web checkout to bypass the 15–30% store commission — present the lapsed user with a "Claim your offer" button (push, email, or in-app banner), route to a Stripe-hosted web checkout, then restore access in-app immediately on success. This works in regions/states where alternative billing is permitted; respect current Apple/Google policy and regional law before relying on it.
Timing
- Expired trial: wait 1–3 days, then a single "we saved your progress" + discounted offer. Don't spam.
- Lapsed subscriber: at genuine expiry (when premium access actually ends, not when they hit cancel), then again at 7/30/60 days with escalating personalization.
- Inactive free user: 3 days silent → gentle nudge; 7 days → direct message + incentive; 14 days → escalated offer. (See
design-lifecycle-messaging.)
The cancel-vs-expire distinction (important)
A user who cancels (turns off renewal) but is still in their paid period is not yet lapsed — don't fire a win-back. Wait until premium access actually expires. Firing win-back at cancel-time cannibalizes the subscription they're still paying for. Use server notifications to distinguish SUBSCRIPTION_CANCELED (still active until period end) from SUBSCRIPTION_EXPIRED (lapsed).
Messaging that converts
- Name the specific value they're missing. "Your 47-day streak is frozen. Come back before you lose it." Not "We miss you!"
- Surface what's new since they left. Features, content, improvements. Give them a reason beyond the discount.
- Make the offer time-limited and specific. "50% off your next 3 months — claim by Friday." Not "a discount."
- One clear CTA. Claim offer / update payment / come back. No menus.
Measure
textwinback_segment_assigned {segment: expired_trial|lapsed|inactive|involuntary} winback_message_sent {channel: push|email|in_app|web} winback_offer_presented winback_offer_claimed winback_reactivated // returned to active subscription or active use winback_revenue // attributed revenue from the campaign
Track reactivation rate and attributed revenue per segment. Win-back ROI is typically far higher than new-user acquisition ROI.
What not to do
- Don't fire win-back at cancel-time. Wait for genuine expiry.
- Don't spam. One well-timed, personalized message beats five generic ones.
- Don't offer >50% off routinely — trains users to cancel-for-deals. Cap routine win-back at 20–30% for 2–3 months. See
build-cancellation-flow. - Don't ignore involuntary churn — it's the biggest, cheapest recovery pool and the most neglected.
Pair with
integrate-revenuecat-rn— win-back offerings, web checkout, server notifications.app-store-server-notifications/app-store-server-notifications-android— the lifecycle signals that trigger win-back.build-cancellation-flow— intercept active churn before it becomes win-back territory.design-lifecycle-messaging— the cross-channel cadence this lives in.pricing-strategy— discount guardrails.


