How to Build a Subscription Mobile App with RevenueCat
Contents
A subscription app needs more than a paywall. It must show the right products, complete purchases through Apple or Google, unlock the correct features, restore access, and react when a subscription renews, expires, or is refunded.
RevenueCat sits between your app and the stores. It gives your mobile client one way to fetch products, make purchases, and check access across iOS and Android. You still create the subscription products in App Store Connect and Google Play Console, but you avoid maintaining two separate entitlement systems inside the app.
Create store products first, map them to one RevenueCat entitlement, check that entitlement instead of product IDs, identify signed-in users consistently, support restore purchases, and use webhooks when your backend also needs subscription state.
How the subscription system fits together
Four layers are involved. Keeping them separate makes the setup much easier to reason about.
| Layer | What it controls | Example |
|---|---|---|
| App Store and Google Play | Price, billing period, regional availability, and purchase processing | Monthly and annual products |
| RevenueCat products and packages | A normalized catalog your app can request | $rc_monthly and $rc_annual packages |
| RevenueCat entitlement | The access a paid customer receives | pro |
| Your app | Paywall, purchase button, feature gates, and account UI | Unlock exports and remove limits |
An entitlement should describe access, not a billing plan. For example, both a monthly product and an annual product can unlock the same pro entitlement. Your app then asks one simple question: does this customer have active pro access?
RevenueCat explains this model in its entitlements documentation. It is safer than scattering monthly and annual product IDs throughout the code.
1. Define what the subscription unlocks
Before opening either store dashboard, write down the paid promise. Be specific enough that a customer can understand it and a developer can enforce it.
Weak definition: access to premium features.
Useful definition: unlimited projects, cloud sync, PDF export, and AI summaries up to the plan's monthly allowance.
Decide these details before implementation:
- Which features remain free?
- Is there one paid tier or several?
- Will you offer monthly, annual, or both?
- Is there a free trial or introductory price?
- What happens to saved data after access expires?
- Can one account use the subscription on multiple devices?
A simple first release usually benefits from one entitlement and two billing periods. Multiple tiers can work, but they add upgrade, downgrade, proration, and messaging cases that need testing.
2. Create products in both stores
Create auto-renewable subscriptions in App Store Connect and equivalent subscription products in Google Play Console. Use stable IDs such as:
The IDs can differ between platforms, but matching names reduce confusion. Never encode a price in the ID because prices can change by market.
For Apple, place related subscription products in the same subscription group when customers should hold only one of them at a time. Apple's subscription setup guide covers product creation and review information. Google documents plans, offers, and prepaid options in its Play Billing subscription guide.
Complete the metadata while you are there:
| Store field | What to check |
|---|---|
| Display name | Clear plan name that fits small screens |
| Description | Concrete benefits without vague promises |
| Billing period | Monthly or annual as intended |
| Price | Correct base price and regional availability |
| Trial or offer | Eligibility and duration are clearly explained |
| Review details | Screenshots and instructions help reviewers find the paywall |
Products may not appear immediately in development. Store agreements, tax details, product status, and build configuration can all affect availability.
3. Configure RevenueCat
Create a RevenueCat project, add the iOS and Android apps, then connect the store credentials requested by RevenueCat. Import or add the products you created in the stores.
Next, create:
- An entitlement named
pro. - An offering named
defaultorcurrent. - Monthly and annual packages inside that offering.
- A mapping from each package's store product to the
proentitlement.
The offering is the catalog shown by the app. This lets you change which products appear without shipping a new build. RevenueCat's displaying products guide explains how offerings and packages are returned to the SDK.
Keep one active current offering unless you have a deliberate experiment. Several half-configured offerings make missing-product bugs harder to diagnose.
4. Install and configure the SDK
Install RevenueCat's SDK for your framework. Use the official React Native installation guide or the dedicated Expo guide for an Expo app.
Configure the SDK once when the app starts. Use the public RevenueCat SDK key for the current platform. Do not put store private keys, webhook secrets, or backend credentials in the app bundle.
Your initialization flow should be predictable:
Avoid configuring the SDK from several screens. A single subscription service or provider makes purchase state easier to test and prevents duplicated listeners.
5. Choose a stable customer identity
Anonymous purchases are useful when people can subscribe before creating an account. Signed-in identities are useful when access must follow a user across devices.
If your app has accounts, use the same non-sensitive internal user ID for RevenueCat after login. Do not use an email address as the primary app user ID. Emails can change and expose personal information in logs or integrations.
RevenueCat's customer identification guide explains anonymous IDs, custom IDs, login, logout, and alias behavior. Read it before mixing guest purchases with account creation.
Test these identity transitions:
| Scenario | Expected result |
|---|---|
| Guest opens app | Anonymous customer exists |
| Guest buys, then signs up | Purchase follows the new account according to your identity policy |
| Existing user signs in on another device | Active entitlement is restored after identity loads |
| User signs out | Previous account's access is no longer shown |
| Different account signs in | App fetches fresh CustomerInfo before unlocking features |
Do not cache a simple isPro boolean forever. Subscription state can change outside the current device.
6. Fetch the offering and build the paywall
Request the current offering when the paywall opens. Render the available packages from RevenueCat rather than hardcoding localized prices.
The store provides the customer-facing price and billing period. That matters because currencies, taxes, and formatting vary. The paywall should show:
- The plan benefits.
- Monthly and annual options that are actually available.
- Trial terms, if applicable.
- The recurring billing period.
- Restore purchases.
- Links to the privacy policy and terms.
- A clear way to close the paywall when it is not mandatory.
If no offering is returned, show a useful fallback and log enough context to diagnose it. Do not display an empty card or a purchase button with a guessed price.
RevenueCat also provides configurable paywalls. A custom paywall gives you more layout control, while a remotely configured paywall can make iteration faster. The purchase rules remain the same either way.
7. Handle the purchase as a stateful flow
A purchase is not just a button tap. The UI needs explicit states.
| State | UI behavior |
|---|---|
| Ready | Plan choices and purchase button are enabled |
| Purchasing | Disable repeated taps and show progress |
| Success | Refresh CustomerInfo, unlock access, and close or confirm |
| Cancelled | Return to the paywall without an alarming error |
| Pending | Explain that approval or payment confirmation is still required |
| Failed | Show a concise retry message and retain the selected plan |
Call the SDK purchase method with the selected package. Treat the returned entitlement state as the source of truth. A successful-looking store sheet is not enough by itself.
RevenueCat's making purchases guide includes the platform methods and error handling patterns.
8. Gate features with CustomerInfo
After initialization, login, purchase, restore, or foreground refresh, inspect CustomerInfo and check whether the pro entitlement is active. RevenueCat documents the available entitlement data in its CustomerInfo guide.
Centralize the check:
Every premium screen should read the same access state. Do not let one screen check a product ID while another checks a locally stored flag.
Think carefully about expiration. In many apps, an expired customer should keep their data but lose paid actions. Deleting user content because billing ended creates an avoidable support problem.
9. Add restore purchases and subscription management
Apple requires a way to restore restorable purchases. Put a visible Restore Purchases action on the paywall or account screen. After restore completes, refresh CustomerInfo and explain the result.
Also include a Manage Subscription action that opens the appropriate store subscription settings. Users should be able to change or cancel a plan without contacting support.
Use calm messages:
- Restored: "Your subscription is active again on this device."
- Nothing found: "We could not find an active purchase for this store account."
- Error: "Restore could not be completed. Check your connection and try again."
Do not promise a refund from inside the app. Refund decisions and workflows belong to the stores.
10. Sync subscription state to your backend
Client-side entitlement checks are enough for purely local UI access. A backend also needs subscription state when it protects paid API endpoints, grants usage credits, sends lifecycle messages, or runs paid jobs while the app is closed.
Use RevenueCat webhooks to receive events. Your endpoint should authenticate the request, store the event idempotently, and update a subscription record linked to the RevenueCat app user ID.
Do not trust a client request that says isPro: true. The backend should check its own synchronized record for paid operations.
Plan for renewals, cancellations, billing issues, expirations, refunds, transfers, and temporary grace periods. RevenueCat's webhook event flows show how lifecycle events relate.
11. Test the complete lifecycle
Test purchases before release with sandbox users, StoreKit configuration where appropriate, TestFlight, and Google Play test tracks. Google provides a dedicated billing testing guide.
Your checklist should cover more than the happy path:
- Monthly and annual purchase.
- User cancels the store sheet.
- Network drops during purchase.
- Purchase is pending.
- Subscription renews.
- Subscription enters a billing issue or grace period.
- Subscription expires.
- Refund or revocation removes access.
- Restore works after reinstalling.
- Login on a second device shows the same access.
- Logout does not leak the previous account's entitlement.
- Webhook delivery is duplicated or arrives late.
Enable verbose RevenueCat logs in development, never as noisy production telemetry. The RevenueCat debugging guide lists common configuration checks.
Common RevenueCat mistakes
| Mistake | Better approach |
|---|---|
| Hardcoding prices | Render the localized store price from the package |
| Checking product IDs everywhere | Check one named entitlement |
Storing isPro permanently | Refresh CustomerInfo at important lifecycle points |
| Using email as customer ID | Use a stable internal account ID |
| Unlocking backend work from a client flag | Verify access with server-side subscription state |
| Ignoring restore | Add and test an obvious restore action |
| Testing only one successful purchase | Test expiration, refund, pending, and identity changes |
For a broader payment overview, see how to add in-app purchases to a mobile app.
Build your subscription app with Huxly
Huxly helps you build the mobile frontend, backend, authentication, database, and subscription flow in one project. Start from your plan rules and paywall design, add RevenueCat for iOS and Android billing, test the complete purchase lifecycle, and prepare the app for TestFlight and Google Play without starting from an empty codebase.
FAQ
Does RevenueCat replace App Store Connect and Google Play Billing?
No. Apple and Google still process purchases and control store products. RevenueCat connects those systems to a shared catalog, entitlement model, customer record, and event layer.
Should I check the product ID or the entitlement?
Check the entitlement for access. Several products, such as monthly and annual plans, can unlock the same entitlement.
Can I use RevenueCat with Expo?
Yes. Follow RevenueCat's Expo installation requirements and use a development build when native purchase functionality is required. Purchase testing cannot be treated like an ordinary web preview.
Do I need a backend for RevenueCat?
Not for a basic client-only entitlement check. You do need backend synchronization when paid access controls server endpoints, usage credits, scheduled work, or other resources outside the app.
How should I handle users without accounts?
RevenueCat can create anonymous customer identities. If those users later sign up, define and test how the anonymous purchase becomes associated with the account.
When should subscription access refresh?
Refresh after app initialization, login, purchase, restore, and relevant foreground transitions. Also listen for customer information updates provided by the SDK.
Conclusion
A reliable subscription app separates store products, RevenueCat configuration, entitlement checks, and backend access control. Configure the catalog carefully, identify customers consistently, render live store data, and make every state from purchase to expiration visible and testable.
The paywall is only the front door. Restore behavior, identity changes, webhook handling, and lifecycle testing are what keep paid access accurate after launch.
