# Meta Conversion API

Send subscription lifecycle events from Superwall to Meta's server-side Conversion API. Use this integration to report subscriptions, renewals, cancellations, refunds, and other revenue events to your Meta Pixel.

Use the Meta Conversion API integration to forward Superwall subscription
lifecycle events to Meta over a server-side API. This is the integration to use
when you want Meta to receive renewals, cancellations, expirations, refunds,
and other webhook-driven revenue events.

> **Note:** If you need browser-side tracking for web paywalls, see
> [Facebook Pixel](/docs/integrations/facebook-pixel). That integration tracks
> client-side paywall events and does not send subscription lifecycle events.

Features [#features]

* **Server-side event tracking**: Events are sent directly to Meta's servers.
* **Standard event mapping**: Superwall maps subscription events to Meta
  standard events where possible.
* **Sandbox environment support**: Use separate credentials for sandbox events.
* **Test event mode**: Validate events in Meta Events Manager before going
  live.
* **Flexible revenue reporting**: Send gross revenue or net proceeds.
* **Anonymous user handling**: Control how events behave when a user has no
  `originalAppUserId`.
* **Deduplication**: Event IDs prevent duplicate event counting.

Set up in Superwall [#set-up-in-superwall]

Set this up from the dashboard UI, not by manually building a JSON payload.

1. Open your app in Superwall.
2. Go to **Integrations**.
3. In **Marketing**, open **Meta Conversion API**.
4. Fill in the fields shown in the form.
5. Click **Enable Meta Conversion API**.

If the integration is already connected, Superwall shows an overview with
status, configured field count, recent delivery performance, and an **Edit
Configuration** button.

Revenue tracking requirement [#revenue-tracking-requirement]

This integration depends on Superwall revenue tracking. If revenue tracking is
not configured, the dashboard shows a **Revenue tracking required** banner and
blocks the form until you set that up first.

Fields shown in the dashboard [#fields-shown-in-the-dashboard]

| UI field                  | Required | What to enter                                                |
| ------------------------- | -------- | ------------------------------------------------------------ |
| `Access Token`            | Yes      | A Meta access token with `ads_management` permission         |
| `Pixel Id`                | Yes      | The Meta Pixel ID that should receive events                 |
| `Sales Reporting`         | Yes      | Choose `Revenue` or `Proceeds`                               |
| `Sandbox Access Token`    | No       | Optional token for sandbox events                            |
| `Sandbox Pixel Id`        | No       | Optional sandbox Pixel ID                                    |
| `Test Event Code`         | No       | Optional Meta test code for validation in Events Manager     |
| `Anonymous User Behavior` | No       | Choose `send` or `dontSend` for users without an app user ID |

Superwall handles the internal integration identifier automatically. You do not
enter `integration_id` in the dashboard UI.

The current dashboard UI also does not expose custom event-name mappings for
this integration, so setup is limited to the fields above.

Getting your credentials [#getting-your-credentials]

Meta Events Manager is the source of truth for the credentials this integration
needs.

Access token [#access-token]

1. Go to [Meta Events Manager](https://business.facebook.com/events_manager).
2. Select your Pixel from **Data Sources**.
3. Click the **Settings** tab.
4. Scroll to **Conversions API**.
5. Generate an access token, or use an existing System User token.
6. Copy the access token.

**Note**: The access token requires `ads_management` permission. For
production use, Meta recommends using a System User token instead of a
personal token.

Pixel ID [#pixel-id]

1. Go to [Meta Events Manager](https://business.facebook.com/events_manager).
2. Select your Pixel from **Data Sources**.
3. Copy the Pixel ID shown at the top of the page.

Test event code [#test-event-code]

1. In Events Manager, select your Pixel.
2. Click the **Test Events** tab.
3. Copy the displayed test event code.
4. Add it to the **Test Event Code** field while validating your integration.

Event mapping [#event-mapping]

Superwall maps revenue lifecycle events to Meta standard events when possible.
Using standard events helps Meta optimize ad delivery for those outcomes.

Standard event mappings [#standard-event-mappings]

| Superwall event            | Meta standard event | Description                  |
| -------------------------- | ------------------- | ---------------------------- |
| `sw_subscription_start`    | `Subscribe`         | New paid subscription        |
| `sw_trial_start`           | `StartTrial`        | Free trial begins            |
| `sw_renewal`               | `Purchase`          | Subscription renewal payment |
| `sw_trial_converted`       | `Purchase`          | Trial converts to paid       |
| `sw_intro_offer_converted` | `Purchase`          | Intro offer converts to paid |

Custom event mappings [#custom-event-mappings]

Events without a standard Meta equivalent are sent with their Superwall event
names by default:

| Superwall event             | Meta event name             |
| --------------------------- | --------------------------- |
| `sw_subscription_cancelled` | `sw_subscription_cancelled` |
| `sw_trial_cancelled`        | `sw_trial_cancelled`        |
| `sw_subscription_expired`   | `sw_subscription_expired`   |
| `sw_billing_issue`          | `sw_billing_issue`          |
| `sw_refund`                 | `sw_refund`                 |
| `sw_product_change`         | `sw_product_change`         |

Complete event mapping reference [#complete-event-mapping-reference]

| Superwall event    | Condition                  | Meta event                  |
| ------------------ | -------------------------- | --------------------------- |
| `INITIAL_PURCHASE` | `periodType = Trial`       | `StartTrial`                |
| `INITIAL_PURCHASE` | `periodType = Normal`      | `Subscribe`                 |
| `INITIAL_PURCHASE` | `periodType = Intro`       | `sw_intro_offer_start`      |
| `RENEWAL`          | `periodType = Trial`       | `Purchase`                  |
| `RENEWAL`          | `periodType = Normal`      | `Purchase`                  |
| `RENEWAL`          | `isTrialConversion = true` | `Purchase`                  |
| `CANCELLATION`     | `periodType = Trial`       | `sw_trial_cancelled`        |
| `CANCELLATION`     | `periodType = Normal`      | `sw_subscription_cancelled` |
| `EXPIRATION`       | Any                        | `sw_*_expired`              |
| Any event          | `price < 0`                | `sw_refund`                 |

Event format [#event-format]

Superwall sends events to Meta's Conversion API in the following format:

API endpoint [#api-endpoint]

```
POST https://graph.facebook.com/v21.0/{pixel_id}/events?access_token={access_token}
```

Request payload [#request-payload]

```json
{
  "data": [
    {
      "event_name": "Subscribe",
      "event_time": 1705312200,
      "event_id": "evt_abc123",
      "action_source": "app",
      "user_data": {
        "external_id": ["user_12345"]
      },
      "custom_data": {
        "value": 9.99,
        "currency": "USD",
        "content_type": "product",
        "content_name": "com.app.premium.monthly",
        "content_ids": ["com.app.premium.monthly"]
      }
    }
  ],
  "test_event_code": "TEST12345"
}
```

Event fields [#event-fields]

| Field           | Description                                 | Example                           |
| --------------- | ------------------------------------------- | --------------------------------- |
| `event_name`    | Meta standard event or custom event name    | `"Subscribe"`                     |
| `event_time`    | Unix timestamp in seconds                   | `1705312200`                      |
| `event_id`      | Unique event ID for deduplication           | `"evt_abc123"`                    |
| `action_source` | Always set to `"app"` for mobile app events | `"app"`                           |
| `user_data`     | User identification data                    | `{"external_id": ["user_12345"]}` |
| `custom_data`   | Event-specific data including revenue       | See below                         |

Custom data fields [#custom-data-fields]

| Field          | Description                                              | Example                       |
| -------------- | -------------------------------------------------------- | ----------------------------- |
| `value`        | Revenue amount based on your **Sales Reporting** setting | `9.99`                        |
| `currency`     | ISO 4217 currency code                                   | `"USD"`                       |
| `content_type` | Always `"product"` for subscription events               | `"product"`                   |
| `content_name` | Product identifier                                       | `"com.app.premium.monthly"`   |
| `content_ids`  | Array containing the product ID                          | `["com.app.premium.monthly"]` |

User identification [#user-identification]

Meta's server-side event matching depends on the `external_id` value that
Superwall sends.

Known users [#known-users]

For users with an `originalAppUserId`, Superwall sends:

```json
{
  "user_data": {
    "external_id": ["user_12345"]
  }
}
```

Anonymous users [#anonymous-users]

For users without an `originalAppUserId`, behavior depends on the
**Anonymous User Behavior** setting.

When set to `"send"`:

* Events are sent with a synthetic ID using the store name and original
  transaction ID.

```json
{
  "user_data": {
    "external_id": ["$APP_STORE:1000000123456789"]
  }
}
```

When set to `"dontSend"`:

* Events from anonymous users are skipped.

Revenue tracking [#revenue-tracking]

The **Sales Reporting** setting controls which amount is sent in `custom_data`.

Revenue vs. proceeds [#revenue-vs-proceeds]

* **Revenue**: The full amount charged to the customer.
* **Proceeds**: The amount after store fees and taxes.

Zero-value events [#zero-value-events]

Events without revenue, such as cancellations and expirations, omit `value`
and `currency`:

```json
{
  "custom_data": {
    "content_type": "product",
    "content_name": "com.app.premium.monthly",
    "content_ids": ["com.app.premium.monthly"]
  }
}
```

Refund events [#refund-events]

Refunds are sent with a negative value:

```json
{
  "event_name": "sw_refund",
  "custom_data": {
    "value": -9.99,
    "currency": "USD",
    "content_type": "product",
    "content_name": "com.app.premium.monthly",
    "content_ids": ["com.app.premium.monthly"]
  }
}
```

Sandbox handling [#sandbox-handling]

The integration supports separate credentials for sandbox events.

With sandbox credentials configured [#with-sandbox-credentials-configured]

When both **Sandbox Pixel Id** and **Sandbox Access Token** are provided:

* Production events use the main credentials.
* Sandbox events use the sandbox credentials.
* Events stay separated in Meta Events Manager.

Without sandbox credentials [#without-sandbox-credentials]

When sandbox credentials are not provided:

* Production events are sent normally.
* Sandbox events are skipped entirely.
* Test data does not pollute your production Pixel.

Test event mode [#test-event-mode]

Use the **Test Event Code** field to validate the integration without affecting
production metrics.

1. Copy a test event code from Meta Events Manager.
2. Add it to the **Test Event Code** field.
3. Send test events from your app.
4. Verify the events in the **Test Events** tab.

Events sent with a test event code:

* Appear in **Test Events**
* Do not count toward main metrics
* Are not used for optimization

Remove the **Test Event Code** value before sending live traffic.

Testing the integration [#testing-the-integration]

Validate the integration end-to-end before relying on it for attribution.

1\. Configure a test event code [#1-configure-a-test-event-code]

Add a value to the **Test Event Code** field from Meta Events Manager.

2\. Send test events [#2-send-test-events]

Trigger subscription events from your app in sandbox mode.

3\. Verify in Meta Events Manager [#3-verify-in-meta-events-manager]

1. Go to Meta Events Manager.
2. Select your Pixel.
3. Open **Test Events**.
4. Verify the event names, parameters, and user data.

4\. Check event quality [#4-check-event-quality]

1. Open your Pixel in Meta Events Manager.
2. Check the **Event Match Quality** score.
3. Review whether matching quality is acceptable for your use case.

5\. Test scenarios [#5-test-scenarios]

* [ ] Production event sends to the main Pixel
* [ ] Sandbox event sends to the sandbox Pixel when configured
* [ ] Sandbox event is skipped when sandbox credentials are not configured
* [ ] Trial start maps to `StartTrial`
* [ ] Subscription start maps to `Subscribe`
* [ ] Renewal maps to `Purchase`
* [ ] Cancellation sends as a custom event
* [ ] Anonymous users follow your configured behavior
* [ ] Revenue is included for paid events
* [ ] Test event code appears in **Test Events**

Best practices [#best-practices]

1. Use System User tokens for production access.
2. Configure separate sandbox credentials for testing.
3. Remove the **Test Event Code** value before going live.
4. Keep `external_id` values consistent across data sources.
5. Monitor Event Match Quality in Events Manager.
6. Prefer Meta standard events when they fit your use case.

Common use cases [#common-use-cases]

Optimizing campaigns for subscriptions [#optimizing-campaigns-for-subscriptions]

1. Send `Subscribe` events for new paid subscriptions.
2. Create a custom conversion in Meta Ads Manager based on `Subscribe`.
3. Optimize campaigns for subscription conversions.

Measuring trial-to-paid conversion [#measuring-trial-to-paid-conversion]

1. Track `StartTrial` for trial starts.
2. Track `Purchase` for trial conversions.
3. Build reporting in Meta around the conversion funnel.

Retargeting churned users [#retargeting-churned-users]

1. Track `sw_subscription_cancelled`.
2. Build a custom audience for cancelled users.
3. Run re-engagement campaigns with targeted offers.

Value-based optimization [#value-based-optimization]

1. Include revenue in `custom_data.value`.
2. Create value-based conversions in Meta.
3. Optimize for higher-value subscribers.

Troubleshooting [#troubleshooting]

Events not appearing in Events Manager [#events-not-appearing-in-events-manager]

**Possible causes:**

* Invalid access token
* Incorrect Pixel ID
* Sandbox events without sandbox credentials
* Test event code routing events into **Test Events** only

**Solutions:**

1. Verify your token has `ads_management`.
2. Confirm the Pixel ID matches Events Manager.
3. Check whether sandbox credentials are configured.
4. Clear the **Test Event Code** field if you expect the events in the main
   overview instead of **Test Events**.

Authentication errors (Error 190) [#authentication-errors-error-190]

**Possible causes:**

* Expired token
* Missing permissions
* Revoked token

**Solutions:**

1. Generate a new access token.
2. Verify the token has `ads_management`.
3. Prefer a System User token for long-term stability.

Low event match quality [#low-event-match-quality]

**Possible causes:**

* Only `external_id` is being sent
* Limited user data is available

**Solutions:**

* Keep `external_id` consistent with your other Meta data sources.
* Include additional user data if your webhook pipeline supports it.

Events show as duplicate [#events-show-as-duplicate]

**Possible causes:**

* The same event is sent more than once
* Event ID collisions

**Solutions:**

* Superwall uses the event ID for deduplication.
* Verify that the same webhook is not being processed multiple times.

Wrong event names [#wrong-event-names]

**Possible causes:**

* The event type maps differently than expected

**Solutions:**

1. Compare the payload against the mapping table above.
2. Validate with the **Test Event Code** field.
3. If you need a different naming scheme, note that the current dashboard UI
   does not expose custom event-name mappings for this integration.

Rate limits [#rate-limits]

Meta's Conversion API has the following published limits:

| Limit              | Value            |
| ------------------ | ---------------- |
| Requests per hour  | 10,000 per Pixel |
| Events per request | 1,000 maximum    |
| Request body size  | 1MB maximum      |

The integration sends one event per webhook, which is well within those limits.

API reference [#api-reference]

Endpoint [#endpoint]

```
POST https://graph.facebook.com/v21.0/{pixel_id}/events
```

Authentication [#authentication]

The access token is passed as a URL parameter:

```
?access_token={access_token}
```

Request headers [#request-headers]

```
Content-Type: application/json
Accept: */*
```

Response [#response]

**Success (200 OK)**:

```json
{
  "events_received": 1,
  "messages": [],
  "fbtrace_id": "ABC123..."
}
```

**Error (400/401/403)**:

```json
{
  "error": {
    "message": "Invalid OAuth access token.",
    "type": "OAuthException",
    "code": 190,
    "fbtrace_id": "ABC123..."
  }
}
```

Additional resources [#additional-resources]

* [Meta Conversion API Documentation](https://developers.facebook.com/docs/marketing-api/conversions-api)
* [Server Events Parameters Reference](https://developers.facebook.com/docs/marketing-api/conversions-api/parameters)
* [Event Quality Scoring Guide](https://www.facebook.com/business/help/765081237991954)
* [App Events Best Practices](https://developers.facebook.com/docs/app-events/best-practices)
* [Meta Events Manager](https://business.facebook.com/events_manager)
* [Facebook Pixel](/docs/integrations/facebook-pixel) for browser-side web
  paywall tracking