Subscription Commerce in React Native: Implementing In-App Subscriptions
AI generated
RN
native
React Native · Subscription Commerce · RevenueCat · IAP
React Native Subscription Commerce
implementing subscriptions the right way

Subscription commerce in React Native means far more than adding a buy button. Entitlements, paywall design, server-side validation and how you handle failed renewals decide whether a subscription model reliably generates revenue or quietly leaks it. This article walks through the full path from the first product definition to a synchronized entitlement status.

19 min read RevenueCat · StoreKit 2 · Play Billing · Paywalls React Native 0.74+ · Expo · TypeScript

1. Why subscription commerce works differently from web checkout

Subscription commerce in a mobile app follows a fundamentally different rulebook than a classic web checkout. Apple and Google require that recurring payments for digital content and features go through their respective in-app purchase infrastructure, a direct Stripe form in a WebView is not allowed for digital in-app subscriptions and reliably gets rejected in review. That means: anyone who wants to sell a subscription in a React Native app has to deal with StoreKit on iOS and the Google Play Billing Library on Android, whether they want to or not.

This store requirement also brings advantages. Users manage their subscriptions centrally in their operating system settings, payment data never touches your own infrastructure, and cancellations follow a flow users already know. The price for that is a commission of typically 15 to 30 percent, plus significantly more complexity around status checks, since the app initially has no information at all about whether a subscription is active, paused, in a refund window, or already canceled.

Anyone setting up subscription commerce in React Native properly from the ground up therefore does not think first about the paywall UI, but about the entitlement model behind it: which features which subscription unlocks, how that state stays reliably available across app restarts, device switches and store outages, and how that state synchronizes with your own backend.

2. Defining products, entitlements and offerings

The first step of every subscription model is modeling it in the respective store backend: App Store Connect and the Google Play Console require defining product IDs, regional price tiers and subscription groups within which users can switch between tiers. A typical setup has three product IDs for monthly, yearly and a cheaper introductory offer, all within the same subscription group, so Apple and Google automatically prevent a user from paying for two overlapping subscriptions at once.

On top of that sits the entitlement layer: instead of checking directly against product IDs in the app code, you define logical permissions like premium_access or pro_features, mapped to one or more products. This layer of indirection saves you a code change whenever pricing structure or product names change, since the app keeps asking only for the entitlement, never for the concrete product ID.


{
  "entitlements": {
    "premium_access": {
      "productIdentifiers": [
        "com_app_premium_monthly",
        "com_app_premium_yearly",
        "com_app_premium_intro"
      ]
    }
  },
  "offerings": {
    "default": {
      "packages": [
        { "identifier": "monthly", "productId": "com_app_premium_monthly" },
        { "identifier": "annual", "productId": "com_app_premium_yearly" }
      ]
    }
  }
}

3. RevenueCat as an abstraction over StoreKit and Play Billing

Implementing StoreKit 2 directly means fully managing transaction verification, receipt parsing, restore logic and renewal events yourself, and then again in a separate form for the Google Play Billing Library. RevenueCat abstracts exactly that difference and provides a unified API for both platforms, including server-side validation, analytics and webhook events, without needing to build your own validation server. For a React Native team, that means a considerable time saving compared to a custom react-native-iap integration.

Setup happens through the react-native-purchases SDK, configured on app start and providing offerings, active entitlements and purchase flows through one shared interface afterward. The decisive advantage over a raw StoreKit integration: RevenueCat caches the entitlement status locally and syncs it in the background, so the app can still show the last known subscription status even during a brief loss of internet connectivity.


// purchases.ts — configure RevenueCat and check entitlement status
import Purchases from 'react-native-purchases';

export function configurePurchases(apiKey: string, userId: string) {
  Purchases.configure({ apiKey, appUserID: userId });
}

export async function hasPremiumAccess(): Promise<boolean> {
  const info = await Purchases.getCustomerInfo();
  return typeof info.entitlements.active['premium_access'] !== 'undefined';
}

export async function purchasePackage(packageId: string) {
  const offerings = await Purchases.getOfferings();
  const pkg = offerings.current?.availablePackages.find(p => p.identifier === packageId);
  if (!pkg) throw new Error('Package not found in current offering');

  const { customerInfo } = await Purchases.purchasePackage(pkg);
  return typeof customerInfo.entitlements.active['premium_access'] !== 'undefined';
}

4. Paywall patterns: soft, hard and trial-first

The paywall decides the conversion rate of the entire subscription model more than any other UI element in the app. A soft paywall shows premium content in a reduced or blurred form and still allows limited use of the app, a hard paywall blocks the app entirely until a subscription is completed. Hard paywalls usually achieve higher conversion rates right after onboarding, but they lose users who have not yet decided, without them ever trying the app.

Trial-first strategies, where the user immediately starts a free trial instead of making a purchase decision upfront, lower the barrier considerably, but push the actual payment commitment out by several days. In any case, a clear, non-misleading price display is essential: Apple and Google explicitly check during review whether price, billing interval and cancellation terms are clearly visible before a purchase is triggered, obscured paywalls reliably lead to rejections.

5. Server-side validation and webhook events

Pure client-side checking of purchase status is never sufficient for a production subscription model, since manipulated clients or jailbroken devices could fake a purchase. The reliable source of truth is always server-side validation, either through your own endpoints querying Apple and Google servers directly, or through RevenueCat's webhook system, which sends a signed event to your own endpoint on every purchase, renewal and cancellation.

These webhooks are the anchor for keeping entitlements current in your own backend, for example a Magento instance or a separate commerce API. A typical flow: RevenueCat sends an INITIAL_PURCHASE event, your server verifies the signature, updates the user record with the active entitlement, and then makes that status available through your own API for web or desktop clients of the same user account as well.


# Verify a RevenueCat webhook signature on your own backend endpoint
# (pseudo-CLI illustrating the verification step, actual code lives in your API layer)
curl -X POST https://api.example.com/webhooks/revenuecat \
  -H "Authorization: Bearer YOUR_WEBHOOK_SECRET" \
  -H "Content-Type: application/json" \
  --data '{"event": {"type": "INITIAL_PURCHASE", "app_user_id": "user_123"}}'

# Typical handling steps on the receiving backend endpoint:
# 1. Reject the request if the Authorization header does not match the shared secret
# 2. Parse the event type: INITIAL_PURCHASE, RENEWAL, CANCELLATION, BILLING_ISSUE, EXPIRATION
# 3. Look up the local user record via app_user_id
# 4. Update the stored entitlement state and its expiration timestamp
# 5. Return HTTP 200 quickly, RevenueCat retries on non-2xx responses
echo "Webhook processed for user_123: entitlement premium_access updated"

A robust webhook handler should also be idempotent: RevenueCat does not guarantee exactly-once delivery, it can send the same event multiple times during network issues. Your own endpoint should therefore check via an event ID whether processing has already happened before writing the entitlement status again, to avoid duplicate notifications or inconsistent timestamps.

6. Upgrades, downgrades and proration between tiers

As soon as a subscription model offers more than one price tier, for example Basic and Pro, switching between tiers becomes its own challenge. Both Apple and Google support upgrades and downgrades within the same subscription group, but calculate proration differently: Apple credits the remaining value of the old subscription proportionally toward the new one, Google offers several proration modes to choose from, such as an immediate switch with a credit, or a switch only at the next billing period.

For the app side that means: the entitlement status needs to be resynchronized after a tier switch, since both the unlocked features and the next billing date may change. RevenueCat represents these switches as new customer info events, so the app can react through the same listener mechanism used for the initial purchase, without needing separate special-case logic for upgrades.

7. Grace periods and billing retry for failed payments

An underestimated source of revenue loss in subscription commerce is silent cancellation caused by failed payments, for example when a stored credit card has expired. Both stores offer a grace period for this: the user temporarily keeps access to premium features while Apple or Google retry the payment in the background multiple times, often spread across several days with increasing intervals.

The app needs to explicitly handle this intermediate state, rather than revoking access immediately on the first failed payment attempt. RevenueCat provides a dedicated billing_issue_detected_at timestamp in the customer info object, which the app can use to show a subtle warning, such as "please update your payment method," without immediately blocking usage. That noticeably reduces involuntary churn compared to a hard immediate lockout.

8. Sandbox testing with StoreKit configuration and license testers

Testing subscription flows manually in production is neither practical nor safe, since real payments would be triggered. Xcode offers local StoreKit configuration files for this, letting you simulate products, prices and even accelerated subscription cycles, entirely without a connection to App Store Connect. A monthly subscription can be tested end to end in a few minutes instead of a real thirty days, including renewal, cancellation and failed payment.

On the Android side, license testers in the Play Console take on this role: for registered test accounts, purchases go through the real Play Billing flow but are not actually charged. What matters for both platforms is that these sandbox purchases show up clearly separated from real production purchases in RevenueCat's dashboard, so test transactions do not distort real revenue metrics.

9. Custom IAP integration versus RevenueCat

Whether a custom react-native-iap integration or RevenueCat is the right choice depends heavily on team size and the desired level of control. The table below contrasts both approaches along the most important decision dimensions.

Dimension Custom IAP integration RevenueCat
Development effort High, custom StoreKit and Billing integration Low, one SDK for both platforms
Server validation Must be built and operated yourself Included, accessible via webhooks
Cross-platform sync Needs custom app-user-ID mapping logic Built-in app-user-ID concept
Cost No additional service fee Revenue-based fee above a certain threshold
Maintenance for store changes Your own team has to keep up with API changes Maintained centrally by the provider

For most teams the time saving from RevenueCat clearly outweighs the added cost, especially as long as no highly specialized billing requirements exist. A custom integration usually only pays off when revenue volume is large enough that the RevenueCat fee exceeds the saved development time, or when regulatory reasons demand a fully self-controlled validation chain.

Mironsoft

React Native development, subscription commerce and mobile backend integration

Implementing a subscription model in your React Native app?

We build subscription commerce with RevenueCat or a custom StoreKit and Play Billing integration, including paywall design, server sync and grace-period handling for your subscription model.

Paywall & entitlements

High-converting paywalls with a clear entitlement model

Server sync

Webhook integration and entitlement reconciliation with your backend

Testing & rollout

Sandbox setup, grace-period handling and store review preparation

10. Summary

Subscription commerce in React Native starts with a clean entitlement model, not with the paywall UI. Products, price tiers and subscription groups are defined in the store backend, RevenueCat abstracts the differences between StoreKit 2 and Play Billing behind a shared API, and webhooks keep the entitlement status current in your own backend. Paywall patterns, proration on tier switches and correctly handling grace periods ultimately decide how much of the theoretical revenue actually arrives.

The biggest lever for a working subscription model is rarely the last UI detail of the paywall, but the robustness of the status logic behind it. A user locked out instantly and without warning because of an expired credit card is more likely to cancel than to update their payment method. Thorough sandbox testing with StoreKit configuration files and Play Console license testers uncovers these edge cases before they cost real revenue in production.

React Native Subscription Commerce — The Essentials at a Glance

Entitlement model

Logical permissions instead of hard product ID checks decouple code from pricing changes.

RevenueCat as abstraction

One API for StoreKit 2 and Play Billing saves considerable integration effort.

Server sync via webhooks

Signed events keep entitlements current in your own backend, across platforms.

Grace period & testing

Subtle warnings instead of instant lockout, thorough sandbox testing before every release.

11. FAQ: React Native Subscription Commerce and In-App Subscriptions

1Do I have to bill through the app stores?
For digital content, yes. An external payment form gets rejected in review.
2Product ID versus entitlement?
The product ID is the store item, the entitlement a logical permission like premium_access. Code checks only the entitlement.
3Why RevenueCat over a custom integration?
One API for both platforms, including server validation and webhooks, saves significant development effort.
4Soft or hard paywall?
Hard paywalls convert better right after onboarding, soft paywalls retain undecided users longer.
5Why isn't client-side checking enough?
Manipulated clients could fake a purchase. Server-side validation is always the reliable source of truth.
6How does proration work?
Apple credits the remaining value proportionally, Google offers several proration modes to choose from.
7What happens on a failed payment?
A grace period preserves access temporarily while the payment is retried in the background.
8How do I test without real payments?
StoreKit configuration files in Xcode, license testers in the Play Console for Android.
9How do I sync across platforms?
Through a stable app user ID and webhook events that update the centrally stored entitlement status.
10What commission do the stores keep?
Typically 15 to 30 percent, often reduced after the first year or for smaller developers.