Mobile store billing
The server side of in-app purchases - the RevenueCat webhook, how a plan's source is decided, what the web checkout does about it, and the reconcile jobs.
Store purchases are an additive lane, never a replacement: a user may be billed by your web provider or by a store, and the same account and the same entitlements serve both. The lane ships only with --mobile-purchases revenuecat; the app's own side is on mobile billing.
The flag
config.payment.mobile is declared beside config.payment.enabled, not inside it, so it reads without narrowing on the web provider first.
| Key | Type | Default | Description |
|---|---|---|---|
enabled | boolean | false | Whether the store lane runs, and it needs payment.enabled. The CLI writes true for --mobile-purchases revenuecat |
provider | "revenuecat" | "revenuecat" | The store aggregator |
allowSandboxEvents | boolean | false | Whether a sandbox purchase takes effect on a deployment running as production. A non-production deployment accepts sandbox events already |
grantCreditsToFamilyShare | boolean | false | Whether a family-shared purchase grants credits |
externalPurchaseLinks | "off" | "us-only" | "off" | Whether the app may link out to your own checkout. "us-only" needs the US external-purchase entitlement and its store declarations |
customerCenter | boolean | true | Show RevenueCat's Customer Center for manage and cancel |
Environment
| Var | Purpose |
|---|---|
REVENUECAT_WEBHOOK_SECRET | Verifies the webhook signature |
REVENUECAT_AUTH_HEADER | The authorization header value RevenueCat is configured to send |
REVENUECAT_API_KEY | Server key for reconcile reads |
EXPO_PUBLIC_REVENUECAT_IOS_KEY, EXPO_PUBLIC_REVENUECAT_ANDROID_KEY | The app's public SDK keys, in apps/mobile/.env |
An empty REVENUECAT_WEBHOOK_SECRET never fails open: the webhook refuses every event rather than trusting an unverified one.
Who funds the plan
Every plan carries a source. It decides which surface may change it.
| Source | Bought through | Managed in |
|---|---|---|
stripe / polar | Your web checkout | Your billing settings |
revenuecat | Apple or Google, in the app | The store, or the Customer Center |
When the source is a store, the web billing page says the subscription is managed through the app store, hides subscribe and upgrade, and the API refuses a subscribe call for that user. That is a store requirement, not a preference: a web checkout offered over a store subscription double-bills the user.
Reconcile
Store state is authoritative and webhooks can be missed, so two background passes re-read it: a daily sweep over the lane, and a per-entity pass the webhook enqueues when one event implies another. Both are idempotent - see Background jobs.
POST /billing/mobile/syncis the third path: the app calls it after a purchase and after a restore, so a late webhook never leaves a paying customer at the paywall. It answers 503 when the lane is off orREVENUECAT_API_KEYis blank.
Organizations
Multi-tenant organizations with member roles, email invitations, and shared workspaces funded by the organization owner's personal balance.
Projects
The project entity that scopes a user's work inside their account or inside their active organization - ownership, the API, config.projects.enabled, and the useActiveProject hook.