> For the complete AppRefer docs index, see https://apprefer.com/llms.txt (every page as markdown) or https://apprefer.com/llms-full.txt (all docs in one file).

# Meta Ads

AppRefer sends your installs, trials and purchases to Meta's Conversions API from the server, matched to the ad click, and imports your Meta spend for cost and ROAS.

## What you need

-   A Meta dataset (pixel) in Events Manager.
-   An access token that can send events to that dataset: generate one in Events Manager (dataset → Settings → Conversions API), or use a system user token with access to the dataset.
-   For spend sync: the ad account IDs, and a token with `ads_read` on them.

## Setup

1.  In **Settings → Integrations → Meta**, paste the Dataset ID and access token and save. The integration is connected once both are stored.
2.  Optionally add a **Test Event Code** from Events Manager and click **Test Connection**. With a code the test event shows in Test Events; without one it is a real `Lead` event.
3.  Add your ad account IDs to turn on spend sync (see below).
4.  Point your Meta ads at an AppRefer tracking link. See [Tracking links](https://apprefer.com/docs/tracking-links.md) for the URL parameters.

### Configuration fields

| Field | Meaning |
| --- | --- |
| `datasetId` | Dataset (pixel) that receives events. Required. |
| `accessToken` | Conversions API token. Required, secret. |
| `webFunnelDatasetId` | Optional second dataset for website conversions: verified website receipts and purchases made in a web funnel checkout. Without it they use datasetId, as do all app events. |
| `testEventCode` | Used only by Test Connection. |
| `adAccountIds` | Ad accounts to import spend from, for example act\_123456789. |
| `costAccessToken` | Optional separate token for spend sync. Use it when the Conversions API token has no ads\_read on the ad account (Meta error #200). Secret. |
| `adAccountOwnership` | Which campaigns in each ad account belong to this app (see below). |

The dashboard covers the common fields. Set `webFunnelDatasetId`, `costAccessToken` and `adAccountOwnership` with `PATCH /api/v1/apps/{appId}/integrations/meta` or the MCP tool `configure_integration`. Send only the fields you change; stored secrets you leave out are kept.

## What AppRefer sends

Each event goes to the dataset with `action_source: "website"`, because the ad click happened on a web tracking link. This lets you run web campaigns while AppRefer closes the loop to the in-app conversion.

**Meta Conversions API**

```
POST https://graph.facebook.com/v24.0/{datasetId}/events
```

| Field | Source |
| --- | --- |
| `fbc` | The click's `fbc` from the landing page, or `fb.1.{clickTime}.{fbclid}`. Never sent when the fbclid looks like an unfilled template such as `{{fbclid}}`. |
| `fbp` | Browser ID from the landing page, or from your website receipt. |
| `client_ip_address, client_user_agent` | The browser at click time when known, otherwise the device. |
| `external_id` | Your hashed user ID from a website receipt, then the hashed AppRefer device ID. |
| `em, ph, fn, ln, db, ct, st, zp, country` | Advanced Matching, already SHA-256 hashed. |
| `madid` | IDFA or GAID, only for installs that allowed tracking. |
| `custom_data` | value and currency. Always present on Purchase (value 0 if unknown). |
| `event_source_url` | The website page from a receipt, otherwise the tracking link URL. |

## Event mapping

These are the defaults. Change any of them in **Settings → Event Mapping**. Events with no mapping that are not one of Meta's standard event names are not sent, so in-app events such as `app_opened` or `paywall_viewed` never reach Meta unless you map them.

| AppRefer event | Meta event |
| --- | --- |
| install, sign\_up | Lead |
| complete\_registration (website sign\_up receipts) | CompleteRegistration |
| startTrial, qualified\_trial | StartTrial |
| Subscription first payment or trial conversion (subscription\_purchase) | Subscribe |
| Subscription renewal (subscription\_renewal) | Subscribe |
| One-time purchase, SDK purchase event, website purchase receipt | Purchase |
| subscribe (plan change) | Subscribe |
| add\_to\_cart | AddToCart |
| initiate\_checkout (including website checkout\_started) | InitiateCheckout |
| search | Search |
| view\_content | ViewContent |

Subscription payments follow the mapping RevenueCat recommends for Meta: a subscription's first payment (including intro and promotional offers), its trial conversion and every renewal are `Subscribe`; only one-time purchases are `Purchase`. If your live ad sets optimise for Purchase, map `subscription_purchase` (and `subscription_renewal` if you want) to `Purchase` until you rebuild them.

> **Organic purchases**
>
> The subscription split applies to Meta-attributed installs. Organic and unattributed purchases sent to Meta keep the plain mapping, so their payments arrive as `Purchase`.

Trial starts can be held back and only sent once a trial looks likely to convert. See [Qualified trials](https://apprefer.com/docs/qualified-trials.md).

## Advanced Matching

Give AppRefer the user's email or phone after sign-up or login. The SDK normalises and SHA-256 hashes each value on the device; nothing readable leaves the phone.

**Swift**

```swift
await AppRefer.setAdvancedMatching(email: "user@example.com", phone: "+447700900123")
```

**Kotlin**

```kotlin
AppRefer.setAdvancedMatching(email = "user@example.com", phone = "+447700900123")
```

**Dart**

```dart
await AppReferSDK.setAdvancedMatching(email: 'user@example.com', phoneNumber: '+447700900123');
```

**TypeScript**

```typescript
await AppRefer.setAdvancedMatching({ email: 'user@example.com', phone: '+447700900123' });
```

All four SDKs also accept `firstName`, `lastName` and `dateOfBirth`.

## Website events

Verified receipts from your payment backend ([web events](https://apprefer.com/docs/events.md#verified-website-payments)) are sent to Meta for Meta-attributed clicks: `purchase` as Purchase, `sign_up` as CompleteRegistration, `checkout_started` as InitiateCheckout and `start_trial` as StartTrial. To improve matching, a receipt can carry:

-   `fbp`: the `_fbp` cookie from your site. Used when the click itself has no browser ID. A value not in `_fbp` format is rejected with `400 invalid_fbp`.
-   `emailSha256` and the other hashed fields (`firstNameSha256`, `lastNameSha256`, `citySha256`, `stateSha256`, `zipSha256`, `countrySha256`): lowercase hex SHA-256, sent as Advanced Matching.
-   `externalIdSha256`: the same hashed user ID your Meta pixel sends, so browser and server events line up.

> **Test Events never carry fbc**
>
> When a website receipt is routed to Meta Test Events, AppRefer leaves `fbc` out. Test events still feed your dataset's diagnostics, and a made-up click ID would raise Meta's "modified fbclid" warning against your real traffic. Production events are not affected. Never invent an fbclid to test a live setup.

## Spend sync and shared ad accounts

Add each Meta ad account to `adAccountIds`. AppRefer imports campaign spend, impressions and clicks on a schedule (each run refreshes today and yesterday), using `costAccessToken` if set and `accessToken` otherwise. It reads each account's currency from Meta and stores spend in that currency. Totals are only added up when the rows share one currency; mixed currencies are flagged instead, and ROAS is hidden when spend and revenue aren't comparable.

An ad account is treated as belonging to this app alone unless you say otherwise. If several apps share an account, give each app an `adAccountOwnership` rule with mode `shared` and the exact campaign IDs or campaign-name prefixes it owns:

**PATCH /api/v1/apps/{appId}/integrations/meta**

```json
{
  "adAccountIds": ["act_123456789", "act_987654321"],
  "adAccountOwnership": {
    "act_123456789": { "mode": "shared", "campaignNamePrefixes": ["Sparkly |"] },
    "act_987654321": { "mode": "dedicated" }
  }
}
```

> **Ownership rules fail closed**
>
> A shared account with no campaign IDs and no name prefixes imports no spend and records a sync error. Once `adAccountOwnership` exists, every account in `adAccountIds` needs a rule (`dedicated` or `shared`); an account without one is skipped with an error. This stops another app's campaigns being counted against this one.

## Deduplication

Every event has a stable `event_id`, reused on retries, so Meta deduplicates repeats within its 48-hour window. Each renewal or repeat purchase gets its own ID.

---

Source: https://apprefer.com/docs/integrations/meta · Last updated: 2026-10-19
