# Connect RevenueCat

> Connect RevenueCat with OAuth, a secret API key or a webhook set up by hand, so Whisperr knows the billing state of each user.

Whisperr receives subscription events from RevenueCat through a webhook. It uses them to know the billing state of each user: in a trial, paid, cancelled with access left, payment failed, expired or refunded. When you connect with RevenueCat (OAuth) or a secret API key, Whisperr sets up the webhook itself and imports the current state of each subscription. The webhook needs no app code. Your app needs only the shared user ID below.

RevenueCat webhooks need the RevenueCat Pro plan.

## Use the same user ID in both SDKs

Whisperr links a RevenueCat customer to a Whisperr user by the RevenueCat app user ID. Your app must call `Purchases.logIn` with the same user ID that it gives to Whisperr `identify()`. Call `logIn` where you call `identify`, and `logOut` where you call `reset`.

| Platform                                | Log in                                                    | Log out                                    |
| --------------------------------------- | --------------------------------------------------------- | ------------------------------------------ |
| Swift (`RevenueCat`)                    | `_ = try await Purchases.shared.logIn(user.id)`           | `_ = try? await Purchases.shared.logOut()` |
| React Native (`react-native-purchases`) | `await Purchases.logIn(user.id)`                          | `await Purchases.logOut()`                 |
| Flutter (`purchases_flutter`)           | `await Purchases.logIn(user.id)`                          | `await Purchases.logOut()`                 |
| Web (`@revenuecat/purchases-js`)        | `await Purchases.getSharedInstance().changeUser(user.id)` | None                                       |

If your app already passes the same ID as `appUserID` to `Purchases.configure`, you do not need to change anything.

RevenueCat anonymous IDs (`$RCAnonymousID:…`) never match a user. An app with no log-in can set the RevenueCat subscriber attribute `whisperr_id` to the Whisperr anonymous ID. Of the Whisperr SDKs, only Flutter exposes it today:

`lib/billing.dart`

```dart
final id = Whisperr.instance.anonymousId;
if (id != null) await Purchases.setAttributes({'whisperr_id': id});
```

Whisperr reads no other subscriber attribute.

## Connect with RevenueCat (OAuth)

This is the recommended way to connect. You sign in to RevenueCat and approve access. You do not create or paste a key. A workspace owner or admin must do this.

1. In the Whisperr dashboard, open **Integrations**, then **RevenueCat** under **Billing**, or go straight to `https://app.whisperr.net/integrations?source=revenuecat`. Click **Connect RevenueCat**.
2. Sign in to RevenueCat if you are not signed in.
3. RevenueCat shows the projects and permissions that Whisperr asks for. Approve them.
4. Back in Whisperr, choose the RevenueCat project for this app and click **Use this project**. If you granted exactly one project, Whisperr uses it without asking.
5. Whisperr creates a webhook named **Whisperr** in the project and imports the current state of each subscription, the same as with an [API key](#connect-with-an-api-key-instead). The dashboard shows the progress.

If the app already has a RevenueCat connection made with an API key or by hand, connecting with OAuth reuses it. An app still has one RevenueCat connection.

### What does RevenueCat ask me to approve?

| Permission                                      | Access         | Needed | Why                                                                  |
| ----------------------------------------------- | -------------- | ------ | -------------------------------------------------------------------- |
| `project_configuration:projects:read`           | Read only      | Yes    | Lists the projects that you granted, so you can choose one.          |
| `project_configuration:integrations:read_write` | Read and write | Yes    | Creates, updates and removes the Whisperr webhook.                   |
| `customer_information:customers:read`           | Read only      | Yes    | Lists customers, their app user IDs and the `whisperr_id` attribute. |
| `customer_information:subscriptions:read`       | Read only      | Yes    | Reads each subscription’s current state.                             |
| `customer_information:purchases:read`           | Read only      | Yes    | Reads one-time purchases.                                            |
| `project_configuration:apps:read`               | Read only      | No     | Not used today. You can uncheck it.                                  |
| `charts_metrics:overview:read`                  | Read only      | No     | Not used today. You can uncheck it.                                  |
| `charts_metrics:charts:read`                    | Read only      | No     | Not used today. You can uncheck it.                                  |

The only thing Whisperr writes is its own webhook integration. It writes nothing to customers, subscriptions, purchases, entitlements or offerings, and it never cancels or refunds a purchase.

On RevenueCat’s consent screen you can narrow both the projects and the permissions. Whisperr sees only the projects that you grant. If you uncheck a permission marked **Yes**, Whisperr does not connect and the dashboard names the missing permissions. Click **Connect RevenueCat** again and leave those permissions checked. If you grant no project, there is nothing to choose: connect again and grant the project for this app.

### What happens when access lapses?

Whisperr stores the RevenueCat tokens encrypted and refreshes them for as long as you stay connected. A RevenueCat refresh token expires after 30 days without use. If Whisperr can no longer refresh, for example because you removed its access in RevenueCat, the dashboard asks you to reconnect. Click **Reconnect RevenueCat** and approve again.

## Connect with an API key instead

Use a secret API key if the dashboard does not show **Connect RevenueCat**. Whisperr then does the same setup as with OAuth. A workspace owner or admin must do this.

1. In RevenueCat, open **API keys** and click **+ New secret API key**. Choose version **V2** and give the key these permissions:

   | Permission                                      | Access         | Why                                                                  |
   | ----------------------------------------------- | -------------- | -------------------------------------------------------------------- |
   | `customer_information:customers:read`           | Read only      | Lists customers, their app user IDs and the `whisperr_id` attribute. |
   | `customer_information:subscriptions:read`       | Read only      | Reads each subscription’s current state.                             |
   | `customer_information:purchases:read`           | Read only      | Reads one-time purchases.                                            |
   | `project_configuration:integrations:read_write` | Read and write | Creates and updates the Whisperr webhook.                            |

   Give the key no other permission. A key with `project_configuration:projects:read` also lets Whisperr find the project by itself.

2. In the Whisperr dashboard, open **Integrations**, then **RevenueCat** under **Billing**, or go straight to `https://app.whisperr.net/integrations?source=revenuecat`. Click **Use an API key instead** if the dashboard shows **Connect RevenueCat**. Paste the key. If the key cannot read projects, also paste the **Project ID** from RevenueCat **Project settings → General settings**. Click **Connect**.

3. Whisperr checks the key. If a permission is missing, the dashboard names it. Add it to the key in RevenueCat, or create a new key, and connect again. Nothing changes until the check passes.

4. Whisperr creates a webhook named **Whisperr** in the project, for all apps, events and environments, with its own Authorization value. Then it imports the current state of each subscription. The dashboard shows the progress.

Whisperr stores the key encrypted. It uses the key only to manage its own webhook and to read subscriptions and purchases. Connecting again with a new key replaces the old key and the webhook’s Authorization value, and keeps one Whisperr webhook in the project.

## Set up the webhook by hand

Use this if you do not want to give Whisperr OAuth access or an API key. In the RevenueCat setup, click **Set up the webhook by hand instead**.

1. Click **Create values** (on some screens, **Create webhook values**). Copy the **Webhook URL** and the **Authorization header value**. Whisperr shows the Authorization value only once and keeps only its hash.
2. In RevenueCat, open your project → **Integrations** → **Webhooks** → **Add new configuration**. Paste the URL and the Authorization header value. Set **Environment** to both production and sandbox. Leave **Events** and **Apps** on **All**. Save.
3. In the webhook that you saved, click **Send test event**. The Whisperr dashboard shows **Test event received**. RevenueCat is now connected.
4. Optional, recommended: turn on webhook signing in RevenueCat and paste its secret in the **Signing secret** step on the **Integrations** page. Whisperr then rejects any delivery without a valid signature, and the step shows **Signature check is on.**

If the dashboard says that the Authorization value did not match, paste the value from step 1 again.

If you lose the Authorization value, press and hold **Hold to replace the value** on the **Integrations** page (or click **Create a new Authorization value**) and paste the new value in RevenueCat. The old value stops working within a minute.

A webhook set up by hand imports no history: see below.

Whisperr uses one billing source for each app. To connect RevenueCat, first disconnect Superwall or Adapty if the app has one.

## Disconnect and revoke access

To disconnect, open RevenueCat in **Integrations → Billing** and hold **Hold to disconnect**.

- **Connected with OAuth or an API key:** Whisperr tries to delete its webhook from your RevenueCat project, then revokes its stored token or key and never uses it again. If the webhook delete fails, the disconnect still finishes. Check **Integrations** → **Webhooks** in RevenueCat and delete the **Whisperr** webhook if it is still there.
- **Connected with OAuth:** RevenueCat has no API that lets Whisperr revoke its own access. Also remove Whisperr under RevenueCat **Account** → **Security** → **Active OAuth tokens** ([app.revenuecat.com/settings/security](https://app.revenuecat.com/settings/security)).
- **Connected with an API key:** delete the key under **API keys** in RevenueCat.
- **Webhook set up by hand:** delete the webhook in RevenueCat yourself.

## What Whisperr does with billing events

- **It keeps the billing state of each subscription.** The states are `never_subscribed`, `trial_active`, `trial_cancelled`, `trial_expired`, `paid_active`, `cancel_pending`, `grace`, `billing_retry`, `paused`, `expired_voluntary`, `expired_involuntary`, `expired_unconfirmed` and `refunded`. A user with more than one subscription gets the most urgent state.

- **It writes billing events on the user timeline,** for example `billing_issue_started`, `billing_cancel_intent` and `billing_trial_ending_3d`. Then the engine evaluates the user again. Billing events never count as user activity.

- **It runs three subscription playbooks:**

  | Playbook         | Fires when                                      | Billing states                    |
  | ---------------- | ----------------------------------------------- | --------------------------------- |
  | Payment recovery | A renewal charge fails.                         | `grace`, `billing_retry`          |
  | Cancel save      | The user turns off auto-renew and keeps access. | `cancel_pending`                  |
  | Trial ending     | The trial ends in about 3 days.                 | `trial_active`, `trial_cancelled` |

  A playbook fires on its billing event, whatever the churn score, and only while the user’s billing state is one of the states in the table. If your workspace already has a live policy when you connect, Whisperr adds the playbooks to it paused. They send nothing until you resume them on the **Interventions** page.

- **It handles a lost event.** If a paid period ends and no renewal, billing issue or expiration event arrives within 72 hours, the state becomes `expired_unconfirmed`. A late RevenueCat event still sets the true state.

- **It ignores sandbox events for billing.** A sandbox purchase shows in the dashboard. It does not change a billing state and does not start a playbook.

Whisperr stores a minimized copy of each event: type, store, product, dates, cancel and expiry reasons, and price. It stores no subscriber attribute other than `whisperr_id`, and never `$email`. After Whisperr matches an event, it keeps RevenueCat customer IDs only as keyed hashes.

## History

**With OAuth or an API key,** Whisperr imports the current state of each customer’s App Store and Google Play subscriptions and one-time purchases. Imported state sets the billing state of each user. Importing does not message anyone about past changes. Changes after the import, such as a cancellation or a failed renewal, can start playbooks as usual. Subscriptions in other stores, such as Stripe or Amazon, appear at their next RevenueCat event.

**With the webhook set up by hand,** Whisperr sees RevenueCat events from the moment you connect. Each subscriber appears at their next RevenueCat event, usually the next renewal. A monthly subscriber appears within about 30 days. An annual subscriber appears at the next renewal.

An event that reaches Whisperr more than 72 hours after it happened also sets the billing state without starting a playbook.

A customer who buys first and signs up later is linked after your app identifies that user ID.

## Check the match

After live events arrive, the dashboard shows the share of RevenueCat customers in the last 30 days that match a Whisperr user. It also counts anonymous, unknown and conflicting IDs. Your coding agent can read the same numbers with the Whisperr MCP tool `get_revenuecat_status`.

A conflict means that one RevenueCat customer names two Whisperr users. Use one ID for each person in both SDKs.

## Common questions

### What can Whisperr change in my RevenueCat project?

Only its own webhook integration, which it creates, updates and removes. Whisperr writes nothing to customers, subscriptions, purchases, entitlements or offerings, and never cancels or refunds.

### How do I revoke Whisperr's access to RevenueCat?

Disconnect RevenueCat in the Whisperr dashboard. Then remove Whisperr under RevenueCat Account → Security → Active OAuth tokens, because RevenueCat has no API that lets Whisperr revoke its own token.

### Which RevenueCat API key permissions does Whisperr need?

A V2 secret key with customer_information:customers:read, customer_information:subscriptions:read, customer_information:purchases:read and project_configuration:integrations:read_write. With project_configuration:projects:read, Whisperr also finds the project by itself.

### Does importing RevenueCat history send messages?

No. Imported state sets each user's billing state but never starts a playbook. Changes after the import, such as a cancellation or a failed renewal, can start playbooks as usual.

### Can I connect RevenueCat and Superwall or Adapty at the same time?

No. Whisperr uses one billing source per app. Disconnect the other source first.

### Why does a RevenueCat customer not match a Whisperr user?

Whisperr matches by the RevenueCat app user ID. Anonymous IDs ($RCAnonymousID:…) never match. Call Purchases.logIn with the same user ID that you pass to Whisperr identify().

Source: https://docs.whisperr.net/integrations/revenuecat/
