Introducing Offline Entitlements: Uninterrupted in-app purchases

When 99.9999% uptime isn’t enough.

Introducing offline entitlements: Uninterrupted in-app purchases
Nacho Soto

Nacho Soto

Published

We’re excited to announce a major addition to the RevenueCat SDKs for both iOS and Android: support for Offline Entitlements. 

This new feature paves the way for users to make purchases from the app stores even in scenarios where the RevenueCat servers are temporarily unreachable. Let’s dig into what this means for developers, why it’s important, and how we accomplished this.

A necessity in an unpredictable world

In an ideal world, our servers would never go down, but reality is different. Even the most robust systems can have occasional downtimes, and these can be disruptive for applications relying on server-based functionalities. This is precisely the challenge our new Offline Entitlements feature is designed to address.

Our servers are used to validate in-app purchases and renewals prior to granting access in apps. This is necessary to verify the status of subscriptions as well as determining which entitlements are unlocked.

How in-app purchases and renewals are communicated between RevenueCat and other systems.
Figure 1. How in-app purchases and renewals are communicated between RevenueCat and other systems.

Therefore any server downtime could temporarily interrupt the purchasing process, affecting the user experience and sales.

With this new feature, even if our main and backup servers are down, the SDKs can continue to process purchases. This is enabled transparently to the user on the rare occasion that our servers are down, and when they come back online, the SDK automatically syncs the information so it can be visible in the dashboard.

We’re still striving to avoid any server downtime, but this adds yet another layer to our Swiss cheese model, thereby improving the reliability of RevenueCat as a whole.

The Swiss cheese model of risk mitigation
Figure 2. The Swiss cheese model of risk mitigation

Note that prior versions of the SDK were already resilient to our servers being down thanks to well-tested caching layers. The main missing piece was being able to handle purchases and renewals during an outage.

How we achieved this

The process of adding Offline Entitlements was complex and required careful planning and implementation. We had to ensure that, while allowing Offline Entitlements, we also maintained robust security measures to prevent fraudulent activities.

The first piece of the puzzle was allowing the SDK to have Offerings cached if the server was down.

Due to the dynamic nature of Offerings, which allows apps to change them at any point or perform experiments, the SDK has a low time-to-live on the Offerings cache. However, with the latest versions of our SDKs, apps will now be able to display paywalls even during an outage.

The second piece was allowing the SDK to determine which products unlocked which Entitlements.

Figure 3. How products are configured in RevenueCat

Traditionally these two responsibilities were delegated to the backend server:

  • Figure out which products a user has purchased: this is done by our servers communicating with Apple and Google’s servers directly
  • Figure out which Entitlements the user is entitled to

Now, the latest SDK versions have a full mapping of products and entitlements, allowing them to determine what users have unlocked.

To ensure we had the right safeguards, we leveraged Apple’s and Google’s secure systems. On Apple’s side, we use only StoreKit 2 to determine the current subscriptions, which ensures that users aren’t able to fake this data (as they might have been able with StoreKit 1 receipts).

On Android, we use Google Play BillingClient (or the Amazon Appstore SDK) to determine the current subscriptions securely and calculate the current entitlements.

How we tested this

This inevitably brought additional complexity to the SDK. That’s why it was of utmost importance to ensure proper test coverage now and moving forward.

We followed all stages of rigorous testing: including unit tests, integration tests, and manual testing. If you’d like to find out more about how we test, we wrote a detailed post about our approach to SDK testing at RevenueCat.

We expanded our end-to-end testing infrastructure to allow us to verify the behavior of the SDK with varying combinations of state changes: simulating our API being down, up, or changing throughout the lifecycle of the app.

1func testPurchaseWhileServerIsDownPostsReceiptWhenForegroundingApp() async throws {
2    let logger = TestLogHandler()
3
4    // 1. Purchase while server is down
5    self.serverDown()
6    try await self.purchaseMonthlyProduct()
7
8    logger.verifyMessageWasNotLogged("Finishing transaction")
9
10    // 2. Server is back
11    self.serverUp()
12
13    // 3. Request current CustomerInfo
14    let info1 = try await Purchases.shared.customerInfo()
15    try await self.verifyEntitlementWentThrough(info1)
16
17    // 4. Ensure transaction is finished
18    logger.verifyMessageWasLogged("Finishing transaction", level: .info)
19
20    // 5. Restart app
21    Purchases.shared.invalidateCustomerInfoCache()
22    await self.resetSingleton()
23
24    // 6. To ensure (with a clean cache) that the receipt was posted
25    let info2 = try await Purchases.shared.customerInfo()
26    try await self.verifyEntitlementWentThrough(info2)
27}
28
29

Limitations

Availability

On iOS this requires iOS 15.0+, since it relies on StoreKit 2 to determine current entitlements in a secure way.

Cross-platform purchases

During a server outage, purchases made on a different platform won’t carry over. In other words, if a user purchased a subscription on Android, it won’t be active on iOS, etc.

Fresh installs

This feature requires the user to launch the app at least once with our servers online so it can download the necessary information. But stay tuned on further reliability improvements that will mitigate that.

Consumables

Guaranteeing uniqueness of consumable purchases isn’t possible without a server. Therefore this feature isn’t compatible with them.

Why this is a huge win for developers

This new feature ensures a more reliable experience for users when making in-app purchases and subscriptions. This is a big win because users no longer need to rely on RevenueCat’s uptime or that of our cloud providers in order to validate their purchases. This leads to a better experience and makes apps better equipped to handle potential technical issues with RevenueCat servers, resulting in a more resilient business.

This is just one of the many steps we are taking to ensure that RevenueCat continues to serve you better, helping you to make more money.

We’re thrilled to introduce this new feature that delivers automatic benefits to your app with no code changes needed. We look forward to hearing your feedback!

In-App Subscriptions Made Easy

See why thousands of the world's tops apps use RevenueCat to power in-app purchases, analyze subscription data, and grow revenue on iOS, Android, and the web.

Related posts

Everything we shared in our inaugural Launch Party
Company

Everything we shared in our inaugural Launch Party

What we've shipped, what we're shipping, and answers to your questions.

Peter Meinertzhagen

Peter Meinertzhagen

December 22, 2023

Company

Navigating RevenueCat’s new pricing for existing users

A detailed guide to understanding the changes, benefits, and potential downsides of migrating to our updated pricing

Rik Haandrikman

Rik Haandrikman

September 22, 2023

Helping developers make (even) more money And keeping the lights on in the process
Company

Helping developers make (even) more money

And keeping the lights on in the process

Jacob Eiting

Jacob Eiting

September 20, 2023

Want to see how RevenueCat can help?

RevenueCat enables us to have one single source of truth for subscriptions and revenue data.

Olivier Lemarie, PhotoRoomOlivier Lemarie, PhotoRoom
Read case study