---
title: OpenAI Ads (Pixel)
slug: openai-ads-pixel
docTags: 
createdAt: 2026-09-14T18:33:06.810Z
---

**OpenAI Ads Pixel** is OpenAI's browser measurement pixel for ChatGPT Ads. This destination loads the OpenAI measurement SDK (`oaiq`) in the browser and forwards ecommerce and engagement events to OpenAI Ads.

Pair it with the **OpenAI Ads Conversions API** destination. The two are complementary rather than redundant, and the pixel does one thing the server-side API cannot: it captures and persists OpenAI's `oppref` click identifier, which is what links a conversion back to the ad that produced it.

# Getting Started

This is a device-mode destination. The OpenAI SDK is loaded automatically — no manual script installation is required. You will need:

- **Pixel ID** — created in **OpenAI Ads Manager** under **Tools > Conversions**. Use the same value in the OpenAI Ads Conversions API destination, so OpenAI can deduplicate a conversion reported by both.
  This destination needs no key of its own. The **Conversion Key** shown alongside the Pixel ID is only for the server-side Conversions API destination, and the key under **Settings** is a third credential that Chord never uses.

Optionally:

- **Enable Advanced Matching** — sends the visitor's email, phone, customer ID and name to OpenAI as SHA-256 hashes to raise the match rate. Nothing is sent in plaintext.
- **Custom Event Mapping** — routes a Chord event that OpenAI has no standard equivalent for onto a custom event you have registered in Ads Manager.
- **Debug Logging** — turns on the OpenAI SDK's own browser console logging. Useful while verifying an installation; leave off in production.

:::BlockQuote
**Important:** If you are also using the **OpenAI Ads Conversions API** 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 Pixel 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 Pixel** from the destination catalog.
5. Enter the destination name and your **Pixel ID**.
6. Optionally enable **Advanced Matching** and configure **Custom Event Mapping**.
7. Click **"Create"** to connect.

# Deduplication

Every event carries `event_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 Conversions API 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

Every event sends an `eventData` object whose `type` matches the table above.

| 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.

# Advanced Matching

When **Enable Advanced Matching** is on, visitor identifiers are SHA-256 hashed in the browser before being sent. Plaintext PII is never transmitted.

| OpenAI Attribute      | Chord Source (Priority Order)                         | Normalization                                                                                             |
| --------------------- | ----------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| `email_sha256`        | `context.traits.email` → `properties.email`           | Trimmed and lowercased, then hashed                                                                       |
| `phone_number_sha256` | `context.traits.phone` → `properties.phone`           | Country code kept; whitespace, punctuation, a leading `+` and leading zeroes removed. Must be 8–15 digits |
| `external_id_sha256`  | `userId`                                              | Trimmed, **case preserved**, then hashed                                                                  |
| `first_name_sha256`   | `context.traits.first_name` → `properties.first_name` | Lowercased, whitespace and ASCII punctuation removed, accents preserved                                   |
| `last_name_sha256`    | `context.traits.last_name` → `properties.last_name`   | Same as first name                                                                                        |
| `city`                | `context.traits.address.city`                         | Sent unhashed                                                                                             |
| `region`              | `context.traits.address.state`                        | Sent unhashed                                                                                             |
| `postal_code`         | `context.traits.address.zipcode`                      | Sent unhashed                                                                                             |
| `country`             | `context.traits.address.country`                      | 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 Conversions API destination exactly, so the same person produces the same digest on both surfaces. Note that the pixel's field names are **singular** (`email_sha256`) while the Conversions API's are plural lists (`emails_sha256`) — both are correct for their own surface.

Advanced matching is supplied at initialization, which is the only point at which the pixel accepts user data. An `identify` call re-initializes the pixel with the newly known identifiers, raising the match rate for the rest of the session; `identify` itself is not reported as a conversion.

# Attribution

OpenAI appends an `oppref` query parameter to ad click-through URLs. The pixel captures it on the landing page and persists it in a first-party `__oppref` cookie, along with its own `__obref` browser reference, so later events in the session remain attributable.

Chord also captures both values onto the event, which is what allows the **OpenAI Ads Conversions API** destination to attribute server-side conversions — OpenAI's documentation is explicit that the Conversions API does not collect `oppref` on its own.

# Custom Properties Are Not Forwarded

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 on the event
either — unlike most ad platforms, which provide one.

An unrecognized field is not ignored. OpenAI rejects the whole event, and the pixel logs
`validation failed; event dropped` in the browser console. So Chord deliberately sends **no**
custom properties to this destination; forwarding them would discard the conversion entirely.

Custom property mappings that target a field OpenAI *does* accept still work normally. Remapping
the source of the order total or currency, for example, reaches OpenAI as usual — it is only
destination fields with no OpenAI equivalent that cannot be delivered. The same applies to the
OpenAI Ads Conversions API destination.

If you need to segment on a property OpenAI has no field for, register a custom event in Ads
Manager and route the event to it with **Custom Event Mapping** instead.

# Empty Cart Protection

An `Order Completed` event with no products is skipped with a warning rather than sent. An empty purchase is a data-quality problem upstream, not a conversion, and forwarding it would corrupt your conversion counts and revenue reporting.

# Initialization

The plugin loads `https://bzrcdn.openai.com/sdk/oaiq.min.js` and calls `oaiq("init", { pixelId })`. Before the script loads, a queue buffers any calls to `oaiq` so nothing is lost during load.

The plugin manages SDK loading state (`fresh` → `loading` → `loaded`/`failed`) to prevent duplicate script loads. If the script fails to load, subsequent events are silently dropped.

The OpenAI SDK does **not** fire a page view on initialization. Chord sends `page_viewed` explicitly for each `page` call, so page views are neither missed nor doubled.

# CSP Requirements

If your site uses a Content Security Policy, allow the following domains:

| Directive     | Domain                      |
| ------------- | --------------------------- |
| `script-src`  | `https://bzrcdn.openai.com` |
| `connect-src` | `https://bzr.openai.com`    |

# Troubleshooting

**No events appear in Ads Manager**

- Confirm the **Pixel ID** matches the `pixel_id` returned when the pixel was created.
- Enable **Debug Logging** and check the browser console for the SDK's own output.
- Check the Chord CDP **Live Events** stream to confirm the event reached the CDP at all.

**Events appear but conversions are attributed to no campaign**

- Confirm `oppref` is present in the landing page URL when arriving from an ad.
- Check that the `__oppref` cookie is being set and is not blocked by a consent tool.

**A conversion is counted twice**

- Confirm the **OpenAI Ads Conversions API** 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 browser console 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**
