# Validate purchases with RevenueCat

> Connect InfluTo to RevenueCat — tag subscribers with the referral code from the SDK, add the webhook automatically or by hand, and test that purchases are attributed.

Use this path if your app already sells through RevenueCat. The InfluTo SDK writes the referral code onto the RevenueCat subscriber, RevenueCat sends every purchase, renewal and refund to InfluTo's webhook, and InfluTo turns the attributed ones into commissions. Your app calls nothing at purchase time.

Not using RevenueCat? Use [store-direct](https://docs.influ.to/integrations/store-direct.md) instead — never both on the same platform.

## How it works

1. A referred user opens your app. `checkAttribution` (or a code they type) gives the SDK the creator's code.
2. The SDK sets two subscriber attributes in RevenueCat: `influto_code` (for example `MAYA`) and `influto_referral` (the string `"true"`).
3. The user buys. RevenueCat sends the event to `https://influ.to/api/webhooks/revenuecat/{app_id}`.
4. InfluTo reads `influto_code` from the event, finds the creator, and records the conversion. If the attribute is missing, it falls back to the user ID you passed to `identifyUser`.

<!-- steps -->

### Let the SDK tag subscribers

How the SDK reaches RevenueCat depends on the platform. Configure RevenueCat before you call `checkAttribution` or `applyCode`, so the attribute can be written.

<!-- tabs -->
<!-- tab: React Native / Expo -->
Nothing to add. If `react-native-purchases` is installed and configured, the SDK finds it and sets the attributes itself.

```tsx
Purchases.configure({ apiKey: REVENUECAT_API_KEY });
await InfluTo.initialize({ apiKey: INFLUTO_API_KEY });
```
<!-- tab: iOS (Swift) -->
Give the SDK a setter. It calls it with the two attributes.

```swift
var config = InfluToConfig(apiKey: influtoApiKey)
config.revenueCatAttributeSetter = { attrs in
  Purchases.shared.attribution.setAttributes(attrs)
}
try await InfluTo.initialize(config)
```
<!-- tab: Android (Kotlin) -->
The SDK finds the RevenueCat SDK on its own. To be explicit, pass a setter:

```kotlin
InfluTo.initialize(applicationContext, InfluToConfig(
  apiKey = influtoApiKey,
  revenueCatAttributeSetter = { attrs -> Purchases.sharedInstance.setAttributes(attrs) },
))
```
<!-- tab: Flutter (Dart) -->
Pass a hook in the config.

```dart
await InfluTo.instance.initialize(InfluToConfig(
  apiKey: influtoApiKey,
  revenueCatHook: (attrs) => Purchases.setAttributes(attrs),
));
```
<!-- tab: Web / Capacitor / PWA -->
Pass a binding for the RevenueCat SDK you use.

```ts
import { revenueCatCapacitor } from '@influto/web/revenuecat';

await InfluTo.initialize({
  apiKey: INFLUTO_API_KEY,
  ...(await capacitorAdapters()),
  revenueCat: revenueCatCapacitor(Purchases),
  // purchases-js (Web Billing): revenueCat: revenueCatJs(() => Purchases.getSharedInstance())
  // cordova-plugin-purchases:   revenueCat: revenueCatCordova(window.Purchases)
});
```
<!-- /tabs -->

Then, once you know who the user is, call `identifyUser` with their RevenueCat app user ID. It's the fallback InfluTo uses when an event arrives without the attribute.

### Connect the webhook

Open **Setup & SDK → Webhook** in the InfluTo dashboard and pick one of the two ways.

**Automatically (recommended).** Paste a RevenueCat **v2 secret API key** under *Connect RevenueCat automatically*, and InfluTo creates a webhook named "InfluTo" in your RevenueCat project — both environments, all events. Create the key in RevenueCat under your project → **API keys → New secret API key**, version V2, with these permissions:

- Project configuration → Integrations: **Read & write**
- Project configuration → Projects: **Read**
- Customer information → Customers: **Read & write** — only if you also use [free-access codes](https://docs.influ.to/platform/creator-access-codes.md)

If the key can see several projects, InfluTo asks which one belongs to this app.

**By hand.** In RevenueCat, open your project → **Integrations → Webhooks → Add new configuration**:

| Field | Value |
|---|---|
| Name | Anything, for example `InfluTo` |
| Webhook URL | `https://influ.to/api/webhooks/revenuecat/{app_id}` — copy it from **Setup & SDK**, it already contains your app ID |
| Authorization header value | `Bearer rc_webhook_…` — press **Generate secret** on **Setup & SDK** and paste exactly what it shows, including the word `Bearer`. It's shown once. |
| Environment | Both Production and Sandbox |
| Events filter | All apps, all events |

Leave HMAC signing off — InfluTo checks the Authorization header.

### Test it

In RevenueCat, open the webhook's test page and press **Send Test Event**. Within seconds it appears in **Real-Time Events** in InfluTo.

Then run a real sandbox purchase through a referral link, as in the [quick start](https://docs.influ.to/quick-start.md#test-a-referred-purchase), or press **Send Test Webhook** on **Setup & SDK** to push a simulated sandbox purchase through the attribution pipeline. Full checklist: [Verify the integration](https://docs.influ.to/integrations/testing.md).

<!-- /steps -->

## Events InfluTo uses

Leave RevenueCat on "All events". InfluTo acts on these and acknowledges the rest:

| Event | What InfluTo does |
|---|---|
| `INITIAL_PURCHASE` | Records the conversion. A free trial is recorded but earns nothing until it converts to paid. |
| `RENEWAL` | Pays commission again (and turns a converting trial into a paid conversion). |
| `NON_RENEWING_PURCHASE` | Pays commission on a one-time in-app purchase. |
| `PRODUCT_CHANGE` | Notes the new product; the next renewal is paid on its price. |
| `CANCELLATION`, `UNCANCELLATION`, `EXPIRATION` | Track whether the subscription is still active. |
| `REFUND` | Reverses the commission. |
| `BILLING_ISSUE` | Logged only. |
| `TEST` | Confirms the webhook works. |

Purchases from RevenueCat's sandbox, Xcode and local testing environments are recorded for testing but never move money or change stats.

## The subscriber attributes

| Attribute | Value | Used by |
|---|---|---|
| `influto_code` | The creator's code, uppercased | InfluTo — this is what attributes the purchase |
| `influto_referral` | The string `"true"` | You — for RevenueCat targeting or experiments that treat referred users differently ([example](https://docs.influ.to/guides/referral-offers.md)) |

Keep `influto_referral` a string. RevenueCat attributes are strings, and a targeting rule written for `"true"` won't match a boolean.

If your app learns the code some other way — your own deep link, a promo flow — you can set `influto_code` yourself with RevenueCat's `setAttributes`. InfluTo uppercases it.

## Webhook responses

| Status | Meaning |
|---|---|
| `200` | Received. Processing problems are also answered with 200 (and an `error` field) so RevenueCat doesn't retry them — check **Real-Time Events** if a purchase is missing. |
| `401` | The Authorization value doesn't match this app's secret. The response says how to fix it, and **Setup & SDK** shows how many calls were rejected. |
| `404` | Unknown app ID in the URL. |
| `400` | The body isn't valid JSON. |

## When purchases aren't attributed

- **No `influto_code` on the subscriber.** RevenueCat wasn't configured when the SDK tried to tag the user, or (iOS, Flutter, web) the setter, hook or binding isn't passed. Call `identifyUser` as a safety net.
- **The campaign or the creator isn't active.** Codes only attribute while the campaign is active.
- **The user was already attributed.** The first creator who brought a user keeps them; a code typed later doesn't move an existing subscriber.

More causes and fixes: [Troubleshooting](https://docs.influ.to/advanced/troubleshooting.md).

## With an AI agent

The MCP tool `influto_setup_revenuecat` does the automatic connection for you (using the stored v2 key) and falls back to the exact manual steps. See [AI agents](https://docs.influ.to/agents/index.md).
