Stack recipe · first-party opinion

The subscription stack for Expo apps.

Our opinionated stack for a subscription Expo app: Expo and EAS own builds, updates, and submission; RevenueCat owns store purchases and receipt validation; Amba owns everything after the purchase — auth, entitlement state, segments, push campaigns, streaks, and analytics. An AI agent provisions the Amba layer over MCP in about ten seconds; the full wiring is a 30-minute job.

Get an API key Backend for Expoupdated June 12, 2026
the agent path:

This is a recipe, not a survey — and we are one of its three layers, so read it as a first-party opinion with the receipts shown. The division of labor is the whole idea: each layer does the thing it is genuinely best at, and the seams between them are explicit. Expo never touches your backend; RevenueCat never runs your engagement loop; Amba never validates a receipt. Features in your app check one thing — an Amba entitlement — so every layer composes through one explicit seam instead of fusing into a ball of vendor glue. Every command, tool call, and SDK line below is verified; the timed budget covers software wiring, not App Store paperwork.

who owns what

Which layer owns which job?

LayerOwnsYou configure it with
Expo + EASBuilds, over-the-air updates, store submission, development clients, push-token capture via expo-notificationsapp.json + eas.json; npx expo install
RevenueCatStore products, offerings and paywalls, App Store / Play receipt validation, subscription revenue analytics (MRR, churn)RevenueCat dashboard + react-native-purchases (public SDK key)
AmbaAuth and users, entitlement state the app reads, segments, push campaigns, streaks / XP / leaderboards, data collections, engagement analyticsMCP tool calls from your coding agent + @layers/amba-expo (client key)

The connective tissue is one webhook: RevenueCat reports subscription events to Amba, and Amba turns them into entitlements, segment membership, and push targeting. Details on exactly what syncs: /integrations/revenuecat.

the walkthrough

How do you wire it in 30 minutes?

  1. Provision the Amba layer

    ~1 min

    Point your coding agent at the hosted MCP server and have it sign up. amba_developer_signup mints a developer token and a provisioned project — client key and server key included — with no browser; the project goes active in about ten seconds.

    Agent connects + provisions

    $ claude mcp add --transport http amba https://mcp.amba.dev/mcp
    
    amba_developer_signup { "email": "you@app.com", "password": "<min 8 chars>" }
    → returns pat + project with client_key and server_key
  2. Install both SDKs

    ~3 min

    Two installs. The Amba Expo SDK is pure JS over HTTPS and runs in Expo Go; react-native-purchases is a native module, so purchases need a development build — that constraint is RevenueCat-on-Expo, not this stack.

    Install

    npx expo install @layers/amba-expo @react-native-async-storage/async-storage
    npx expo install react-native-purchases
  3. Configure both SDKs with one shared user identity

    ~5 min

    Configure Amba with the project keys the agent handed back, configure RevenueCat with its public SDK key, and use ONE stable user key on both sides: RevenueCat's app_user_id must equal the Amba user's external_id so webhook events resolve to the same person.

    App startup

    import { Amba } from '@layers/amba-expo';
    import Purchases from 'react-native-purchases';
    
    await Amba.configure({ projectId: 'proj_…', clientKey: 'amba_ck_…' });
    await Amba.auth.signInAnonymously();
    
    // One identity across layers: RevenueCat's app_user_id must match the
    // Amba user's external_id so subscription events land on the same person.
    Purchases.configure({ apiKey: '<RevenueCat public SDK key>' });
    await Purchases.logIn('<your stable user key — set as external_id in Amba>');
  4. Declare the entitlement contract in Amba

    ~5 min

    The agent defines the entitlement your app gates on and maps the store products that grant it. Features check the entitlement, never a product or a store — one seam, checked in one place, no matter how your catalog grows.

    Entitlement + product map

    amba_entitlements_define
      { "project_id": "proj_…", "key": "pro", "display_name": "Pro" }
    
    amba_products_create
      { "project_id": "proj_…", "product_id": "pro_yearly",
        "grants_entitlement_id": "pro",
        "store_product_refs": { "app_store": "com.yourapp.pro.yearly",
                                "play_store": "pro-yearly" } }
  5. Connect RevenueCat to Amba

    ~5 min

    amba_integrations_configure stores your RevenueCat secret API key plus a webhook secret and returns the exact webhook_url. Register that URL in RevenueCat's webhook settings with the same Authorization header value, then verify with amba_integrations_test. From now on every purchase, renewal, and expiration syncs server-side.

    The one webhook

    amba_integrations_configure
      { "project_id": "proj_…", "provider": "revenuecat",
        "config": { "secret_api_key": "<RC secret API key>",
                    "webhook_secret": "<long random string>" } }
      → webhook_url: https://api.amba.dev/webhooks/revenuecat?project_id=proj_…
    
    amba_integrations_test { "project_id": "proj_…", "provider": "revenuecat" }
  6. Sell with RevenueCat, gate with Amba

    ~5 min

    The paywall and purchase flow run on RevenueCat's SDK. Your feature gates read Amba — server-synced entitlement state, no receipt parsing in the app, survives reinstalls and device switches.

    Feature gate

    // Purchase happens through RevenueCat's SDK on device.
    // Gating reads Amba — synced server-side by the webhook.
    const isPro = await Amba.entitlements.has('pro');
    
    // After a reinstall or device switch, force a re-sync.
    await Amba.entitlements.restore();
  7. Wire the engagement loop

    ~6 min

    This is the part the other two layers do not do: a live "active subscribers" segment from the synced subscription properties, a push campaign targeting it, a streak to bring users back daily, and optionally a reward rule on entitlement.granted.

    Subscription state drives engagement

    amba_segments_create
      { "project_id": "proj_…", "name": "Active subscribers",
        "rules": { "operator": "AND", "conditions": [
          { "field": "properties.subscription_active", "op": "eq", "value": true }
        ] } }
    
    amba_push_campaigns_create
      { "project_id": "proj_…", "name": "Pro welcome",
        "title": "You're in", "body": "Pro is live on this device.",
        "segment_id": "<segment id>" }
    
    amba_streaks_create
      { "project_id": "proj_…", "name": "Daily practice",
        "qualifying_event": "session_completed" }
subtraction

What do you not need to build?

A server or a webhook handler
RevenueCat events land on a hosted Amba endpoint with dedup, ordering guards, and retries built in. There is no handler to write, no queue to run, nothing to deploy or babysit.
A receipt-validation service
RevenueCat owns store receipt validation end to end. Your app never parses a receipt, and your backend never talks to App Store or Play billing APIs.
A cron for subscriber lists
Segments re-evaluate when subscription state changes and on a schedule. "Lapsed subscribers" is a live cohort, not a nightly export.
A separate push provider
Campaigns, segment targeting, and scheduling are built into Amba, delivering over APNs and FCM. One fewer SDK, one fewer dashboard, one fewer bill.
A second pipeline for engagement analytics
Amba.events.track feeds funnels, retention, and segment rules in the same backend. RevenueCat keeps the revenue analytics — that split is intentional, not a gap.
the honest part

When is this stack wrong?

You sell nothing — drop RevenueCat
A free app, or one with no subscriptions, needs two layers, not three. Expo + Amba alone covers auth, data, push, and gamification; add RevenueCat the week you add a paywall.
Your revenue is web-first
This recipe is built around mobile store subscriptions. If most checkout happens on the web, the wiring differs — Amba ingests web subscription events through a separate integration — and parts of this page simply do not apply.
Your data model is deeply relational
If the product is hand-written SQL, joins, and reporting queries first, a SQL-first backend is a legitimate better fit. The trade-offs are laid out honestly at /compare/supabase rather than restated here.
You want one vendor on purpose
This stack is deliberately three services with explicit seams. If your organization optimizes for a single throat to choke over best-per-layer, an all-in-one platform will fit your constraints better than this recipe — that is a real trade, not a strawman.
the agent path

How much of this can an AI agent do for you?

Amba’s hosted MCP server at https://mcp.amba.dev/mcp (Streamable HTTP) exposes the whole backend as tool calls. amba_developer_signup mints a developer token and a provisioned project — client key and server key included — with no browser, and the project goes active in about ten seconds. The Amba layer is the agent-native one: provisioning, the entitlement contract, the webhook connection, segments, campaigns, and streaks above are all MCP tool calls. RevenueCat key creation and webhook registration happen in its dashboard, and Expo runs from its CLI — the agent walks you through both.

questions

Expo + RevenueCat + AmbaFAQ

What is the best stack for a subscription Expo app?

Our answer — and we are one layer of it, so weigh accordingly: Expo + EAS for builds and updates, RevenueCat for store purchases and receipt validation, Amba for everything after the purchase (auth, entitlements, segments, push, gamification). Each layer is best-in-class at its job, and the seams between them are one webhook and one shared user key.

Why add Amba instead of letting RevenueCat handle post-purchase too?

RevenueCat is deliberately a monetization layer, not an app backend. The post-purchase loop — feature gates your app reads, live subscriber segments, push campaigns, streaks, user data — needs a backend. Amba ships those as primitives wired to subscription state out of the box, so "lapsed subscribers get a win-back push" is two tool calls, not infrastructure you build.

Does this stack work in Expo Go?

Partially, and the limit is not Amba's. The Amba SDK is pure JavaScript over HTTPS — auth, data, events, entitlement reads all work in Expo Go. react-native-purchases is a native module, so purchases need a development build, and Expo Go cannot receive remote push (an Expo platform limit). Plan on a dev build once money or push enters the picture.

How do the user identities line up across the three layers?

One stable user key, used twice: set it as the Amba user's external_id and pass the same value to RevenueCat's logIn. Amba's webhook pipeline resolves RevenueCat's app_user_id against external_id, so every purchase, renewal, and expiration lands on the right person without any mapping table on your side.

What is genuinely not done in 30 minutes?

The store-side human floor. Creating store products, setting prices, signing agreements, banking and tax forms, and app review are dashboard work no API can do — for any stack. The 30-minute budget covers the software wiring: SDKs configured, entitlement contract declared, webhook connected, engagement loop live against sandbox purchases.

Can an AI agent set this whole stack up?

The Amba layer, yes — provisioning through engagement loop is MCP tool calls end to end, about ten seconds for the project itself. RevenueCat needs its dashboard for API keys and webhook registration, and Expo runs from its CLI. A capable agent drives all three and tells you exactly which two steps are yours.

start in 30 seconds

Hand the docs to your agent.
Ship by lunch.

Read the docs