# Connect RevenueCat to Appbase **Both app code and dashboard configuration are needed.** Our SDK measures first opens and behavior. RevenueCat delivers verified billing events to our backend. The identity bridge connects those two histories, so trial outcomes and first payments appear against the customer's original first-open date. The separate [Revenue section](revenue.md) sums billing for all customers and does not depend on SDK identity matching. The identity bridge remains necessary for New business and behavioral attribution. Automatic setup imports available historical billing events, product names/terms and subscription snapshots into our database. Reports read those stored records alongside incoming webhooks; webhook delivery alone does not establish historical completeness. ## Automatic setup Open **App setup → Connect automatically**, or follow the connection link your coding agent provides. Click **Connect RevenueCat** and authorize Appbase in RevenueCat. Choose **Only selected RevenueCat projects** to limit access to the project(s) you want to connect. **Read & Write** works. For narrower access, choose **Custom** and keep Projects, Apps, Products, Customers and Subscriptions on **Read**, and Integrations on **Read & Write** so Appbase can create its webhook. **Read only** cannot create the webhook: Appbase explains the missing permissions and asks you to authorize again. Then return to Appbase and choose your store app. No copied provider keys or webhook secrets are needed for this path. Alternatively, expand **Use a secret API key instead** and enter your project ID and secret v2 key directly into Appbase, never into agent chat. For this automatic path, the key needs: - `project_configuration:apps:read` - `project_configuration:products:read` - `project_configuration:integrations:read_write` (to create Appbase's webhook) - `customer_information:customers:read` - `customer_information:subscriptions:read` Choose the store app. Appbase creates a webhook with a generated authorization secret and maps **both** analytics environments automatically. Test Store goes to Development only. If RevenueCat supplies a signing secret, Appbase stores it encrypted and verifies signatures too; signing is optional and is not enabled by the current automatic path. Retries retain any existing signing requirement and recover the same receiver and webhook. Use **Connect another store** for the other mobile platform. Existing manual or paused connections are preserved and must be managed through their existing setup controls. After connecting the webhook, Appbase attempts the initial history import (up to two minutes of provider reads). The success screen separately shows webhook delivery and history-import status. Use **Import available history** to retry a failed or older connection, or **Refresh history** to repeat a successful import. A failed import keeps the webhook connected and preserves the previous snapshot. Repeated imports and matching webhook transactions do not double-count revenue. The success screen distinguishes configuration from first delivery. Your agent sees that safe status and the project/app identifiers, not your provider keys or OAuth tokens. The agent still needs to link RevenueCat identity and purchase transaction IDs in your app as described below. Provider credentials are encrypted server-side; OAuth access refreshes on demand. Revoked/expired access requests reconnection. Disconnect provider access clears the saved API/OAuth credentials; the webhook and stored analytics history remain. Revoke access in RevenueCat as well when appropriate. ## Manual setup (alternative) Open **App setup → Configure webhook manually**. Enter your RevenueCat project ID, app ID and store. The selected analytics environment determines the mapping: Development → SANDBOX; Production → PRODUCTION. Test Store is development-only. Add each platform and environment you use. Connections are reused within your account by project ID. The dashboard creates the connection and mapping, then shows a webhook URL and one-time authorization value. Save that value before leaving. Existing connections offer explicit authorization replacement; it invalidates the previous value, so update RevenueCat immediately. An optional secret v2 API key enables history/subscription sync. Grant read access to product configuration and customer information. The service checks access to your project’s products before saving; sync separately checks the remaining permissions. If you enable webhook signing in RevenueCat, also save its signing secret here. Keys are encrypted with AES-256-GCM on the server, bound to their connection and purpose, and never returned. Manage credentials lets you replace/remove them. Webhook delivery itself needs no REST API key. ## Before you start - Initialize React Native using the [quickstart](quickstart.md), or native Swift using the [Swift guide](swift-sdk.md) and its stated release availability. This supplies first opens and the analytics identity. - Configure purchases, products and offerings in your app's existing RevenueCat integration. A native Expo development build is needed to verify purchases; Expo Go is insufficient. The lab already has this setup. - Find the RevenueCat **project ID** and each RevenueCat **app ID**. These are different from public SDK keys, bundle IDs, store product IDs and our internal analytics app UUID. - Start with sandbox/Test Store purchases and our **development** collection key. Production store transactions belong to the **production** dataset. Choose based on the transaction environment, not just the build's `__DEV__` flag. - Have an HTTPS API endpoint reachable by RevenueCat. Appbase uses `https://api.appbase.so/v1/webhooks/revenuecat/CONNECTION_ID`; copy the exact URL from App setup. Your dashboard's localhost address cannot receive remote webhooks. ## 1. App code: link the customer identities Keep your existing Purchases configuration and purchase code. After both SDKs initialize, record RevenueCat's current customer ID: ```ts // analytics is your shared Appbase instance. // Purchases is your already configured react-native-purchases instance. const syncRevenueCatIdentity = () => analytics.linkRevenueCatUser({ projectId: 'YOUR_REVENUECAT_PROJECT_ID', // Project ID, not an API key. getAppUserId: () => Purchases.getAppUserID(), }); await syncRevenueCatIdentity(); ``` Use a fresh lookup inside the callback. The helper records a durable SDK event; it does not guarantee delivery. Normal SDK flushing/retries deliver it. A null result means no link was recorded; inspect SDK diagnostics. Tracking failures should not prevent purchases or app startup. Run this bridge at these points, serializing account changes in your app: | When | What to do | | ------------------------------------- | ----------------------------------------------------------------------------------------------- | | Initial launch / restored app session | Initialize both SDKs, restore any analytics account identity, then sync. | | Successful RevenueCat login | Identify the same opaque app account with `analytics.identify(user.id)`, then sync. | | Switching accounts | Reset analytics before identifying the next account; sync after the RevenueCat change succeeds. | | Successful RevenueCat logout | Call `analytics.reset()`, then sync the new anonymous RevenueCat customer. | | Successful restore | Sync the current customer again. Do not automatically merge app accounts. | For a native Swift app with RevenueCat already configured: ```swift import RevenueCat await analytics.linkRevenueCatUser(projectId: "YOUR_REVENUECAT_PROJECT_ID") { await MainActor.run { Purchases.shared.appUserID } } await analytics.flush() ``` The lookup obtains the current ID inside the captured analytics identity window. Use the same lifecycle/account ordering in the table above. Swift paywall handles expose `purchaseStarted` and `finished`; see the Swift SDK guide for the typed equivalents of the React Native examples. RevenueCat documents `Purchases.shared.appUserID` in its [identity guide](https://www.revenuecat.com/docs/customers/identifying-customers). The SDK does not add RevenueCat as a dependency or configure purchases for you. Anonymous users work too. Keep RevenueCat's own App User IDs; do not replace them with our anonymous UUIDs. Do not put login/logout calls inside the lookup callback. The [SDK identity reference](sdk.md#revenuecat-identity-link-optional) explains race protection and diagnostics; [RevenueCat's identity guide](https://www.revenuecat.com/docs/customers/identifying-customers) explains its customer IDs and aliases. You do **not** need to emit a separate analytics event for every trial, renewal or payment. Webhooks supply those facts. For paywall-level conversions, also pass `purchased.transaction.transactionIdentifier` to `attempt.finished({ result: 'succeeded', transactionId })` after the actual purchase. At presentation, supply the optional `view({ accessState })` snapshot from the current customer's access: active (including trials), inactive, or unknown. Keep recording subscriber views; reporting excludes them from acquisition conversion. Missing/stale CustomerInfo must not silently become inactive. See the [paywall guide](paywalls.md#existing-subscribers-and-eligibility). Successful callbacks alone are not revenue. ## 2. Analytics backend: create a connection and map its apps These are trusted server-side calls to the **API origin**. With hosted InsForge accounts, send `Authorization: Bearer ACCOUNT_ACCESS_TOKEN`, using the verified dashboard account's access token from its normal authenticated session. The API assigns the connection to that account and only permits mappings to apps it owns. Never put this token in the mobile app, public snippets, chat, or RevenueCat. Do not extract or copy browser session cookies for setup; the dashboard server already holds the authenticated session. The dashboard handles these calls through its authenticated server actions; manual API calls are optional for automation. InsForge mode rejects the old shared `REPORTING_TOKEN`; that token applies only to the separate legacy local backend. Apply database migrations first if this is a fresh backend. First list `GET /v1/admin/revenuecat/connections`. Reuse the connection for the same project; one connection can serve multiple mapped apps/environments. If none exists, create one: ```http POST /v1/admin/revenuecat/connections Content-Type: application/json {"name":"My app billing","project_id":"YOUR_REVENUECAT_PROJECT_ID"} ``` Save the returned `connection.id`, `webhook_path` and one-time `authorization` privately. The authorization includes its `Bearer ` prefix. Existing connections do not reveal it again; consult saved configuration before considering a credential rotation. For each provider app/store/environment, add its exact destination: ```http POST /v1/admin/revenuecat/connections/CONNECTION_ID/mappings Content-Type: application/json { "provider_app_id": "YOUR_REVENUECAT_APP_ID", "store": "TEST_STORE", "environment": "SANDBOX", "app_id": "YOUR_ANALYTICS_DEVELOPMENT_APP_UUID" } ``` Use `APP_STORE` or `PLAY_STORE` for platform purchases. `TEST_STORE` only supports `SANDBOX`. Sandbox maps to development; `PRODUCTION` maps to the separate production analytics UUID. Repeat for iOS/Android as needed. Existing mappings cannot be silently moved to another dataset. Previously unmapped receipts require explicit routing replay after adding a mapping; see the [owner API](api.md#revenuecat-webhook-inbox). ## 3. RevenueCat dashboard: configure delivery In the project, open **Integrations → Webhooks** and add a configuration: | Setting | Value | | -------------------- | ----------------------------------------------------------- | | URL | Public HTTPS **API** origin + the returned `webhook_path` | | Authorization header | Our returned `authorization`, verbatim, including `Bearer ` | | App / environment | The apps and transaction environments mapped in step 2 | | Event types | All events, including lifecycle changes | For hosted accounts, enter the signing secret in App setup → Connect RevenueCat → Manage credentials. Authorization remains required. Configure signing consistently on both sides; incorrect or missing signatures are rejected. Customer accounts cannot reference server environment variables. On the **legacy local backend only**, copy a signing secret into the API server environment as, for example, `REVENUECAT_SIGNING_SECRET_MY_APP`. Restart the API to load it, then configure the connection using the local reporting token: ```http POST /v1/admin/revenuecat/connections/CONNECTION_ID/settings Content-Type: application/json {"signing_secret_env":"REVENUECAT_SIGNING_SECRET_MY_APP"} ``` Send the environment-variable **name**, not its secret value. Configure signing on both sides before verification. Authorization remains required even with signing. See [RevenueCat webhook settings](https://www.revenuecat.com/docs/integrations/webhooks). ### Local development Run `node scripts/revenuecat-webhook-proxy.mjs CONNECTION_ID` from this repository, then point an HTTPS tunnel at `http://127.0.0.1:4401`. Use the tunnel origin plus `webhook_path`. The proxy exposes only this connection's webhook path. If the tunnel URL changes, update RevenueCat. Use a durable HTTPS API host for continuous delivery. ### Which credentials go where? | Value | Location | | ------------------------------- | --------------------------------------------------------------- | | Our public collection key | App; selects its analytics dataset | | RevenueCat public SDK key | App's Purchases configuration | | RevenueCat project ID | Identity bridge and backend connection; not a secret | | Dashboard account access token | Dashboard server / trusted authenticated API client only | | Legacy owner reporting token | Separate legacy local backend only; rejected on hosted accounts | | Generated webhook authorization | RevenueCat webhook configuration; keep private | | Webhook signing secret | Encrypted server-side storage; keep private | ## 4. Verify the complete path Use the development dataset and sandbox/Test Store before enabling production: 1. Send a RevenueCat test webhook. Confirm successful delivery and a `test` receipt in `GET /v1/admin/revenuecat/connections/CONNECTION_ID`. This proves transport/authentication/storage only. 2. Launch the app with a fresh test customer, allow its SDK first-open and identity-link events to flush, then make a sandbox purchase or start a trial through RevenueCat. **Check for events** confirms SDK delivery only. 3. Find the actual purchase receipt in connection status. Check `GET /v1/admin/revenuecat/connections/CONNECTION_ID/receipts/EVENT_ID/identity`: expect `matched` for exactly one analytics subject. URL-encode the provider event ID. 4. Refresh **New business**, selecting the correct app/environment and a range containing the customer's first open. Confirm trials, direct buyers and first-purchase revenue. Inspect exact products in **Revenue → By product** or **Subscriptions**. A zero-price trial is not a paying customer yet. 5. Check the relevant onboarding or in-app paywall: the exact presentation should show a trial/direct payment after both SDK and provider evidence arrive. Expand Tracking coverage if it remains unmatched. 6. After the trial's first payment arrives, verify its original first-open cohort **and original paywall** change from pending to paid. A confirmed expiration can move it to ended without payment; disabling renewal alone does not. Later renewals should increase Revenue without adding paywall conversions. The connection status's legacy `financial_reporting: unavailable` field describes the inbox endpoint, not the acquisition report. Read the app report's `acquisition.status` and coverage for dashboard availability. A mapped connection alone does not prove that delivery and identity linking work. ## If something is missing - **Webhook fails:** check HTTPS reachability, the exact returned path, authorization and signing configuration on both sides. Local tunnels may have stopped. - **Unmapped receipt:** verify provider app ID, store and transaction environment, then replay that receipt after fixing the mapping. - **Unmatched identity:** check the project ID, current RevenueCat ID and delivery of the SDK link in the same analytics dataset. Late links resolve on the next report read. Conflicts are excluded rather than guessed. - **No matching first open:** the billing customer needs an observed SDK first-open history. A restore or existing subscription is not a new customer. - **Pending trial after its expected end:** wait for verified billing/expiration evidence. Time passing alone does not establish the outcome. - **Paywall conversion missing:** check the exact purchase transaction ID and product ID, customer identity link, and full paywall/attempt context. Multiple presentations claiming the same transaction stay unattributed. Older SDK successes without transaction IDs cannot be linked by a billing sync alone. - **Unexpected totals:** select the customer's first-open date, not just the payment date, and inspect the metric information tooltips. New-business revenue excludes renewals; Revenue uses transaction dates and includes renewals. Current reports show **observed, partial history**. Webhook setup alone does not import past purchases or reconcile missing events. Exact paywall attribution is available when the app supplies transaction and identity evidence. The history import recovers available history and catalog metadata; full export reconciliation remains future work. See [New business](new-business.md) for current scope and [HTTP API](api.md) for request/response details. ## Historical import and refresh No extra mobile SDK code is required for this part. Keep live webhooks enabled, then: Automatic OAuth/API-key connections use their encrypted stored access and import once during setup; no additional key is needed. Existing connections can use **Import available history** on their connection page. Manual webhook connections need a separate read-only credential: 1. In the same RevenueCat project, open **API keys → New secret API key**. Choose **V2** and read-only access to **Customers**, **Subscriptions** and **Products**. Leave write permissions off. These are `customer_information:customers:read`, `customer_information:subscriptions:read` and `project_configuration:products:read`. 2. Save the key in **App setup → Connect RevenueCat** (or **Manage credentials** on an existing connection). It stays encrypted on the API server. Never put it in mobile code or public environment variables. 3. Select **Import / refresh RevenueCat data**. A successful import updates products, subscription snapshots and the history RevenueCat exposes. A failure preserves the previous snapshot and shows a safe error. Fix permissions and retry if needed. Automation may POST `{ "api_key": "sk_...", "signing_secret": "..." }` to `/v1/admin/revenuecat/connections/:id/credentials` using an account-authorized server session. Omitted fields are unchanged; null removes a field and its legacy fallback. Values are never returned. Do not include secrets in shared prompts or logs. For operators on the **legacy local backend**, run from the repository root (these package commands load the local `.env`): ```bash pnpm revenuecat:sync CONNECTION_UUID REVENUECAT_API_KEY_MY_APP # Subsequent syncs reuse the saved key reference: pnpm revenuecat:sync CONNECTION_UUID ``` The CLI can attach the key reference and run the sync directly. The dashboard API equivalent is `POST /v1/admin/apps/APP_UUID/revenuecat-sync` with an empty JSON object. Hosted requests use the verified account token; legacy local requests use the reporting token. The dashboard forwards the signed-in account automatically. After the initial automatic import, refreshes are manual or explicitly agent-triggered; no recurring sync is scheduled. The small first implementation bounds work to 120 seconds of provider reads, 2,000 requests, 10,000 customers, 50,000 event occurrences and 32 MiB of provider responses per connection. Pagination and 429/5xx retries are supported; a limit or failure preserves the previous snapshot. A limit is shown as an incomplete import, not successful full coverage. Larger projects will need incremental jobs and export reconciliation. RevenueCat cannot reconstruct SDK first opens, onboarding answers or paywall views from before Appbase was installed. The lab uses an isolated read-only V2 key in ignored local server configuration; no purchase or customer-write permissions are required. ### Recurring metrics The existing read-only sync stores products, subscription snapshots and available historical customer events; dashboard reports read those local records. It does not yet fetch MRR/ARR/churn. RevenueCat's Charts API requires the additional **Charts metrics → Charts Configuration → Read only** permission (`charts_metrics:charts:read`) for that future integration. Overview/project-wide metrics are not a substitute for app/date-scoped charts. Charts contains production purchases only, so these metrics remain unavailable in the Test Store lab. No additional app/SDK code is required for fetching provider charts. See [Revenue](revenue.md#mrr-arr-and-churn--availability-and-next-integration). ### Find connection and history controls In **App setup → RevenueCat**, an unconnected app shows **Connect RevenueCat**; an existing mapping shows **Manage connection & history**. The connection page shows **History not imported yet**, progress, a failure, or **Available history imported** with its timestamp. Existing connections were not bulk-backfilled: use **Import available history** once, then **Refresh history** when needed. Newly configured stores attempt an import automatically. All time on the analytics dashboard displays all stored data; it does not start an import.