> For the complete documentation index, see [llms.txt](https://docs.chord.co/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.chord.co/activate/event-streaming/destinations/advertising/openai-ads/openai-ads-pixel.md).

# OpenAI Ads Pixel

**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 browser destination. Chord loads the OpenAI SDK for you, so you don't install any script.

You need your **Pixel ID**, created in **OpenAI Ads Manager** under **Tools > Conversions**.

This destination needs no key of its own. The **Conversion Key** shown alongside the Pixel ID is only for the OpenAI Ads Conversions API destination, and the key under **Settings** is a third credential that Chord never uses.

## Settings

* **Destination Name** (required): a name to tell this destination apart.
* **Pixel ID** (required): your pixel ID from OpenAI Ads Manager under **Tools > Conversions**. Use the same ID as the OpenAI Ads Conversions API destination so OpenAI can deduplicate. This destination needs no key of its own.
* **Enable Advanced Matching** (optional): sends the visitor's email, phone, customer ID and name to OpenAI as SHA-256 hashes to improve conversion matching. Nothing is sent in plaintext. Off by default.
* **Custom Event Mapping** (optional): routes a Chord event with no standard OpenAI equivalent onto a custom event you registered in Ads Manager. Use the same mapping as the OpenAI Ads Conversions API destination.
* **Debug Logging** (optional): turns on the OpenAI pixel's own browser console logging. Off by default. Leave it off in production.
* **Consent Requirements** (optional): the consent categories a visitor must accept before Chord sends data to this destination. See [Consent](/activate/event-streaming/consent.md).

## Connect

Add **OpenAI Ads Pixel** from the destination catalog, using the settings above. See [Connect a destination](/activate/event-streaming/connect-a-destination.md) for the full steps.

## Deduplication

Every event carries `event_id`, set to the Chord message ID, so a conversion reported by both the browser and your server is recorded once. See [How they deduplicate](/activate/event-streaming/destinations/advertising/openai-ads.md#how-they-deduplicate).

## 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             | Notes                          |
| ----------------------------------------------------------------------------- | ------------------------ | ------------------------------ |
| `page`                                                                        | `page_viewed`            | `data.type`: `contents`        |
| [`Product Viewed`](/collect-data/tracking-plan/product-viewed.md)             | `contents_viewed`        | `data.type`: `contents`        |
| [`Product Added`](/collect-data/tracking-plan/product-added.md)               | `items_added`            | `data.type`: `contents`        |
| [`Checkout Started`](/collect-data/tracking-plan/checkout-started.md)         | `checkout_started`       | `data.type`: `contents`        |
| [`Order Completed`](/collect-data/tracking-plan/order-completed.md)           | `order_created`          | `data.type`: `contents`        |
| [`Signed Up`](/collect-data/tracking-plan/signed-up.md)                       | `registration_completed` | `data.type`: `customer_action` |
| [`Email Captured`](/collect-data/tracking-plan/email-captured.md)             | `lead_created`           | `data.type`: `customer_action` |
| [`Subscription Created`](/collect-data/tracking-plan/subscription-created.md) | `subscription_created`   | `data.type`: `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.

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

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

| Chord source (priority order)                       | OpenAI field   | Notes |
| --------------------------------------------------- | -------------- | ----- |
| `properties.products[].product_id` → `.sku` → `.id` | `id`           |       |
| `properties.products[].name`                        | `name`         |       |
| `properties.products[].category`                    | `content_type` |       |
| `properties.products[].quantity` (defaults to `1`)  | `quantity`     |       |
| `properties.products[].price`, in minor units       | `amount`       |       |
| The order currency                                  | `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.

| Chord source (priority order)                         | OpenAI field          | Notes                                                                                                                    |
| ----------------------------------------------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `context.traits.email` → `properties.email`           | `email_sha256`        | Normalization: Trimmed and lowercased, then hashed                                                                       |
| `context.traits.phone` → `properties.phone`           | `phone_number_sha256` | Normalization: Country code kept; whitespace, punctuation, a leading `+` and leading zeroes removed. Must be 8–15 digits |
| `userId`                                              | `external_id_sha256`  | Normalization: Trimmed, **case preserved**, then hashed                                                                  |
| `context.traits.first_name` → `properties.first_name` | `first_name_sha256`   | Normalization: Lowercased, whitespace and ASCII punctuation removed, accents preserved                                   |
| `context.traits.last_name` → `properties.last_name`   | `last_name_sha256`    | Normalization: Same as first name                                                                                        |
| `context.traits.address.city`                         | `city`                | Normalization: Sent unhashed                                                                                             |
| `context.traits.address.state`                        | `region`              | Normalization: Sent unhashed                                                                                             |
| `context.traits.address.zipcode`                      | `postal_code`         | Normalization: Sent unhashed                                                                                             |
| `context.traits.address.country`                      | `country`             | Normalization: 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

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

Chord 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 **Live Events** stream to confirm the event reached Chord 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.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.chord.co/activate/event-streaming/destinations/advertising/openai-ads/openai-ads-pixel.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
