Back to Blog
Technical

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.

11 min read

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:

  1. The native client SDK receives a signed transaction token from StoreKit 2 or Google Play Billing.
  2. The client forwards this signed token to your backend API or edge gateway.
  3. Your backend validates the cryptographic signature using the store's public root certificate or credentials.
  4. Your backend queries or updates your database, granting an active entitlement.
  5. 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.