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 Connect → Apps → + →
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_lifetimeUnder 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 YearOptionally 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:
| Key | Where | Used for |
|---|---|---|
| App Store Connect API key | Integrations → App Store Connect API | Validating product metadata, status, pricing (role: App Manager) |
| In-App Purchase key | Integrations → In-App Purchase | StoreKit 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.SoarStarterSwiftUpload 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_xxxxxxxxxxxxxxxxxCreate 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_lifetimeCreate 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 = proCreate 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_lifetimeMake 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:
| Field | Value |
|---|---|
| URL | https://<project-ref>.supabase.co/functions/v1/revenuecat-webhook |
| Authorization header | the 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_AUTHOnce 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.
