Build a win-back flow for lapsed and churning subscribers using Apple StoreKit 2 win-back offers, RevenueCat campaigns, and segmented messaging. Use when the user says 'win-back', 'lapsed users', 'churn recovery', 'bring users back', 'expired trial', 'canceled subscription', 'reactivation', or 'promotional offer'.
Installs into .claude/skills of the current project.
Are you the author of Build Win Back Flow?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/khadinakbarlabs-build-win-back-flow)
---
name: "build-win-back-flow"
description: "Build a win-back flow for lapsed and churning subscribers using Apple StoreKit 2 win-back offers, RevenueCat campaigns, and segmented messaging. Use when the user says 'win-back', 'lapsed users', 'churn recovery', 'bring users back', 'expired trial', 'canceled subscription', 'reactivation', or 'promotional offer'."
---
# 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](https://www.revenuecat.com/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 to `BILLING_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
```text
winback_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.