rshankras/win-back-offers
Generates the complete win-back offer flow for churned subscribers — StoreKit Message API handling, eligibility verification, offer sheet presentation, and analytics. Use when implementing win-back campaigns or re-engagement for lapsed subscribers.
npx skills add https://github.com/rshankras/claude-code-apple-skills --skill win-back-offers
Generate a complete win-back flow for recovering churned subscribers using StoreKit 2's win-back offer APIs (iOS 18+), Message API handling, and fallback promotional offers for older OS versions.
Use this skill when the user:
Win-back offers involve meaningful engineering and ASC configuration. Check these thresholds before proceeding:
| Churned Subscribers | Recommendation |
|----|---|
| Under 50 | Not worth the setup effort. Use personal email outreach instead — write individual messages to churned users. Focus energy on preventing churn (improve onboarding, engagement). |
| 50-500 | Implement basic win-back via promotional offers (simpler than full win-back flow). Use offer codes distributed via email. |
| 500+ | Full win-back implementation justified. Native iOS 18+ win-back offers, Message API handling, automated pipeline. |
If you don't know your churn numbers: Check App Store Connect > Subscriptions > Retention metrics. If your total subscriber base is under 200, you likely don't have enough churn volume to justify this skill yet.
subscription-offers skill was already usedGlob: **/*WinBack*.swift, **/*Reengag*.swift
Grep: "winBackOffers" or "Message.messages" or "presentWinBackOffer"
Ask user via AskUserQuestion:
Read templates.md for all win-back implementation code.
- If Sources/Store/ exists → Sources/Store/WinBack/
- If Store/ exists → Store/WinBack/
- Otherwise → WinBack/
Store/WinBack/
├── WinBackOfferManager.swift # Core win-back logic
├── WinBackMessageHandler.swift # Message API handling
├── WinBackEligibility.swift # Churn detection
├── WinBackOfferView.swift # Custom offer UI
├── WinBackAnalytics.swift # Funnel tracking (optional)
└── WinBackFallback.swift # iOS < 18 fallback (optional)
App Entry Point — Message Handling:
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.storeMessagesTask { message in
// Handle win-back messages from App Store
await WinBackMessageHandler.shared.handle(message)
}
}
}
}
Check Win-Back Eligibility:
let manager = WinBackOfferManager()
let winBackOffer = await manager.checkWinBackAvailability(
productID: "com.app.subscription.monthly"
)
if let offer = winBackOffer {
// Show win-back UI
showWinBackSheet(offer: offer)
}
Present Win-Back Offer:
// Native (iOS 18+) — raw StoreKit path
if case .verified(let renewalInfo) = status.renewalInfo,
let bestID = renewalInfo.eligibleWinBackOfferIDs.first, // first ID = best offer
let offer = product.subscription?.winBackOffers.first(where: { $0.id == bestID }) {
let result = try await product.purchase(options: [.winBackOffer(offer)])
}
// With StoreKit views, choose the merchandised offer instead
SubscriptionStoreView(groupID: groupID)
.preferredSubscriptionOffer { product, subscription, offers in
offers.first { $0.type == .winBack }
}
Hide win-back merchandising from anyone still entitled with auto-renew on — the offer is for lapsed subscribers only.
The App Store does the distribution — eligible lapsed subscribers see your win-back offer on:
Streamlined purchasing is ON by default: users can redeem on the App Store before ever reopening your app, so the transaction arrives via Transaction.updates — listen from launch. If you disable streamlined purchasing, implement PurchaseIntent to complete the purchase in-app.
Config gotchas:
> The App Store Connect tier is open to all developers (WWDC26); the real-time server API is gated behind an access-request form + a mandatory sandbox performance test. (Per the session "Explore Retention Messaging in App Store Connect".)
The App Store presents your message on the cancel confirmation page — the last screen before a subscriber churns. It complements win-back rather than replacing it:
| Mechanism | Fires |
|-----------|-------|
| Retention messaging | At cancel — before the user churns |
| Win-back offer | After lapse — once the subscription has expired |
Configure both — they cover opposite ends of the churn window.
Mechanics (from Apple's session):
generators/subscription-offers — All offer types including win-backgenerators/paywall-generator — Full paywall implementationapp-store/marketing-strategy — Win-back campaign planningTake rshankras/win-back-offers from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.