RevenueCat Setup

Dashboard-side setup: App Store Connect products, RevenueCat offering, and the webhook.

Before the paywall can show packages and complete purchases, you configure the two dashboards behind it: App Store Connect (the products Apple sells) and RevenueCat (the catalog + offering the app reads, plus the webhook that writes your subscriptions table). This is all account-side work — no code changes beyond three keys.

You need a paid Apple Developer account and the app's bundle id (com.soarstarter.SoarStarterSwift by default). All product/offering work happens in the browser; the app only needs REVENUECAT_IOS_KEY, REVENUECAT_PRO_ENTITLEMENT_ID, and the webhook secret.

1. App Store Connect: create the app & clear paid-app requirements

Create the app record

In App Store ConnectApps+New App: pick iOS, enter the name, select the bundle id Xcode uses, and set an internal SKU (e.g. soar-ios). If the bundle id is missing, create it first in Apple Developer under Certificates, Identifiers & Profiles → Identifiers.

Complete Paid Apps agreements

Under Business (or Agreements, Tax, and Banking), complete every paid-app prerequisite: the Paid Apps agreement, a bank account, and tax forms. Products can't be fetched by StoreKit until these are done — this is the single most common cause of an empty paywall.

2. Create the products

Create three in-app purchase products with these exact IDs (the paywall renders whichever subset the offering contains, so you can ship fewer):

Under Monetization → In-App Purchases, add a Non-Consumable:

Type: Non-Consumable
Reference Name: SoarStarter Swift Lifetime
Product ID: soar_starter_swift_lifetime

Under Monetization → Subscriptions, create a subscription group (e.g. SoarStarter Pro), then add two auto-renewable subscriptions inside it so Apple can handle upgrades/downgrades and proration:

Reference Name: SoarStarter Pro Monthly
Product ID: soar_starter_swift_pro_monthly
Duration: 1 Month

Reference Name: SoarStarter Pro Yearly
Product ID: soar_starter_swift_pro_yearly
Duration: 1 Year

Optionally add an introductory free trial on either product.

Fill each product's required metadata — price, display name, description, review screenshot, and Cleared for Sale. Each then moves from Missing Metadata to Ready to Submit.

Ready to Submit is the expected state before your first App Review submission — it is not an error, and it's sufficient for sandbox purchase testing.

3. Create the two App Store Connect keys

RevenueCat needs two different keys — don't confuse them. Both are created under Users and Access → Integrations:

KeyWhereUsed for
App Store Connect API keyIntegrations → App Store Connect APIValidating product metadata, status, pricing (role: App Manager)
In-App Purchase keyIntegrations → In-App PurchaseStoreKit 2 transaction processing

Download each .p8, and copy its Key ID and Issuer ID. If RevenueCat's Products page later shows Could not check, the App Store Connect API key is missing or under-privileged.

4. Configure the RevenueCat app

Add the App Store app

In RevenueCat → Apps & providers → add an App Store app. Set the bundle id exactly:

com.soarstarter.SoarStarterSwift

Upload the In-App Purchase key and add the App Store Connect API credentials, then save. Copy the public App Store SDK key — it starts with appl_:

REVENUECAT_IOS_KEY = appl_xxxxxxxxxxxxxxxxx

Create products

Under Product catalog → Products, create App Store products with the exact IDs from step 2:

soar_starter_swift_pro_monthly
soar_starter_swift_pro_yearly
soar_starter_swift_lifetime

Create the entitlement

Under Product catalog → Entitlements, create an entitlement and attach all three products so any purchase grants access. Copy its identifier into Secrets.xcconfig:

REVENUECAT_PRO_ENTITLEMENT_ID = pro

Create the offering

Under Product catalog → Offerings, create the offering default and add the packages. The paywall sorts them Monthly → Yearly → Lifetime and pre-selects Yearly:

Package type: Monthly   Product: soar_starter_swift_pro_monthly
Package type: Annual    Product: soar_starter_swift_pro_yearly
Package type: Lifetime  Product: soar_starter_swift_lifetime

Make sure default is the active/current offering (shown by a blue check in RevenueCat's newer UI). The app reads offerings.current, so an inactive offering yields an empty paywall.

Entitlement identifier ≠ display name. The app matches REVENUECAT_PRO_ENTITLEMENT_ID against the entitlement's identifier (the slug under Entitlements), not its display name. Mismatch here is the classic "purchase succeeds but nothing unlocks" bug.

5. Wire the webhook → your Supabase table

This is what makes entitlement account-bound. RevenueCat POSTs purchase events to your revenuecat-webhook Edge Function, which writes the subscriptions row. In RevenueCat → Integrations → Webhooks, add:

FieldValue
URLhttps://<project-ref>.supabase.co/functions/v1/revenuecat-webhook
Authorization headerthe same value you set as the REVENUECAT_WEBHOOK_AUTH Edge secret

The function authenticates by comparing the Authorization header to REVENUECAT_WEBHOOK_AUTH (timing-safe) — there's no body HMAC. It's deployed with --no-verify-jwt because RevenueCat isn't a signed-in user. Deploy it and set the secret from Supabase Setup.

Identity binding closes the loop: the app logs into RevenueCat with the Supabase user UUID, so every webhook event carries app_user_id == subscriptions.user_id. Events with a non-UUID app_user_id (anonymous customers) are ignored by the handler.

Expected end state

App Store app   → bundle id com.soarstarter.SoarStarterSwift, SDK key appl_...
Products        → 3 App Store products, status Ready to Submit (or valid)
Entitlement     → identifier "pro", all 3 products attached
Offering        → "default", active, packages Monthly/Annual/Lifetime
Webhook         → https://<ref>.supabase.co/functions/v1/revenuecat-webhook
                  Authorization = REVENUECAT_WEBHOOK_AUTH

Once Apple finishes syncing products (can take 15 min to a few hours), a TestFlight build loads offerings and shows the plans. Verify it end-to-end in Purchase Testing.

On this page