Subscription apps rarely live on a single platform. A user might subscribe on their iPhone during their morning commute, then open your Android tablet app at home expecting full access. This expectation is intuitive from the user’s perspective, they paid for a subscription, so it should work everywhere. But from a developer’s perspective, making this work is one of the hardest problems in subscription infrastructure. Google Play Billing and Apple’s StoreKit are entirely separate systems with different receipt formats, different verification mechanisms, different notification systems, and fundamentally different assumptions about how purchases are represented. There is no built in interoperability between them.
In this article, you’ll explore why cross platform subscription state is so difficult to implement, examine the fundamental incompatibilities between Google Play Billing and StoreKit, walk through what it takes to build cross platform entitlement sync from scratch, and see how RevenueCat’s identity system provides a natural solution that dramatically reduces the engineering effort required, especially for small teams and indie developers.
The fundamental problem: One user, two ecosystems
Consider a fitness app with a “Premium” subscription. A user subscribes through the App Store on their iPhone. A week later, they buy an Android tablet and download your app. They log in with the same account and expect to see their premium features. What actually happens?
Without cross platform infrastructure, the Android app has no idea this user has an active subscription. Google Play Billing only knows about purchases made through Google Play. The App Store receipt sitting on Apple’s servers is invisible to the Android app. The user sees a paywall asking them to subscribe again, even though they are already paying.
This is not a bug. It is the expected behavior. Each billing system operates in isolation, and bridging them requires significant infrastructure that neither platform provides.
Two billing systems, zero interoperability
To understand why cross platform sync is so difficult, you need to understand how differently Google and Apple represent purchases. These are not minor API differences. They are fundamentally different architectures.
Receipt formats and verification
Apple and Google use entirely different mechanisms to prove that a purchase happened.
| Aspect | Google Play Billing | Apple StoreKit |
|---|---|---|
| Purchase proof | Purchase token (opaque string) | Signed receipt (JWS in StoreKit 2) |
| Verification endpoint | purchases.subscriptionsv2.get REST API | App Store Server API (/inApps/v1/subscriptions) |
| Authentication | Google service account with JSON key | JWT signed with App Store Connect private key |
| Response format | SubscriptionPurchaseV2 JSON object | JWSTransactionDecodedPayload (signed JSON) |
| Subscription ID format | productId:basePlanId | Simple productId string |
| Renewal tracking | expiryTime field on subscription resource | expiresDate in transaction info |
Google Play uses a purchase token model. When a user subscribes, your app receives a purchase token. You send this token to the Google Play Developer API, which returns the current subscription state. The token is an opaque string with no inherent meaning.
Apple uses a signed transaction model. In StoreKit 2, purchase information is delivered as JSON Web Signatures (JWS) that your server can verify using Apple’s public key. Each transaction is a self contained, cryptographically signed record.
These are not just different APIs wrapping the same concept. They represent different philosophies about where trust lives. Google says “ask our server, we’ll tell you the state.” Apple says “here’s a cryptographically signed proof, verify it yourself.”