In mobile subscription businesses, customer churn rarely manifests as an immediate, clean database status update. Instead, churn frequently enters a prolonged state of uncertainty created by platform billing retry cycles, temporary credit card declines, grace periods, and uncollected server notifications.
When analytics pipelines fail to account for these transition states, cohort models will overstate active subscriber numbers by up to 12% across multi-month windows.
The Anatomy of the StoreKit 2 & Play Billing Retry Cycle
When an active subscriber’s billing cycle renews but the underlying payment method fails, neither Apple nor Google immediately revokes subscription entitlement.
Billing Grace Period (Up to 16 Days):
- If the developer has enabled the App Store or Google Play Billing Grace Period, the user retains full premium access while the platform automatically retries the card.
- Telemetry Trap: If your database updates the subscription expiry timestamp based solely on expected renewal dates, this user is marked as “retained” even if the payment is never recovered.
Billing Retry State (Up to 60 Days):
- After the grace period expires without successful payment, entitlement is cut off, but the platform continues periodic background billing retries for up to two months.
- Telemetry Trap: If the user is categorized as “churned” and subsequently successfully billed during retry 45 days later, their revenue will often be attributed to a new cohort instead of their original acquisition date.
Essential Server Notification Event Types
To maintain exact cohort ledger synchronization, your backend webhook ingestion must listen for and process the following specific events:
Apple App Store Server Notifications V2
DID_FAIL_TO_RENEWwith subtypeGRACE_PERIOD: Payment failed; enter grace period status.GRACE_PERIOD_EXPIRED: Payment unrecovered; revoke entitlement and log silent billing churn.DID_RENEWwith subtypeBILLING_RECOVERY: Payment recovered after failure; record recovery revenue back to the original cohort ledger.EXPIREDwith subtypeVOLUNTARY: Subscriber explicitly disabled auto-renew.
Google Play Real-Time Developer Notifications (RTDN)
SUBSCRIPTION_IN_GRACE_PERIOD(Notification Type 6)SUBSCRIPTION_ON_HOLD(Notification Type 5)SUBSCRIPTION_RECOVERED(Notification Type 7)SUBSCRIPTION_CANCELED(Notification Type 3)
SQL Ledger Reconciliation Blueprint
To audit whether your data warehouse suffers from phantom active subscribers, execute an integrity query comparing your internal active subscriber table against the most recent verified store transaction receipts:
SELECT
c.acquisition_cohort_month,
COUNT(DISTINCT c.user_id) AS internal_reported_active,
COUNT(DISTINCT s.verified_user_id) AS store_receipt_active,
(COUNT(DISTINCT c.user_id) - COUNT(DISTINCT s.verified_user_id)) AS phantom_active_discrepancy
FROM analytics.cohort_subscribers c
LEFT JOIN store_backend.verified_receipts s
ON c.user_id = s.user_id
AND s.status = 'ACTIVE_PAID'
AND s.expiry_timestamp > CURRENT_TIMESTAMP()
WHERE c.cohort_status = 'ACTIVE'
GROUP BY 1
ORDER BY 1 DESC;
If phantom_active_discrepancy exceeds 2% in any historical cohort, your webhook ingestion pipeline requires immediate deduplication and state-machine calibration.