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.
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(andreact-native-purchases-uifor 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
- Create your subscription products in App Store Connect and Google Play Console.
- In RevenueCat, create a project, add your iOS and Android apps, and connect each store (App Store Connect API key, Google Play service account).
- Create an entitlement, for example
pro, and attach the products that grant it. - 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-uiConfigure 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:
- Store agreements: the Paid Applications agreement, tax and banking details are complete in App Store Connect, and the Google Play merchant account is active.
- 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.
- 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.
- 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:
| Event | What to do |
|---|---|
INITIAL_PURCHASE | Grant access, record the subscription |
RENEWAL | Extend access, record revenue |
CANCELLATION | Note that it won't renew; keep access until expiry |
EXPIRATION | Remove access |
BILLING_ISSUE | Prompt the user to update payment details |
PRODUCT_CHANGE | Update 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_ISSUEevent 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
logInafter 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:
| RevenueCat | Stripe | |
|---|---|---|
| Purpose | Manages store subscriptions (Apple, Google) and entitlements | Processes card and wallet payments |
| Where money is collected | App Store and Google Play | Your Stripe account |
| Use it for | Digital subscriptions and in-app upgrades | Physical goods, real-world services, web checkout |
| Together | RevenueCat can include Stripe subscriptions in the same entitlements | Stripe 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
- RevenueCat React Native installation, RevenueCat
- Displaying paywalls, RevenueCat
- Webhooks, RevenueCat
- RevenueCat pricing, RevenueCat
- Development builds, Expo
Related
In-App Purchases in React Native: RevenueCat vs Going Direct
React Native in-app purchases in 2026: RevenueCat vs expo-iap and react-native-iap, server validation, Android acknowledgement and when each choice wins.
When Apple Requires In-App Purchase, and When You Can Use Stripe
Apple's in-app purchase rules in plain English: what must use IAP, the exceptions (physical goods, services, reader apps, US links) and what Apple charges.
Stripe in React Native: Payment Sheet, Apple Pay and Google Pay
Accept payments in React Native with Stripe: Payment Sheet setup in Expo, the server side, Apple Pay and Google Pay, webhooks, and when Stripe is allowed.
