You launched your subscription app, users are converting, and revenue is climbing. Then, six months in, the growth curve flattens. Not because acquisition stopped — but because you're filling a leaky bucket. Churn is silently eating your lifetime value faster than new subscribers can replace it.
This playbook is for iOS and Android developers who already have a live subscription app and want a systematic, data-first approach to diagnosing and fixing that leak. We'll use RevenueCat's analytics, webhooks, SDK, and offer tooling — with concrete code you can ship today.
1. Why Churn Is the Silent Killer of Subscription LTV
Before you optimize anything, understand what's at stake mathematically. The RevenueCat Prediction Explorer forecasts up to 24-month LTV per cohort. Consider a typical scenario:
- Trial conversion rate: 40%
- Predicted 24-month LTV per converting subscriber: $100
- Expected value per trial start: 0.40 × $100 = $40
Now imagine you reduce churn enough to lift that predicted LTV from $100 to $130. The same 40% conversion rate now yields $52 per trial start — a 30% revenue gain without touching acquisition. Conversely, if churn quietly degrades that LTV from $100 to $70, your expected value per trial drops to $28. You need 43% more conversions just to stand still.
The Prediction Explorer makes this concrete: it builds survival curves from RevenueCat's dataset of over 450 million subscriptions and forecasts the LTV of any cohort you define — new customers, initial conversions, or new paying customers — up to 24 months out. Source
2. Reading Your Churn Signal
You can't fix what you can't measure. RevenueCat provides three complementary charts that together give you a complete picture of churn.
The Churn Chart — Your Headline Rate
The Churn Chart measures the percentage of active subscriptions lost in a given period that have not yet resubscribed. Source
Formula:
(Churned Actives / Actives) × 100 = Churn Rate
Where: - Actives = paid, unexpired subscriptions at the start of the period - Churned Actives = paid subscriptions that expired within the period, minus those that resubscribed
A few non-obvious behaviors to understand:
- A cancelled subscription is NOT counted as churned until it actually expires. Cancellation ≠ churn.
- Churn can go negative — if billing retry resubscriptions in a period outnumber newly expired subs, your effective churn for that period is negative, meaning your base grew even without new sign-ups.
- Segment by country and product duration to find your strongest product-market-fit pockets. A segment with 2% monthly churn vs. your app-wide 8% tells you exactly which audience to double down on.
The Subscription Retention Chart — Cohort View
Where the Churn Chart gives you a rolling aggregate, the Subscription Retention Chart shows cohort-level survival curves: of all subscriptions that started in, say, March, how many were still renewing at Month 1, Month 3, Month 6?
Formula:
Retained Subscriptions at period X / Total Subscriptions in cohort = Retention Rate
Retention is always relative to the initial cohort size, not the prior period — so Month 3 retention of 60% means 60% of the original cohort, not 60% of Month 2 survivors. This distinction matters when building LTV models.
Use this chart to segment by product duration (monthly vs. annual) and spot the critical drop-off point. If 40% of monthly subscribers churn at Month 1, that's a value-delivery problem in your onboarding, not a pricing problem.
Note (July 2024 update): The chart now excludes grace period transactions from the retained count for Play Store and Stripe subscriptions, so a subscription that enters a billing grace period but ultimately doesn't recover is correctly counted as churned. Source
The Active Subscriptions Movement Chart — The "Why" Behind Trends
When your Active Subscriptions count changes week-over-week, the Active Subscriptions Movement Chart breaks down why:
Formula:
New Actives − Churned Actives = Active Subscription Movement
Critically, Churned Actives in this chart subtracts resubscriptions — so if billing retry recovers a lapsed subscriber mid-period, it reduces your apparent churn for that period. This is the chart to monitor when you've implemented billing grace periods or are running win-back campaigns, because successful recoveries show up here as negative churn entries.
3. Involuntary Churn — The Low-Hanging Fruit
The most immediately actionable form of churn is involuntary churn: subscribers who didn't intend to cancel but lost access because of a failed payment. Unlike voluntary churn, which requires you to convince someone your app is worth keeping, involuntary churn only requires you to help someone update their credit card.
Enable Billing Grace Periods
Billing Grace Periods give the store time to retry a failed payment while the subscriber retains access. They're optional but highly recommended for App Store, Google Play, and RevenueCat Web Billing. Source
| Store | Required? | Duration |
|---|---|---|
| App Store | Optional | Customizable in App Store Connect |
| Google Play | Optional | Customizable in Play Console |
| Web Billing | Optional | Customizable |
| Amazon | ❌ Not supported | N/A |
The Webhook Lifecycle for a Billing Failure
When a payment fails, RevenueCat fires events in this sequence: Source
BILLING_ISSUE— payment failed; subscriber enters grace period (if enabled). Watch forgrace_period_expiration_at_msin this event's payload.CANCELLATIONwithcancel_reason: BILLING_ERROR— subscription is now past-due (but still active if in grace period).EXPIRATIONwithexpiration_reason: BILLING_ERROR— grace period ended without recovery; access is revoked.
A RENEWAL event before step 3 means the billing retry succeeded — reset your state.
Important: RevenueCat sends only one
BILLING_ISSUEevent per billing failure episode. Additional retry failures don't generate additional events. Source
Detecting Billing Issues in the SDK
Use getCustomerInfo() on app foreground and check grace_period_expires_date on the relevant subscription to determine whether a user is in a payment recovery state. Surface an in-app prompt to update their payment method immediately — don't wait for a push notification.
Swift:
import RevenueCat
func checkForBillingIssue() async {
do {
let customerInfo = try await Purchases.shared.customerInfo()
// Check if the "pro" entitlement is active but the subscription has a billing issue
if let proEntitlement = customerInfo.entitlements["pro"],
proEntitlement.isActive {
// Find the subscription with a grace period expiration date
for (_, subscription) in customerInfo.subscriptions {
if let gracePeriodDate = subscription.gracePeriodExpiresDate,
gracePeriodDate > Date() {
// User is in a grace period — show payment update prompt
showPaymentUpdatePrompt(expiresOn: gracePeriodDate)
return
}
}
}
} catch {
print("Failed to fetch customer info: \(error)")
}
}
func showPaymentUpdatePrompt(expiresOn date: Date) {
let formatter = DateFormatter()
formatter.dateStyle = .medium
let message = "Update your payment method by \(formatter.string(from: date)) to keep your subscription active."
// Present alert or custom UI
}
Kotlin (Android):
import com.revenuecat.purchases.Purchases
import com.revenuecat.purchases.getCustomerInfoWith
fun checkForBillingIssue() {
Purchases.sharedInstance.getCustomerInfoWith(
onError = { error -> Log.e("RC", "Error fetching customer info: $error") },
onSuccess = { customerInfo ->
val proEntitlement = customerInfo.entitlements["pro"]
if (proEntitlement?.isActive == true) {
// Check subscriptions for grace period
customerInfo.subscriptions.values.forEach { subscription ->
subscription.gracePeriodExpiresDate?.let { graceDate ->
if (graceDate.after(Date())) {
// User is in grace period — prompt payment update
showPaymentUpdateBanner(graceDate)
}
}
}
}
}
)
}
iOS 16.4+ bonus: On iOS 16.4 and later, the system will automatically display a sheet prompting payment method updates when a billing issue occurs — at no extra code cost. You can test this in Sandbox via
Settings → App Store → Sandbox Account → Manage. Source
4. Win-Back and Promotional Offers
Once a subscriber has fully churned, a discount is your lever. RevenueCat supports multiple offer mechanisms across platforms.
iOS 18 Win-Back Offers (Lapsed Subscribers)
Apple introduced Win-Back Offers with iOS 18, allowing you to present custom pricing or a free trial to subscribers who have previously cancelled. They are only available on iOS 18.0+ and require an In-App Purchase Key to be configured in RevenueCat. Source
Win-Back Offers use Streamlined Purchasing by default — the subscriber can complete the entire redemption flow inside the App Store without returning to your app. This means the lowest friction path for recovery.
To handle a win-back redemption in your app, listen for CustomerInfo updates via the delegate:
// In your AppDelegate or main entry point
Purchases.shared.delegate = self
extension AppDelegate: PurchasesDelegate {
func purchases(_ purchases: Purchases, receivedUpdated customerInfo: CustomerInfo) {
// Win-back redemption completes in the App Store;
// this delegate fires when entitlement becomes active again.
if customerInfo.entitlements["pro"]?.isActive == true {
unlockProFeatures()
}
}
}
Promotional Offers — At-Risk and Lapsed Subscribers (iOS)
Promotional Offers are available for both existing and lapsed subscribers on iOS 12.2+. Unlike Win-Back Offers, these are presented inside your app and require you to fetch and apply the discount manually. Source
The recommended delivery mechanism is Customer Center — RevenueCat's pre-built cancellation and retention flow. By configuring a Cancellation Retention Discount in the Customer Center's Offers tab, the SDK automatically surfaces the right promotional offer when a subscriber initiates a cancellation. You can also configure a Refund Retention Discount for refund requests. Source
To present a promotional offer manually (e.g., on a custom "stay" screen):
// 1. Get the product and the discount you want to offer
let offerings = try await Purchases.shared.offerings()
guard let package = offerings.current?.annual,
let discount = package.storeProduct.discounts.first(where: {
$0.offerIdentifier == "annual_winback_50off"
}) else { return }
// 2. Fetch a signed promotional offer from RevenueCat
let promotionalOffer = try await Purchases.shared.promotionalOffer(
forProductDiscount: discount,
product: package.storeProduct
)
// 3. Purchase with the discount applied
let result = try await Purchases.shared.purchase(
package: package,
promotionalOffer: promotionalOffer
)
if !result.userCancelled {
print("Win-back successful: \(result.customerInfo)")
}
Google Play Promotional Offers (At-Risk Subscribers)
On Android, Google Play supports promotional offers with Developer Determined eligibility — you decide who sees them. For Customer Center integration, tag your offer with both rc-customer-center and rc-ignore-offer in Play Console. The first makes the offer exclusive to Customer Center; the second prevents it from being auto-applied for older SDK users. Source
// Fetch the offering and find the discounted offer by tag
Purchases.sharedInstance.getOfferingsWith(
onError = { error -> Log.e("RC", "$error") },
onSuccess = { offerings ->
val annualPackage = offerings.current?.annual
// Developer-determined offers always appear in subscriptionOptions
val retentionOffer = annualPackage?.storeProduct
?.subscriptionOptions
?.firstOrNull { option ->
option.tags.contains("rc-customer-center")
}
retentionOffer?.let { offer ->
// Present the offer on your retention screen
showRetentionOffer(offer)
}
}
)
Google Play offer changes take up to 24 hours to reflect on device after updating in Play Console. Clear the Play Store app cache to speed up testing. Source
5. Using Experiments to Test Retention-Improving Changes
Once you've addressed involuntary churn and have win-back offers live, the next layer is optimizing the subscription experience itself. RevenueCat Experiments let you A/B test Offerings — no app update required if your paywall already reads the current Offering. Source
What to Test for Retention
| Test | Hypothesis | Key Metric |
|---|---|---|
| Monthly vs. Annual prominence | Promoting annual plans reduces long-term churn by locking in committed subscribers | Realized LTV per paying customer |
| 7-day trial vs. 14-day trial | Longer trials produce more engaged converters with lower early churn | Trial conversion rate + Month 3 retention |
| $9.99 vs. $14.99 monthly | Higher price = fewer converts, but self-selects a higher-intent cohort with better retention | Realized LTV per customer |
| Paywall copy emphasizing value | Explicit benefit callouts reduce "forgot I subscribed" cancellation | Month 1 retention |
You can test up to 4 variants simultaneously in a single experiment, which lets you evaluate multiple price points or trial lengths in parallel rather than sequentially. Source
The LTV Tradeoff You Must Understand
When running paywall experiments for retention, watch for this counter-intuitive pattern: a more prominent annual plan may reduce initial conversion rate while significantly increasing Realized LTV per paying customer. Fewer people subscribe upfront, but those who do are more committed and churn far less.
The Experiments Results page breaks this down explicitly in its product-level view:
"A more prominent yearly subscription may decrease initial conversion rate relative to a more prominent monthly option, but those fewer conversions may produce more Realized LTV per paying customer." Source
Don't declare a winner based on conversion rate alone. Let the Realized LTV per customer metric mature — for annual plan tests, you may need 12+ months of data for the LTV to fully reflect the retention difference.
Pro tip: When you stop an experiment, RevenueCat continues updating results for 400 days to capture subscription events and LTV maturation. Source
6. Measuring the Dollar Impact with the Prediction Explorer
Every churn-reduction effort should be measured in dollars, not just percentage points. The Prediction Explorer is the right tool for this.
Comparing Cohorts Before and After a Change
The Prediction Explorer supports three cohort definitions — New Customers, Initial Conversions, and New Paying Customers — each cohorted independently. Use New Paying Customers to compare the LTV trajectory of subscribers acquired before vs. after a retention improvement. Source
Workflow: 1. Ship your retention change (grace period enabled, new win-back offer, annual plan promotion) 2. Wait 60–90 days for sufficient cohort data to accumulate 3. Open Prediction Explorer → select New Paying Customers 4. Compare the pre-change cohort (e.g., Q3 monthly cohort) vs. the post-change cohort (e.g., Q4 monthly cohort) 5. Look at predicted 24-month LTV: the delta is your dollar-per-customer improvement
As of January 2026, the Prediction Explorer also weights trial subscriptions by their historical conversion probability, so cohorts that include trial starts give you a more complete revenue picture without waiting for all trials to convert. Source
How the LTV Prediction Works
RevenueCat fits subscription data to statistical survival curves (e.g., exponential distribution), updated daily, using data from its dataset of 450M+ subscriptions. For each active paid subscription, it predicts renewal probabilities at each future period and sums them to produce LTV. Predictions modify the base survival curve based on the actual behavior of each specific subscription (number of renewals, cancellation state, etc.). Source
This means a cohort where you've successfully recovered more billing failures will show a higher-angled survival curve and a higher predicted LTV — the model picks up the improved retention signal within a few weeks.
Putting It All Together: A Churn Reduction Checklist
Work through these in order of implementation effort vs. impact:
Week 1 (Zero-Code):
- [ ] Enable Billing Grace Periods in App Store Connect and Google Play Console
- [ ] Configure Cancellation Retention Discount in RevenueCat Customer Center (Offers tab) for both iOS and Android
- [ ] Confirm BILLING_ISSUE webhook is wired to your backend or analytics tool
Week 2–3 (Low-Code):
- [ ] Add in-app billing issue detection using grace_period_expires_date (Swift/Kotlin snippet above)
- [ ] Configure a Win-Back Offer in App Store Connect (iOS 18+)
- [ ] Create Developer Determined offers in Google Play Console with rc-customer-center + rc-ignore-offer tags
Month 2+ (Experiment-Driven): - [ ] Run an annual vs. monthly prominence experiment using RevenueCat Experiments - [ ] Use Prediction Explorer to establish your baseline cohort LTV before changes - [ ] Re-measure after 60–90 days; quantify dollar impact per paying customer
Churn isn't a single thing to fix — it's a system to continuously improve. With RevenueCat's charts surfacing the signal, webhooks enabling real-time response, offers providing the levers, and the Prediction Explorer quantifying the outcomes, you have every tool you need to close the loop.
Sources
- Churn Chart
- Subscription Retention Chart
- Active Subscriptions Movement Chart
- Prediction Explorer
- Billing Issues & Grace Periods
- Webhook Event Types and Fields
- iOS Subscription Offers (Promotional & Win-Back)
- Google Play Offers
- Configuring Apple Promotional Offers for Customer Center
- Configuring Google Promotional Offers for Customer Center
- Getting Started with Experiments
- Creating Offerings to Test
- Experiments Results
- Getting Subscription Status (CustomerInfo)