Payments· 7 min read

RevenueCat with Expo: Paywalls, Entitlements and Webhooks

A practical RevenueCat guide for Expo apps: setup, offerings, purchases, entitlements, paywalls, restores, backend webhooks, and RevenueCat vs Stripe.

By

Full-Stack, Mobile & AI Engineer

RevenueCat turns in-app subscriptions from a backend project into an SDK integration. You still have to configure products in two stores and think through your business rules, but receipts, renewals, cross-platform access and most of the edge cases are handled for you.

This guide walks through RevenueCat with an Expo app end to end: installing the SDK, configuring offerings, making purchases, checking entitlements, showing a paywall, restoring purchases, and keeping your own backend in sync with webhooks. It finishes with the common "RevenueCat vs Stripe" question.

Key takeaways

  • Install react-native-purchases (and react-native-purchases-ui for paywalls) and run it in a development build, not Expo Go.
  • Model access as entitlements ("pro"), not products. Products change; entitlements are what your code checks.
  • Use offerings to decide which products a paywall shows, so you can change pricing without an app update.
  • Identify users with your own user ID so access follows them across devices and platforms.
  • Webhooks keep your backend in sync with purchases, renewals, cancellations and billing problems.

What RevenueCat is, and what it costs

RevenueCat is a subscription backend for apps: its SDK wraps Apple's and Google's in-app purchase systems, validates receipts on its servers, tracks each customer's entitlements and renewals, and sends webhooks to your backend. You can use it purely for purchases and entitlements with your own paywall screens, or add its paywall templates and experiments. It's free until your app makes $2,500 in monthly tracked revenue, then charges 1% of tracked revenue on its standard plan.

Step 1: set up products and RevenueCat

  1. Create your subscription products in App Store Connect and Google Play Console.
  2. In RevenueCat, create a project, add your iOS and Android apps, and connect each store (App Store Connect API key, Google Play service account).
  3. Create an entitlement, for example pro, and attach the products that grant it.
  4. Create an offering (for example default) with packages such as monthly and annual.

Step 2: install and configure the SDK

npx expo install react-native-purchases react-native-purchases-ui

Configure it once when the app starts, with the public SDK key for each platform:

import { Platform } from "react-native";
import Purchases from "react-native-purchases";

export function configurePurchases(userId?: string) {
  Purchases.configure({
    apiKey: Platform.OS === "ios" ? IOS_PUBLIC_KEY : ANDROID_PUBLIC_KEY,
    appUserID: userId, // your own user ID, if the user is signed in
  });
}

If users sign in after launch, call Purchases.logIn(userId) when they do, and Purchases.logOut() when they sign out. Using your own IDs is what lets a subscription bought on an iPhone give access in the Android app for the same account.

Step 3: show products and purchase

const offerings = await Purchases.getOfferings();
const current = offerings.current; // the offering marked "current" in RevenueCat

const monthly = current?.monthly;
if (monthly) {
  try {
    const { customerInfo } = await Purchases.purchasePackage(monthly);
    if (customerInfo.entitlements.active["pro"]) {
      // unlock pro features
    }
  } catch (e: any) {
    if (!e.userCancelled) {
      // show a friendly error
    }
  }
}

Display prices from the package (monthly.product.priceString), never hard-coded, so users see their local currency and current price.

Step 4: check entitlements everywhere

const info = await Purchases.getCustomerInfo();
const isPro = info.entitlements.active["pro"] !== undefined;

Check entitlements when the app starts and when it returns to the foreground, and listen for changes with Purchases.addCustomerInfoUpdateListener, so a renewal or cancellation is reflected without a restart.

Step 5: paywalls

You can build your own paywall with the offering data above, or use RevenueCat's paywall UI, configured remotely in the RevenueCat dashboard:

import RevenueCatUI, { PAYWALL_RESULT } from "react-native-purchases-ui";

const result = await RevenueCatUI.presentPaywallIfNeeded({ requiredEntitlementIdentifier: "pro" });
if (result === PAYWALL_RESULT.PURCHASED || result === PAYWALL_RESULT.RESTORED) {
  // unlock
}

Remote paywalls let you change copy, layout and featured plans, and run experiments, without shipping a new build. Whatever you use, make the price, billing period and renewal terms clear, because Apple reviews paywalls closely.

Step 6: restore purchases

const info = await Purchases.restorePurchases();

Put a "Restore purchases" button on your paywall or settings screen. Users reinstalling the app or moving to a new phone need it, and App Review expects it.

Why are my offerings empty?

The most common RevenueCat support question, and almost never a code bug. Check, in order:

  1. Store agreements: the Paid Applications agreement, tax and banking details are complete in App Store Connect, and the Google Play merchant account is active.
  2. Products exist and are attached: the product IDs in RevenueCat match the stores exactly, and each product is attached to a package in your current offering.
  3. The app is testable: on Android, the app has been uploaded to at least a testing track; on iOS, test on a real device with a sandbox account, or use a StoreKit configuration file in development.
  4. Identifiers match: the bundle ID or package name and the API key belong to the same RevenueCat app.

Step 7: keep your backend in sync with webhooks

If your server controls access to anything (content, API limits, team seats), it needs to know about subscription changes. Add a webhook URL in RevenueCat and handle events such as:

EventWhat to do
INITIAL_PURCHASEGrant access, record the subscription
RENEWALExtend access, record revenue
CANCELLATIONNote that it won't renew; keep access until expiry
EXPIRATIONRemove access
BILLING_ISSUEPrompt the user to update payment details
PRODUCT_CHANGEUpdate the plan (upgrade or downgrade)

Verify the webhook's authorization header, store each event ID so duplicates are ignored, and when in doubt fetch the current customer state from RevenueCat's API rather than trusting event order.

From the field: RevenueCat and in-app purchases are part of the payments work I've shipped across 12+ apps, alongside Stripe, Razorpay and KNET, always with idempotent, webhook-driven billing. With RevenueCat, the backend rule I'd keep is simple: entitlements decide access, and every webhook is processed exactly once.

Testing the whole subscription lifecycle

Before launch, walk through every state a subscriber can be in, not just the first purchase:

  • Purchase and renewal: in Apple's sandbox and Google's test environment, subscriptions renew on an accelerated schedule (a monthly plan renews within minutes), so you can watch several renewals in one session.
  • Cancellation and expiry: cancel in the sandbox account settings and confirm access continues until the period ends, then stops.
  • Billing problems: check that a BILLING_ISSUE event reaches your backend and that the app nudges the user to fix payment.
  • Restore on a fresh install, and sign in on a second device or platform with the same account.
  • Upgrade and downgrade between monthly and annual plans.
  • Refunds: make sure access is removed when a refund event arrives.

RevenueCat shows sandbox purchases separately from production data, so you can test freely without polluting your revenue charts.

Common RevenueCat mistakes

  • Checking product IDs instead of entitlements, which breaks as soon as you add a new plan.
  • Not calling logIn after sign-in, which creates anonymous users whose purchases don't follow them to other devices.
  • Hard-coding prices in the paywall instead of reading them from the package.
  • Granting access only on the client, so your backend features stay locked or, worse, open.
  • Forgetting the restore button, which App Review expects and users need.

Web purchases and Stripe

RevenueCat isn't limited to the app stores. It can track subscriptions sold through Stripe and offers its own web billing, so a customer who subscribes on your website gets the same entitlement in the app. On the US storefront, Apple's guideline 3.1.1(a) also allows apps to link to those web purchases. If you only sell on the web, Stripe alone may be enough.

Alternatives: Superwall, Adapty and others

Superwall focuses on paywalls and paywall experiments and is often used on top of RevenueCat for purchases. Adapty and Qonversion offer similar SDKs and dashboards to RevenueCat. Compare them on pricing at your expected revenue, web billing support, webhook quality and how easily you could migrate away later.

RevenueCat vs Stripe

They solve different problems, and many apps use both:

RevenueCatStripe
PurposeManages store subscriptions (Apple, Google) and entitlementsProcesses card and wallet payments
Where money is collectedApp Store and Google PlayYour Stripe account
Use it forDigital subscriptions and in-app upgradesPhysical goods, real-world services, web checkout
TogetherRevenueCat can include Stripe subscriptions in the same entitlementsStripe handles the web side of a multiplatform subscription

FAQ

Does RevenueCat work with Expo?

Yes. Install react-native-purchases (and react-native-purchases-ui for paywalls) with npx expo install, then run the app in a development build created with EAS Build. Expo Go doesn't include the native purchase code.

What's the difference between products, offerings and entitlements?

Products are what you sell in each store (a monthly plan on iOS and on Android). Entitlements are what users get (for example "pro"). Offerings are groups of products a paywall shows. Your code should check entitlements, and your paywall should read offerings.

How do I restore purchases with RevenueCat?

Call Purchases.restorePurchases() from a "Restore purchases" button, typically on the paywall or in settings. It re-syncs the user's store purchases and returns updated customer info, including active entitlements.

Is RevenueCat free?

RevenueCat's Pro plan is free up to $2,500 in monthly tracked revenue, then costs 1% of tracked revenue. Optional growth tools and Enterprise plans are priced separately.

Next steps

If you're adding subscriptions to an Expo app, I can set up RevenueCat, the store products, paywalls and the backend webhooks, and test the whole lifecycle before launch. Get in touch, or see how I work as a React Native developer.

Sources

Bhuwanesh Sisodia

Bhuwanesh Sisodia

Full-Stack, Mobile & AI Engineer

Bhuwanesh Sisodia is a Full-Stack, Mobile & AI Engineer based in Mumbai, with 7+ years of experience and 50+ apps shipped to the App Store and Google Play. He works independently with startups and product teams worldwide.

About meGitHubLinkedInX

Related