How to Validate iOS Receipts Server-Side and Handle Android App Subscription Renewals: Complete Backend Guide
A comprehensive developer guide to building a robust in-app purchase backend. Learn how to verify Apple StoreKit 2 JWS transactions, process Google Play Billing RTDN webhooks, handle grace periods, and prevent billing desynchronization.
If you are building a commercial mobile or cross-platform application, handling payments is not merely an in-app UI prompt. It is a distributed systems problem spanning Apple's App Store Server API, Google Play Developer API, edge webhooks, and your core user database.
Relying on client-side receipt validation in 2026 is an immediate security vulnerability. Jailbroken devices, modified APKs, and man-in-the-middle proxy tools can easily spoof local receipt payloads. To safeguard your revenue and keep subscription access synchronized across iOS, Android, and Web, server-side receipt validation is mandatory.
In this architectural guide, we break down step-by-step how to validate Apple StoreKit 2 receipts, process Google Play Real-Time Developer Notifications (RTDN), manage Android subscription renewals and grace periods, and architect an enterprise in-app purchase backend.
1. The Mechanics of Server-Side Receipt Validation
Modern receipt validation operates on cryptographically signed JSON Web Signatures (JWS) rather than legacy binary payloads. When a user completes a purchase on mobile:
- The native client SDK receives a signed transaction token from StoreKit 2 or Google Play Billing.
- The client forwards this signed token to your backend API or edge gateway.
- Your backend validates the cryptographic signature using the store's public root certificate or credentials.
- Your backend queries or updates your database, granting an active entitlement.
- Asynchronous server notifications (webhooks) update the entitlement on auto-renewal, cancellation, or refund.
2. Validating Apple StoreKit 2 Receipts Server-Side
Apple deprecated the legacy /verifyReceipt endpoint in favor of StoreKit 2 and the App Store Server API. StoreKit 2 transactions are formatted as signed JWS objects.
When decoding a StoreKit 2 transaction payload (signedTransactionInfo), your server must verify the following fields:
originalTransactionId: The immutable root identifier for the subscription lifecycle.productId: Your defined in-app product identifier.expiresDate: Unix epoch timestamp in milliseconds when the subscription period ends.revocationDate: Populated if the subscription was revoked or refunded by Apple Customer Care.inAppOwnershipType: Differentiates between direct purchases and Apple Family Sharing.
To handle renewals automatically, you must configure App Store Server Notifications V2 (ASSN V2). Key event types include:
DID_RENEW: The subscription renewed successfully for the next billing cycle.DID_FAIL_TO_RENEW: The billing attempt failed. The user enters a billing retry or grace period state.GRACE_PERIOD_EXPIRED: Payment could not be recovered. Revoke access immediately.REFUND: Apple granted a refund. Revoke the active entitlement.SUBSCRIBED: Initial conversion from trial or promotional offer.
3. Handling Google Play Billing Renewals & RTDN
Google Play uses Google Cloud Pub/Sub to deliver Real-Time Developer Notifications (RTDN). Unlike Apple, Google does not push the full decoded receipt in the notification body; it delivers a purchaseToken and notificationType.
Your in-app purchase backend must receive the Pub/Sub message, acknowledge it, and call the Google Play Developer API (purchases.subscriptionsv2.get) using the purchaseToken to fetch the true state.
Common Google Play subscription states include:
- SUBSCRIPTION_RECOVERED (Type 1): Payment was successfully charged after a previous decline.
- SUBSCRIPTION_RENEWED (Type 2): Standard auto-renewal. Extend entitlement expiration.
- SUBSCRIPTION_CANCELED (Type 3): User turned off auto-renew. Access remains valid until
expiryTime. - SUBSCRIPTION_ON_HOLD (Type 5): User in Account Hold. Pause premium access while Google retries payment.
- SUBSCRIPTION_IN_GRACE_PERIOD (Type 6): Payment declined, but Grace Period is active. Keep features unlocked while prompting for payment update.
- SUBSCRIPTION_PAUSED (Type 10): User paused subscription. Temporarily suspend entitlement.
4. The Pain of DIY Maintenance vs. Orca Unified Infrastructure
Maintaining custom StoreKit 2 and Google Play Billing servers requires continuous engineering maintenance: handling certificate rotations, clock skews, Cloud Pub/Sub dead-letter queues, and database locking for race conditions.
Orca eliminates 100% of this complexity. With Orca's unified mobile subscription infrastructure:
- Server-side receipt validation executes at global edge nodes in under 14ms.
- StoreKit 2, Google Play Billing 7+, and Stripe webhooks are normalized into a single real-time event stream.
- Zero gross revenue cuts: enjoy flat-rate predictable pricing instead of paying 1% of your MRR.
Enjoyed this article?
Subscribe to our newsletter to receive the latest updates, tutorials, and insights directly in your inbox.
No spam, unsubscribe anytime.