EasyStarter logoEasyStarter

RevenueCat

Configure iOS and Android subscriptions, in-app purchases, and Webhooks

RevenueCat In-App Purchases

EasyStarter's mobile app uses RevenueCat to manage iOS and Android subscriptions and in-app purchases. RevenueCat handles the App Store / Google Play integration and syncs subscription status to the server via Webhook.

Prerequisites

Make sure the following accounts and setup are ready before starting:

  • RevenueCat account (free to start)
  • iOS: Apple Developer account, with in-app purchase products created in App Store Connect
  • Android: Google Play Console account, with subscription products created

Prerequisites: Create Store Products

Before configuring RevenueCat, create subscriptions and in-app purchase products in App Store Connect and Google Play Console, and note each product's Product ID.

See the Create Store Products guide for detailed steps.

Configure RevenueCat

Sign in to RevenueCat and follow these steps to complete the backend configuration.

1. Create an App

Go to Project Settings → Apps and click New app configuration, then select your platform (App Store for iOS, Google Play for Android).

Click Save changes when done.

2. Import Products

Products are RevenueCat's mapping to the specific items you created in App Store Connect or Google Play Console.

Go to Product catalog → Products and click Import in the top-right corner. RevenueCat will automatically fetch the product list from your connected store. Select all the products you need (monthly subscription, annual subscription, lifetime purchase, etc.) and click Import.

If the import returns an empty list, the store credentials may not have propagated yet, or the products are not in a submittable state. iOS products must be in "Ready to Submit" status or higher before RevenueCat can fetch them.

Once imported, each Product will show its Product Identifier — the same product ID from App Store Connect or Google Play. Take note of these; you will need them in app-config.ts.

3. Configure Offerings

Offerings define the purchase options presented to users. Each Offering contains one or more Packages, where each Package maps to a specific Product.

  1. Go to Product catalog → Offerings. RevenueCat creates a default Offering automatically.
  2. Click default to open its detail page, then click Add Package to add the following three packages:
    • Monthly — Package Type: Monthly, link to your monthly subscription Product
    • Yearly — Package Type: Annual, link to your annual subscription Product
    • Lifetime — Package Type: Lifetime, link to your lifetime purchase Product
  3. For each Package, select the corresponding Product for iOS and Android in the right panel, then click Save.

Offerings control which purchase options appear in the app. You can create multiple Offerings for A/B testing, but the SDK defaults to the default Offering.

4. Configure Entitlements

Entitlements define what access a user receives after a purchase — typically one Entitlement per app, for example pro.

  1. Go to Product catalog → Entitlements and click New in the top-right corner. Enter an Identifier (e.g. pro) and click Save.
  2. Open the Entitlement's detail page and click Attach. In the product list, select all Products that should grant this entitlement (Monthly, Yearly, and Lifetime) and click Attach.

The Entitlement Identifier must match the EXPO_PUBLIC_REVENUECAT_ENTITLEMENT_ID value in eas.json. If you use pro, set the environment variable to pro.

Once configured, whenever a user purchases any attached Product, RevenueCat automatically marks this Entitlement as active. The app uses the SDK to check whether the user has an active Entitlement and gates premium features accordingly.

5. Configure a Paywall (optional)

RevenueCat includes a visual paywall editor that lets you update your in-app purchase screen without releasing a new app version.

Go to Paywalls and click New paywall. Choose a template to start editing:

  • Select the Offering to bind (choose default)
  • Enter a paywall name
  • Set the URLs for your Privacy Policy and Terms of Service

Per App Store Review Guidelines, the in-app purchase screen must include links to your Privacy Policy and Terms of Service, or your app risks rejection.

If the chosen template has no Lifetime Package slot, duplicate an existing Package block, change its type to Lifetime, and update the display copy. When finished, click Publish to make the paywall live in your app.

For more customization options and variables, see the RevenueCat paywall variables docs.

6. Get API Key and Entitlement ID

SDK API Keys: Go to Project Settings → API Keys. Find the Public SDK key for each platform (iOS keys start with appl_, Android with goog_), click Show Key → Copy, and paste them into EXPO_PUBLIC_REVENUECAT_IOS_API_KEY and EXPO_PUBLIC_REVENUECAT_ANDROID_API_KEY in eas.json.

Secret API Key: On the same page, find the Secret keys section. Copy the secret key and add it to the server environment variable REVENUECAT_SECRET_API_KEY (used for server-side subscription lookups).

Entitlement ID: Go to Product catalog → Entitlements, copy the Identifier of your Entitlement (e.g. pro), and paste it into EXPO_PUBLIC_REVENUECAT_ENTITLEMENT_ID in eas.json.

7. Configure Webhook (sync subscription status to the server)

RevenueCat pushes subscription events (purchase, renewal, cancellation, etc.) to the server in real time via Webhook. EasyStarter's server already includes the handler — you just need to configure the Webhook URL.

Development (ngrok)

In local development, the server runs on localhost which RevenueCat cannot reach directly. Use ngrok to expose your local port to the internet.

  1. Install ngrok (if not already installed):

    brew install ngrok
  2. Start the local server:

    pnpm dev:server

    The server listens on http://localhost:3001 by default.

  3. Open a ngrok tunnel:

    ngrok http 3001

    ngrok will output a public URL, for example:

    Forwarding  https://a1b2-123-456-789.ngrok-free.app -> http://localhost:3001
  4. In the RevenueCat Dashboard → Project Settings → Integrations → WebhooksAdd webhook, set the Webhook URL to:

    https://a1b2-123-456-789.ngrok-free.app/api/webhooks/revenuecat
  5. In the Authorization header field, enter a random secret you generate yourself. Use the full Bearer Token format:

    # Generate a random value
    openssl rand -hex 32

    Prefix the output with Bearer , e.g. Bearer a1b2c3d4..., then paste that complete value into the RevenueCat Authorization header field. Add the same full value to apps/server/.dev.vars:

    apps/server/.dev.vars
    REVENUECAT_WEBHOOK_SECRET=Bearer your_random_secret

REVENUECAT_WEBHOOK_SECRET is a value you generate and configure yourself — it is not a secret issued by RevenueCat. RevenueCat will forward this exact value as the Authorization request header on every webhook call; your server then validates it. Using the full Bearer xxx form is recommended for clarity and to avoid misconfiguration.

The free ngrok tier generates a new URL on every restart — remember to update the Webhook URL in RevenueCat when that happens. Sign up for a ngrok account to get a persistent domain if you debug webhooks frequently.

Production

Once deployed to Cloudflare Workers, the server has a fixed public URL and can be configured directly.

  1. In the RevenueCat Dashboard → Project Settings → Integrations → WebhooksAdd webhook, set the Webhook URL to:

    https://your-server.workers.dev/api/webhooks/revenuecat
  2. In the Authorization header field, enter a random secret you generate yourself (same format as development):

    openssl rand -hex 32

    Prefix the output with Bearer , e.g. Bearer a1b2c3d4..., paste it into the RevenueCat Authorization header field, and add the same full value to apps/server/.env.production (or manage it via wrangler secret):

    apps/server/.env.production
    REVENUECAT_WEBHOOK_SECRET=Bearer your_random_secret
  3. Push secrets to Cloudflare:

    pnpm -F server secrets:bulk:production

Update eas.json environment variables

Fill in the API Keys and Entitlement ID in the env block of each build profile in apps/native/eas.json:

apps/native/eas.json
{
  "build": {
    "development": {
      "env": {
        "EXPO_PUBLIC_SERVER_API_URL": "https://your-server.workers.dev",
        "EXPO_PUBLIC_WEB_APP_URL": "https://your-app.com",
        "EXPO_PUBLIC_REVENUECAT_IOS_API_KEY": "appl_xxxxxxxxxxxxxxxx",
        "EXPO_PUBLIC_REVENUECAT_ANDROID_API_KEY": "goog_xxxxxxxxxxxxxxxx",
        "EXPO_PUBLIC_REVENUECAT_ENTITLEMENT_ID": "pro"
      }
    },
    "production": {
      "env": {
        "EXPO_PUBLIC_SERVER_API_URL": "https://your-server.workers.dev",
        "EXPO_PUBLIC_WEB_APP_URL": "https://your-app.com",
        "EXPO_PUBLIC_REVENUECAT_IOS_API_KEY": "appl_xxxxxxxxxxxxxxxx",
        "EXPO_PUBLIC_REVENUECAT_ANDROID_API_KEY": "goog_xxxxxxxxxxxxxxxx",
        "EXPO_PUBLIC_REVENUECAT_ENTITLEMENT_ID": "pro"
      }
    }
  }
}

EXPO_PUBLIC_ variables are injected into client-side code by Expo. RevenueCat SDK keys are public client credentials — it is safe to include them here.

Update app-config.ts pricing config

packages/app-config/src/app-config.ts defines the pricing plans shown in the app under native.payments. The providerPriceId values must exactly match the product IDs in App Store Connect / Google Play Console:

packages/app-config/src/app-config.ts
native: {
  payments: {
    enabled: true,
    provider: "revenuecat",
    ios: {
      plans: [
        {
          id: "pro",
          prices: [
            {
              id: "monthly",
              provider: "revenuecat",
              providerPriceId: "easystarternative_10_1m", // App Store Connect product ID
              currency: "usd",
              amountCents: 1000,
              priceType: "subscription",
              interval: "month",
              status: "active",
            },
            {
              id: "yearly",
              provider: "revenuecat",
              providerPriceId: "easystarternative_100_1y",
              currency: "usd",
              amountCents: 10000,
              priceType: "subscription",
              interval: "year",
              status: "active",
            },
          ],
        },
        {
          id: "lifetime",
          prices: [
            {
              id: "lifetime",
              provider: "revenuecat",
              providerPriceId: "easystarternative_299_lifetime",
              currency: "usd",
              amountCents: 29900,
              priceType: "lifetime",
              status: "active",
            },
          ],
        },
      ],
    },
    android: {
      plans: [
        {
          id: "pro",
          prices: [
            {
              id: "monthly",
              provider: "revenuecat",
              providerPriceId: "pro_monthly_android", // Google Play Console product ID
              currency: "usd",
              amountCents: 800,
              priceType: "subscription",
              interval: "month",
              status: "active",
            },
          ],
        },
      ],
    },
  },
},
FieldDescription
providerPriceIdMust exactly match the product ID in App Store Connect / Google Play Console
amountCentsPrice shown in the app UI, in cents ($10.00 = 1000)
priceTypesubscription or lifetime (one-time purchase)
statusactive to show the product, inactive to hide it

Server-side variables

The server also needs two RevenueCat variables to handle Webhooks and server-side subscription queries:

apps/server/.env.production
REVENUECAT_SECRET_API_KEY=          # RevenueCat Secret API Key (for server-side subscription lookups)

Find REVENUECAT_SECRET_API_KEY in the RevenueCat Dashboard → your project → API Keys → Secret keys.