---
title: OpenAI Ads (Conversions)
slug: openai-ads-conversions
docTags: 
createdAt: 2026-09-14T18:32:29.834Z
---

**OpenAI Ads Conversions API** is OpenAI's server-side conversion API for ChatGPT Ads. This destination sends conversion events directly from Chord to OpenAI, so measurement is unaffected by ad blockers, browser tracking restrictions and cookie expiry.

Pair it with the **OpenAI Ads Pixel** destination. The two are complementary: the pixel captures OpenAI's `oppref` click identifier in the browser, which the Conversions API needs but does not collect for itself.

# Getting Started

You will need:

- **Pixel ID** — your OpenAI Ads pixel ID.
- **API Key** — your OpenAI Ads Conversions API key.

Create both in **OpenAI Ads Manager** under **Tools > Conversions**, which offers a **Conversion Key** and a **Pixel ID**.

:::BlockQuote
**Use the Conversion Key — not the API key under Settings.** Ads Manager exposes three credentials and only two of them belong here. The key under **Settings** authenticates OpenAI's separate management API, and the Conversions API rejects it as unauthorized. Chord never needs it.
:::

The Conversion Key is **per ad account, not per pixel** — one key covers every pixel in the account.

Optionally:

- **Custom Event Mapping** — routes a Chord event that OpenAI has no standard equivalent for onto a custom event you have registered in Ads Manager.
- **Validate Only** — OpenAI validates each event and records nothing. Use it to confirm an integration end to end without affecting reporting, then turn it off.

:::BlockQuote
**Important:** If you are also using the **OpenAI Ads Pixel** destination, both must be configured with the **same Pixel ID** and the **same Custom Event Mapping**. OpenAI deduplicates on the combination of pixel ID, event name and event ID — with a different pixel ID, one purchase is counted twice.
:::

# Connecting to the OpenAI Ads Conversions API CDP Destination

:::BlockQuote
**Warning:** Before connecting destinations in the Chord CDP, please verify with all Destination owners that all **non-Chord CDP** configured destinations are **disabled**. Running external destinations alongside configured Chord CDP destinations **can result in duplicate events downstream.**
:::

1. Log into the Chord data platform.
2. Navigate to the CDP.
3. Click the **"Add"** button next to Destinations.
4. Select **OpenAI Ads Conversions API** from the destination catalog.
5. Enter the destination name, your **Pixel ID** and your **API Key**.
6. Optionally configure **Custom Event Mapping** and **Validate Only**.
7. Click **"Create"** to connect.

# Verifying an Integration

Enable **Validate Only** before sending live traffic. OpenAI will validate the full shape of every event and record nothing, so you can confirm the integration is correct against a production pixel without affecting reporting. Turn it off once you are satisfied.

# Deduplication

Every event carries `id`, set to the Chord `messageId`.

OpenAI deduplicates on the combination of **pixel ID**, **event name** and **event ID**, keeping the first event it receives for a matching key and ignoring later duplicates. The **OpenAI Ads Pixel** destination sends the same `messageId` as its `event_id`, so a conversion reported by both the browser and your server is recorded once.

This also protects you if you are sending your own events to OpenAI alongside Chord: reuse the Chord `messageId` as your event ID and OpenAI will merge them rather than double-counting.

# Event Mapping

OpenAI accepts a fixed set of event names. A Chord event with no equivalent is skipped and logged — it is not forwarded under a made-up name, because OpenAI silently discards an unregistered event name, which is indistinguishable from a successful send.

| Chord Event            | OpenAI Event             | `data.type`       |
| ---------------------- | ------------------------ | ----------------- |
| `page`                 | `page_viewed`            | `contents`        |
| `Product Viewed`       | `contents_viewed`        | `contents`        |
| `Product Added`        | `items_added`            | `contents`        |
| `Checkout Started`     | `checkout_started`       | `contents`        |
| `Order Completed`      | `order_created`          | `contents`        |
| `Signed Up`            | `registration_completed` | `customer_action` |
| `Email Captured`       | `lead_created`           | `customer_action` |
| `Subscription Created` | `subscription_created`   | `plan_enrollment` |

To forward anything else, first create a custom event in ChatGPT Ads Manager, then add a **Custom Event Mapping** entry pointing your Chord event name at it. Names are lowercased and spaces become dashes, so `Wishlist Added` becomes `wishlist-added`; the result must be 1–64 characters of lowercase letters, numbers, underscores or dashes.

# Event Attributes

| OpenAI Attribute    | Chord Source (Priority Order)                       | Notes                            |
| ------------------- | --------------------------------------------------- | -------------------------------- |
| `id`                | `messageId`                                         | The deduplication key            |
| `type`              | —                                                   | Set from the event mapping table |
| `custom_event_name` | **Custom Event Mapping**                            | Custom events only               |
| `timestamp_ms`      | `timestamp` → `receivedAt`                          | Milliseconds since epoch         |
| `action_source`     | —                                                   | Always `web`                     |
| `source_url`        | `context.page.url`                                  |                                  |
| `oppref`            | `properties.oppref` → the captured click identifier | See **Attribution** below        |
| `data`              | See below                                           |                                  |

## Conversion Data

| OpenAI Attribute | Chord Source (Priority Order)                                  | Notes                                                        |
| ---------------- | -------------------------------------------------------------- | ------------------------------------------------------------ |
| `type`           | —                                                              | Set from the event mapping table. Required by OpenAI         |
| `amount`         | `properties.total` → `properties.revenue` → `properties.value` | **Minor currency units.** See below                          |
| `currency`       | `properties.currency`                                          | Defaults to `USD`. Required whenever `amount` is present     |
| `contents`       | `properties.products[]`                                        | Omitted for `customer_action` events, which do not accept it |
| `plan_id`        | `properties.subscription_id` → `properties.plan_id`            | `subscription_created` only                                  |

Each entry in `contents` is built from a product:

| OpenAI Attribute | Chord Source (Priority Order)                       |
| ---------------- | --------------------------------------------------- |
| `id`             | `properties.products[].product_id` → `.sku` → `.id` |
| `name`           | `properties.products[].name`                        |
| `content_type`   | `properties.products[].category`                    |
| `quantity`       | `properties.products[].quantity` (defaults to `1`)  |
| `amount`         | `properties.products[].price`, in minor units       |
| `currency`       | The order currency                                  |

## Amounts are sent in minor units

OpenAI expects every monetary amount as an **integer in the currency's minor unit**, so `$129.99` is sent as `12999`. Chord converts this for you.

Zero-decimal currencies are handled correctly: `1000 JPY` is sent as `1000`, not `100000`. This matters because OpenAI accepts either value without complaint — a mistake here would silently report every conversion 100 times too high.

# Conversion Matching

Visitor identifiers are SHA-256 hashed before being sent. Plaintext PII is never transmitted.

| OpenAI Attribute       | Chord Source (Priority Order)                         | Normalization                                                                                             |
| ---------------------- | ----------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| `emails_sha256`        | `context.traits.email` → `properties.email`           | Trimmed and lowercased, then hashed                                                                       |
| `phone_numbers_sha256` | `context.traits.phone` → `properties.phone`           | Country code kept; whitespace, punctuation, a leading `+` and leading zeroes removed. Must be 8–15 digits |
| `external_ids_sha256`  | `userId`                                              | Trimmed, **case preserved**, then hashed                                                                  |
| `first_names_sha256`   | `context.traits.first_name` → `properties.first_name` | Lowercased, whitespace and ASCII punctuation removed, accents preserved                                   |
| `last_names_sha256`    | `context.traits.last_name` → `properties.last_name`   | Same as first name                                                                                        |
| `cities`               | `context.traits.address.city`                         | Sent unhashed                                                                                             |
| `regions`              | `context.traits.address.state`                        | Sent unhashed                                                                                             |
| `postal_codes`         | `context.traits.address.zipcode`                      | Sent unhashed                                                                                             |
| `countries`            | `context.traits.address.country`                      | Sent unhashed                                                                                             |
| `obref`                | The `__obref` cookie set by the OpenAI Ads Pixel      | Sent unhashed                                                                                             |
| `ip_address`           | `requestIp` → `context.ip`                            | Sent unhashed                                                                                             |
| `user_agent`           | `context.userAgent`                                   | Sent unhashed                                                                                             |

A value that cannot be used is omitted rather than hashed — placeholders such as `n/a` or `unknown`, and phone numbers too short to be real, are dropped, since hashing them would send a digest that matches nobody. A value that is already a SHA-256 hash is passed through rather than hashed twice.

These normalization rules match the **OpenAI Ads Pixel** destination exactly, so the same person produces the same digest on both surfaces. Note that the Conversions API's field names are **plural lists** (`emails_sha256`) while the pixel's are singular (`email_sha256`) — both are correct for their own surface.

Chord sends one value per identifier. OpenAI accepts up to three per list and ignores the rest.

Android advertising IDs are forwarded as `android_advertising_id` on Android traffic only. OpenAI has no equivalent field for an iOS advertising ID, so one is omitted rather than sent in the wrong place.

# Attribution

OpenAI appends an `oppref` query parameter to ad click-through URLs, and OpenAI's documentation is explicit that — unlike the pixel — **the Conversions API does not capture it for you**.

Chord handles this: the browser library captures `oppref` from the landing page URL, along with the `__oppref` and `__obref` cookies the OpenAI Ads Pixel sets, and carries them on every subsequent event including the purchase. Installing the **OpenAI Ads Pixel** destination alongside this one is therefore the recommended setup — without a source of `oppref`, server-side conversions cannot be attributed to the ad that produced them.

If your storefront resolves the click identifier itself, send it as `properties.oppref` and it will be used in preference.

# Timestamps

OpenAI rejects an event timestamped more than 7 days in the past or more than 10 minutes in the future. If an event carries no parseable timestamp, Chord falls back to the time it was received and then to the current time — an approximate time is better than a rejected batch, which would lose the event entirely.

# Retries

Rate limits (`429`) and OpenAI server errors (`5xx`) are retried. A rejected event — a validation failure such as a malformed field — is not retried, because the same payload would be rejected again; it is logged instead so it appears in the CDP function logs.

# Property Mappings

OpenAI validates each event against a closed schema: the conversion data accepts only `type`,
`amount`, `currency`, `contents` and `plan_id`, and there is no custom-data field. An unrecognized
field causes OpenAI to reject the event, so Chord forwards no custom properties to this
destination.

Mappings that change **where a supported field is read from** work normally — that is what the
table below is for. If you need to segment on a property OpenAI has no field for, register a custom
event in Ads Manager and route to it with **Custom Event Mapping** instead.

Use custom property mappings if the default field resolution does not match your data model:

| Destination Path                   | Description                                             |
| ---------------------------------- | ------------------------------------------------------- |
| `properties.email`                 | Email for conversion matching                           |
| `properties.phone`                 | Phone for conversion matching                           |
| `properties.first_name`            | First name for conversion matching                      |
| `properties.last_name`             | Last name for conversion matching                       |
| `properties.total`                 | Primary conversion value                                |
| `properties.revenue`               | Fallback conversion value                               |
| `properties.currency`              | Currency code (e.g. `USD`)                              |
| `properties.oppref`                | OpenAI click identifier, if your storefront resolves it |
| `properties.subscription_id`       | Plan ID for subscription events                         |
| `properties.products[].product_id` | Product content IDs                                     |
| `properties.products[].price`      | Per-item price                                          |
| `properties.products[].quantity`   | Per-item quantity                                       |
| `properties.products[].name`       | Product display name                                    |
| `properties.products[].category`   | Product content type                                    |

# Troubleshooting

**No events appear in Ads Manager**

- Confirm the **API Key** is the **Conversion Key** from the Conversions section, not the API key from **Settings**. The Settings key belongs to OpenAI's management API and is rejected here.
- Confirm the **Pixel ID** and the key belong to the same ad account.
- Confirm **Validate Only** is switched off. While it is on, OpenAI validates events and records nothing.
- Check the CDP function logs for the response OpenAI returned.

**Conversions are attributed to no campaign**

- Confirm the **OpenAI Ads Pixel** destination is installed on the storefront, since it is what captures `oppref`.
- Confirm the `__oppref` cookie is being set and is not blocked by a consent tool.

**A conversion is counted twice**

- Confirm the **OpenAI Ads Pixel** destination uses the **same Pixel ID**.
- Confirm any events you send to OpenAI outside Chord reuse the Chord `messageId` as their event ID.

**An event is missing entirely**

- Check the CDP function logs for a "Skipping unsupported event" warning. OpenAI accepts only the fixed event set above; anything else needs a registered custom event and a **Custom Event Mapping** entry.

***

**Updated 31 August 2026**
