For the complete documentation index, see llms.txt. This page is also available as Markdown.

💰In-App Purchases

Sell digital subscriptions and one-time products inside your app using Google's and Apple's native purchase systems.

What are In-App Purchases?

Natively provides built-in support for RevenueCat to handle in-app purchases and subscriptions across iOS and Android. RevenueCat manages purchase validation, receipt handling, and subscription status for you, so you don't have to build and maintain this infrastructure yourself.

Prerequisites

App Stores Product Configuration

Before RevenueCat can validate purchases, your subscription or one-time-purchase products must exist in Google Play Console (for Android) and/or App Store Connect (for iOS).

If you don't see an option to create subscriptions or in-app products yet, upload a build first - create a release in a Closed testing track and upload Android .AAB file from Natively Dashboard.

  1. Open your app in Google Play Console and go to Monetize with Play > Products.

  2. Follow RevenueCat's Android Product Setup guide to create your subscription or in-app products.

  3. Confirm your subscription products show as Active in Google Play Console.

  1. Open App Store Connect and navigate to your app.

  2. Follow RevenueCat's iOS Product Setup guide to create your subscription or one-time purchase products.

  3. Confirm each product's state shows Ready to Submit before it can be used - even for sandbox testing.

RevenueCat Configuration

With your store products created, connect RevenueCat to each store so it can manage the full purchase lifecycle on your behalf.

1

Create your RevenueCat project

  1. Sign up or log in at RevenueCat.

  2. Click New Project, give it a name - typically your app's name - and confirm.

Each RevenueCat project can hold one or more apps (e.g., your iOS and Android apps), and is where all your Products, Entitlements, Offerings, and API Keys will live.

2

Add and connect your app(s)

  1. In your RevenueCat project, go to Apps & providers in the left sidebar.

  2. Click New app configuration.

  3. Select Google Play Store for Android, and App Store for iOS.

  4. Configure the platforms:

  1. Enter the Android app Package Name (Bundle ID) configured in your Natively Dashboard.

  2. Upload your Service Account Credentials JSON. Follow RevenueCat's Google Play Store service credentials guide for the full walkthrough.

  3. Click Save changes.

  1. Enter the iOS app Bundle ID configured in your Natively Dashboard.

  2. Upload your In-app Purchase Key. Follow RevenueCat's In-App Purchase Key guide for the full walkthrough.

  3. Add your App Store Connect API key. Follow RevenueCat's App Store Connect API Key guide for the full walkthrough.

  4. Click Save changes.

The App Store Connect API key you already created for publishing your iOS app in the Natively Dashboard can be reused here - no need to generate a new one.

3

Set up your Product Catalog

Once your app is connected to each store, bring your products into RevenueCat and organize them.

1

Import your product(s)

  1. In RevenueCat, go to Product Catalog in the left sidebar, then click Products.

  2. Find your connected store (App Store or Play Store) and click + New.

  3. Click Import Products and select the products you created earlier in App Store Connect and/or Google Play Console.

  4. Click Import.

Repeat for each platform you support - iOS and Android products need to be imported separately, even if they represent the same offer.

Check RevenueCat's Import Products guide for the full walkthrough.

2

Create Entitlement(s)

Entitlements represent the access level a purchase unlocks in your app - for example premium, pro, or gold. Most apps need only one entitlement; create more only if you offer distinct tiers.

  1. Go to Product Catalog > Entitlements and click + New Entitlement.

  2. Give it an identifier (e.g. premium) and the display name, then click Add.

  3. Open the entitlement you just created. In the Associated products section, click Attach and select the products that should unlock it, including the equivalent product from each platform.

Check RevenueCat's Entitlements guide for the full walkthrough.

Avoid spaces in the identifier - use something like premium or pro_tier instead of Premium Access. The identifier is what your app's code checks against, so keeping it simple and consistent avoids issues later.

3

Create Offering(s)

Offerings control what your app actually displays to users, and let you change pricing, packaging, or messaging remotely - without shipping an app update.

  1. Go to Product Catalog > Offerings.

  2. RevenueCat automatically creates a default offering for you - you can use it, or click + New to create an additional one if you need multiple offerings (e.g., for different user segments).

  3. Open the offering and click Edit to add/change the Packages - these group equivalent products across platforms (e.g., a monthly subscription available on both iOS and Android) under a single identifier your app's code can use.

Check RevenueCat's Offerings guide for the full walkthrough.

RevenueCat provides predefined package identifiers for common cases (e.g. $rc_monthly, $rc_annual), or you can create your own custom identifier if none of the predefined ones fit.

4

Retrieve your Public API Keys

  1. In RevenueCat, go to API Keys in the left sidebar.

  2. Under SDK API Keys, copy the Public API key for each platform you support.

Natively Dashboard Setup

Before proceeding, make sure you have completed the RevenueCat Configuration steps above.

  1. Open your Natively app dashboard and navigate to Features > In-App Purchases.

  2. Toggle the feature to Enabled.

  3. Paste the Public API Keys you copied from RevenueCat into the respective fields:

    • iOS API Key - starts with appl_ .

    • Android API Key - starts with goog_.

  4. Click Save.

  5. Rebuild your app(s).

Implementation

Choose your integration method below: Bubble.io Plugin (No-Code), JavaScript SDK (Code), or AI Agents (for AI-powered editors like Lovable, Base44, and Replit).

Initialization

Check Plugin

Before starting, verify if the Natively plugin is already installed in your Bubble project.

  1. Open your Bubble editor and navigate to the Plugins tab in the left sidebar.

  2. Check Installed Plugins: Look through your list of installed plugins for "Natively iOS & Android app builder".

    • If it IS installed: Check the version number. If an update is available (e.g., you see a button saying "Update"), click it to ensure you have the latest features and bug fixes.

  • If it is NOT installed: Click the + Add plugins button, search for "Natively", and click Install.

Check SDK

Before writing any logic, ensure the Natively SDK is correctly installed and up-to-date in your codebase.

  1. Open your project's main HTML file (or header settings) and look for the Natively script tag inside the <head> section.

  2. Install/Update: If missing or outdated, add the following code. You can specify the SDK version in the URL (e.g., @2.26.0).

To ensure you are using the most up-to-date version, check the Natively GitHub releases page for the latest version number.

Initialize the SDK

AI-powered editors like Lovable, Base44, and Replit use the JavaScript SDK to implement Natively features. Before implementing any feature, the Natively SDK must be initialized in your project.

Copy the line below and paste it into your AI agent to check and set up the Natively SDK in your project.

Setup Logic

[Element] Natively - Purchases

Events:

  • Set Customer Success - fires when Set Customer ID completes successfully.

  • Set Customer Failed - fires when Set Customer ID fails.

  • Customer ID Received - fires when Get Customer ID returns a result.

  • Get Price Success - fires when Get Package Price completes successfully.

  • Get Price Failed - fires when Get Package Price fails.

  • Purchase Success - fires when a purchase completes successfully.

  • Purchase Failed - fires when a purchase fails due to an error.

  • Purchase Cancelled - fires when the user backs out of the purchase flow. Handle this separately from Purchase Failed - it isn't an error.

  • Restore Purchase Success [iOS] - fires when Restore Purchase successfully finds and restores prior purchases.

  • Restore Purchase Failed [iOS] - fires when Restore Purchase fails.

  • Paywall - purchase success - fires when the user completes a purchase through the paywall.

  • Paywall - cancelled - fires when the paywall is shown, and the user closes it without taking action.

  • Paywall - error - fires when the paywall was shown and an error occurred during an operation.

  • Paywall - was not presented - fires when the paywall doesn't display at all.

  • Paywall - Entitlement ID missing - fires only when using Show Paywall if needed without providing an Entitlement ID.

  • Paywall - Offering ID was not found - fires only when the provided Offering ID is invalid.

  • Paywall - restore purchase success [iOS] - fires when the user restores a purchase from within the paywall.

States:

  • Latest Transaction Id - the transaction ID after Purchase Package.

  • Latest Customer Id - the customer ID after Set Customer ID or Purchase Package.

  • Latest Error - empty if no error occurred.

  • Latest GetPrice price - the raw numeric price, e.g. 9.99.

  • Latest GetPrice price (Localized) - the localized price string, e.g. "$9.99" or "9.99 USD".

  • Latest GetPrice currency - the currency code, e.g. "USD", "UAH".

  • Latest PurchasePackage packageId - the package ID from the most recent purchase attempt.

Actions:

  • Purchase Package - initiates the purchase of a product or subscription:

    • Package ID - the package to purchase.

    • Old Product ID (Android) - required only when upgrading or downgrading an active Android subscription. This must be the real store Product ID, not a package ID - retrieve it via RevenueCat - Verify Subscription below.

    • Proration (Android) - controls exactly when the user is charged and how their billing cycle adjusts during an upgrade or downgrade. See RevenueCat's Proration documentation for a full breakdown.

  • Set Customer ID - links your user's ID to a RevenueCat customer:

    • Customer Identifier - recommended to use your Current User's unique ID, so purchases are tied to the same account across devices.

  • Reset Customer ID - unlinks the current Customer ID from this device.

  • Get Package Price - retrieves the price for a specific package.

  • Get Customer ID - retrieves the current customer ID.

  • Show Paywall - displays a RevenueCat paywall:

    • Offering ID - optional; shows this specific offering instead of the default.

  • Show Paywall if needed - displays a paywall only if the user doesn't already have the specified entitlement:

    • Offering ID - optional; shows this specific offering instead of the default.

    • Entitlement ID - required.

  • Restore Purchase [iOS] - restores previous purchases tied to the user's App Store account. RevenueCat handles the underlying restore logic automatically.

RevenueCat - Verify Subscription

Unlike the actions above, this checks entitlement status directly against RevenueCat's servers rather than through the Natively SDK - useful for confirming a user's real subscription state, or for retrieving the store Product ID needed for Android upgrades/downgrades.

This action requires your RevenueCat Secret API Key - a different key from the Public API Keys used in Natively Dashboard Setup. Add it as revenuecat_apiKey in the Bubble plugin settings. Find it under RevenueCat App > API keys > Secret API keys, and confirm you're using an API Key V1 - other versions won't work.

  • Customer ID - the RevenueCat user ID used during Set Customer ID. If no customer exists with this ID, RevenueCat creates a new one.

  • Entitlements ID - the entitlement identifier to check.

Returns:

  • Product ID - the store product ID tied to the active entitlement.

  • Purchase Date

  • Grace Period Expires Date

  • Expiration Date

  • Is Active - Yes/No. Yes - when Expiration Date is after the current date.

  • Error - empty text if no error occurred.

AI-powered editors like Lovable, Base44, and Replit use the JavaScript SDK to implement Natively features.

Copy the line below and paste it into your AI agent.

How to use

Know when Stripe is and isn't allowed

Apple and Google require digital goods, content, and subscriptions consumed within the app to go through their native purchase systems (IAP) - using Stripe or another processor for these will get your app rejected. The main exception is physical goods and real-world services (e.g. a taxi ride, a hotel booking, a physical product) - these can use Stripe or any other payment processor, even inside the app.

Only show IAP UI inside the Natively app

Use Browser Info to detect isNativeApp, and only show purchase/paywall UI when true. In a regular browser, use your web checkout flow instead (e.g., Stripe).

Log in before purchasing, restoring, or fetching offerings

Call login with a stable internal user ID as soon as the user is authenticated in your app - ideally the same ID you'd use across web and mobile. This ties purchases to the right customer in RevenueCat from the start, rather than to an anonymous ID that's harder to reconcile later.

Never trust the client alone for entitlement access

A SUCCESS purchase or restore callback confirms the purchase flow completed - it doesn't guarantee your backend has verified the entitlement yet. Always re-check subscription status server-side (via RevenueCat's REST API, or your own backend synced with RevenueCat) before unlocking premium features.

Handle CANCELLED differently from FAILED

A cancelled purchase means the user backed out - this isn't an error and usually shouldn't show error messaging. Reserve error UI for genuine FAILED results.

Always offer Restore Purchases, especially on iOS

Users reinstalling the app or switching devices need a way to recover purchases tied to their App Store / Google Play account without paying again.

Don't confuse Package ID with Store Product ID

packageId (e.g. $rc_monthly) is what you pass to purchasePackage for a normal purchase. But oldProductId, required for Android upgrades/downgrades, must be the real store product ID (e.g. com.company.app.premium:monthly) - retrieve it from platformProductId in getOfferings()'s response, not from a package ID.

Don't hardcode prices or product info

Use getOfferings() or packagePrice() to display live, localized pricing - prices vary by region and can change without a client update.

How to verify a user's subscription

Never rely on a purchase callback alone as proof of entitlement - always confirm with RevenueCat's own record of the customer's subscription state.

Use the RevenueCat - Verify Subscription action (documented in Setup Logic), which checks a customer's entitlement directly using your RevenueCat Secret API Key.

Setup example:

  1. On each user login or first signup, set the RevenueCat Customer ID to your own user's unique ID using the Set Customer ID action.

  2. Purchase a subscription (package) using the Purchase Package action.

  3. Verify the user's subscription whenever you need to check their entitlement status, using the RevenueCat - Verify Subscription action.

  4. If purchases aren't working, check the Latest Error state on the Natively - Purchases element for details.

  5. You can also use the RevenueCat API directly for other purposes, such as independent validation of purchases.

Step 1
Step 2
Step 3.1
Step 3.2
Step 4

Fetch the customer from RevenueCat's REST API using your Secret API Key and check whether the relevant entitlement is active.

This action requires your RevenueCat Secret API Key - a different key from the Public API Keys used in Natively Dashboard Setup. Find it under RevenueCat App > API keys > Secret API keys, and confirm you're using an API Key V1 - other versions won't work.

The response includes a subscriber.entitlements object, keyed by entitlement identifier. Check whether the entitlement you care about is present, and its expires_date is null or in the future:

For a more real-time, scalable setup, RevenueCat also supports webhooks that notify your backend the moment a subscription's state changes - useful if you want to keep your own database in sync rather than checking RevenueCat on every request. See RevenueCat's webhook documentation if you want to build this.

Testing

Test on Android - follow RevenueCat's Testing purchases in Play Store Sandbox guide for a full walkthrough.

Test on iOS - follow RevenueCat's Testing purchases in App Store Sandbox guide for a full walkthrough.

Troubleshooting

Purchases fail, or the paywall doesn't open

Go through this checklist:

  • Check the Debug Console. Open the Debug Console to inspect the actual error.

  • Confirm you're using the correct Public API Key. Use your App Store (appl_...) or Play Store (goog_...) key in the Natively Dashboard - not the RevenueCat Test Store key.

  • Confirm iOS payment setup is fully complete. Sandbox purchases fail if your Tax and Banking forms in App Store Connect aren't Clear, even with a signed Paid Applications Agreement.

  • Confirm Play Service Credentials have propagated. New Google Play Service Account credentials can show "Invalid Play Store credentials" for up to 36 hours after being generated.

Restore Purchases returns success, but the website doesn't reflect the access

A successful restore only confirms that RevenueCat recognized prior purchases; your app must still call your backend to re-verify entitlement status and update the UI accordingly. Don't unlock features directly from the restore callback alone.

Android upgrade/downgrade fails, or Google Play shows a duplicate subscription instead of a plan change

Confirm oldProductId is the real store Product ID (e.g. com.company.app.premium:monthly), not a package ID like $rc_monthly. Retrieve the correct value from platformProductId in getOfferings()'s response or from the RevenueCat Products section.

Not working in a web browser or the Natively Preview app

In-App Purchases is a native feature and does not work in a standard web browser or in the Natively Preview app. Test on a real device using a full Natively build.

Last updated