ChatGPT Ads conversion tracking: the pixel, the Conversions API, and attribution
ChatGPT Ads conversion tracking runs on three mechanisms — the oaiq JavaScript pixel, the server-side Conversions API, and a no-JavaScript image tag — all reporting into a Pixel ID you provision in Ads Manager. Since conversion-optimized bidding went live in June 2026, this is no longer just reporting plumbing: the events you send are the training signal for the auction, so a broken implementation actively degrades delivery. This guide is the full implementation reference — code, all 13 events, deduplication, hashing rules, consent and CSP, platform-specific setup, QA, and an honest account of the attribution you will still be missing.
The 2-sentence answer
ChatGPT Ads conversion tracking works through three mechanisms — the oaiq JavaScript pixel for browser events, the Conversions API for server-side events, and a no-JavaScript image tag fallback — all reporting into a Pixel ID you provision in Ads Manager's conversions tab. Running the pixel and the Conversions API together, deduplicated by a shared event_id, is the configuration OpenAI's own documentation points toward and the only one that survives ad blockers, cookie restrictions, and multi-session buying journeys.
The short version
- The pixel is
oaiq, loaded frombzrcdn.openai.com, initialized with your Pixel ID. Events fire viaoaiq("measure", "<event>", { … }). - The Conversions API endpoint is
https://bzr.openai.com/v1/events?pid=<PIXEL-ID>— POST, bearer auth, up to 1,000 events per batch, and the whole batch fails if one event fails. - 13 standard events across four data shapes:
contents,customer_action,plan_enrollment, andcustom. - Money is in minor units. $129.99 is
12999. Always sendcurrencywithamount. - Deduplicate by reusing one identifier as the pixel's
event_idand the API'sid. - Hash email and external IDs with SHA-256, lowercase hex. Never send raw emails, IDs, or phone numbers. Geography, IP, and user agent go through raw.
- Timestamps must be within the last 7 days and no more than 10 minutes in the future.
- Only about 40% of ChatGPT-influenced conversions happen in the click session. Plan measurement accordingly.
Why conversion tracking is the bottleneck for ChatGPT Ads
Every new ad channel has a moment where measurement catches up with inventory, and until it does, sophisticated advertisers stay away. ChatGPT Ads had that moment in mid-2026, and the before-and-after is instructive.
In April 2026, an r/PPC thread asking what the platform was like drew a description of the reporting: "Super basic ads manager. Can use it to build campaigns, ad sets and ads. Reporting wise all it has is spend, impressions and clicks" (r/PPC, April 2026). A month later, a thread titled "ChatGPT ads are here, but can we measure their performance?" opened with a problem that will be familiar to anyone who has launched on a young channel:
A SaaS friend of mine said that they got a wave of demo requests after their first trial of these ads, but analytics only labelled most of it as direct traffic. There was no clear source or data they could use for attribution. — r/PPC, "ChatGPT ads are here, but can we measure their performance?", May 26, 2026
The responses split into two camps that are both still worth understanding. One camp diagnosed a setup problem — "Sounds like your friend had a setup issue. You can absolutely put whatever tracking query parameters you want (e.g. UTMs) on paid ad URLs for it. Or install their pixel / CAPI and measure within their universe based on attribution." The other camp argued the problem is structural: "ChatGPT attribution is gonna look more like podcast ads or word of mouth than Google Ads. People see a recommendation, leave, come back later through search or direct traffic, and analytics just labels it as 'direct'."
Both are right, and the distinction organizes this entire guide. The setup problem is solvable today — this article is largely about solving it. The structural problem is not fully solvable, and pretending otherwise leads to bad decisions. AdVenture Media's attribution analysis suggests only about 40% of conversions attributable to ChatGPT ads occur in the immediate post-click session, with the remaining 60% arriving hours, days, or weeks later through paths with no traceable link back to the original interaction.
What changed is that OpenAI shipped real measurement infrastructure. As of the May 5 self-serve beta, a pixel and conversion events existed. By June 5, conversion optimization went live, which made accurate tracking not merely a reporting nicety but the input that drives bidding — see our companion guide on ChatGPT Ads conversion campaigns for how that bidding works. Get tracking wrong now and you are not just mis-measuring; you are actively training the auction on bad data.
Three ways to send a conversion — and when to use each
OpenAI provides three ingestion methods. They are not equivalent, and the right architecture for most advertisers uses at least two of them.
| JavaScript pixel | Conversions API | Image tag | |
|---|---|---|---|
| Runs where | Browser | Your server | Browser (HTML only) |
Captures oppref | Automatically | You must pass it | No — pass manually |
| Blocked by ad blockers | Yes | No | Sometimes |
| Can measure interactions | Yes (clicks, submits) | Yes (any backend event) | No — page load only |
| Batching | Automatic | Up to 1,000 events | One event per request |
| Offline / CRM events | No | Yes | No |
| App events | No | Yes (mobile_app) | No |
| Personal data | Hashed only | Hashed only | Never |
The recommended architecture: pixel for browser-observable events (page views, add-to-cart, checkout starts) plus Conversions API for anything financially meaningful (orders, qualified leads, subscription starts, refunds you want excluded), deduplicated on a shared identifier. The image tag is a fallback for environments where you genuinely cannot run JavaScript — email landing pages, some CMS templates, AMP-like constraints — not a primary method.
Why server-side matters more here than on other channels
ChatGPT Ads sends traffic to your site from an app context, and a meaningful share of that traffic arrives on mobile browsers with aggressive tracking prevention. A browser-only setup loses those conversions silently — they simply never appear. Because oCPC uses conversions as its training signal, missing conversions do not just under-report performance; they teach the optimizer that a converting audience segment does not convert.
The JavaScript pixel: full implementation
The pixel loads an SDK called oaiq. Place the snippet in your <head>, replacing the placeholder with the Pixel ID from Ads Manager's conversions tab.
<script>
(function (w, d, s, u) {
if (w.oaiq) return;
var q = function () { q.q.push(arguments); };
q.q = [];
w.oaiq = q;
var js = d.createElement(s);
js.async = true;
js.src = u;
var f = d.getElementsByTagName(s)[0];
f.parentNode.insertBefore(js, f);
})(window, document, "script", "https://bzrcdn.openai.com/sdk/oaiq.min.js");
oaiq("init", { pixelId: "<YOUR-PIXEL-ID>" });
</script>
The pattern is a standard command queue: calls made before the SDK finishes loading are buffered and replayed. That means you can call oaiq("measure", …) immediately after init without waiting for the script.
Initialization options
Add debug: true during implementation to get console output confirming events fire:
oaiq("init", {
pixelId: "<YOUR-PIXEL-ID>",
debug: true
});
You can also pass user data at init time to improve match rates. As of September 2026 the Pixel accepts a wider identity set: alongside hashed email and external ID it now supports hashed phone, hashed first name, hashed last name, region and postal code. Personal identifiers must be pre-hashed with SHA-256; geographic fields are sent raw:
oaiq("init", {
pixelId: "<YOUR-PIXEL-ID>",
user: {
email_sha256: "<SHA256_HASH>",
phone_number_sha256: "<SHA256_HASH>", // added Sept 2026
first_name_sha256: "<SHA256_HASH>", // added Sept 2026
last_name_sha256: "<SHA256_HASH>", // added Sept 2026
external_id_sha256: "<SHA256_HASH>",
country: "US",
city: "San Francisco",
region: "CA", // added Sept 2026
zip_code: "94107"
}
});
What the SDK handles for you
Four things happen automatically, and knowing them prevents a lot of redundant engineering:
- It captures
oppref, OpenAI's privacy-preserving identifier, from the landing page URL. - It stores that value in a first-party cookie named
__oppref, so later events in the session remain attributable. - It adds the current page as
source_url. - It timestamps and batches events.
The __oppref cookie is the linchpin of the whole attribution model, and it is the value your server-side implementation will need. If you are building Conversions API calls, read that cookie and pass it as obref in the user object — this is how server events get stitched to the original ad click.
Firing an event
The signature is oaiq("measure", "<event_name>", <data>, <options>):
oaiq("measure", "order_created", {
type: "contents",
amount: 2599, // $25.99 in minor units
currency: "USD",
contents: [{
id: "sku_123",
name: "Product name",
content_type: "product",
quantity: 1
}]
}, {
event_id: "order_12345" // for deduplication
});
12999, not 129.99. This is the single most common implementation bug, and it fails silently — your revenue reporting is simply off by 100×. Always include currency whenever you send amount.
The event taxonomy and data shapes
There are 13 standard events. Each requires a specific type in its data object, and sending the wrong shape causes rejection.
| Event | Data type | Meaning |
|---|---|---|
order_created | contents | A purchase is completed |
checkout_started | contents | A user starts checkout |
items_added | contents | Items added to a cart, bundle, or selection |
contents_viewed | contents | A product, listing, article, or content unit is viewed |
page_viewed | contents | A user lands on or views an important page |
lead_created | customer_action | A lead form is submitted or contact requested |
registration_completed | customer_action | An account or event registration finishes |
appointment_scheduled | customer_action | A meeting, demo, or consultation is booked |
app_installed | customer_action | An app is installed (API only) |
app_opened | customer_action | An app is opened (API only) |
trial_started | plan_enrollment | A free trial starts |
subscription_created | plan_enrollment | A paid subscription starts |
custom | custom | Anything outside the standard taxonomy |
The four data shapes
contents — commerce events carrying an item list:
{
type: "contents",
amount: 2599,
currency: "USD",
contents: [
{ id: "sku_123", name: "Product name", content_type: "product", quantity: 1 }
]
}
customer_action — leads and registrations, where amount is optional:
{
type: "customer_action",
amount: 5000, // optional — e.g. estimated lead value
currency: "USD" // optional
}
plan_enrollment — trials and subscriptions:
{
type: "plan_enrollment",
plan_id: "pro_monthly",
amount: 2000,
currency: "USD"
}
custom — for anything the taxonomy does not cover:
oaiq("measure", "custom",
{ type: "custom" },
{ custom_event_name: "quote_requested" }
);
Custom event names must be 1–64 characters, alphanumeric plus underscores and dashes, starting and ending with a letter or number.
Custom events cannot drive bidding
Custom events are fine for measurement, but they cannot be used as an oCPC optimization goal. If a custom event represents your real business outcome, fire a standard event alongside it — lead_created next to quote_requested, for instance — so you have something to optimize toward. Discovering this after building your whole taxonomy on custom events is an expensive rebuild.
The Conversions API: server-side tracking
The Conversions API sends events from your infrastructure directly to OpenAI, bypassing the browser entirely. This is how you capture conversions that ad blockers eat, that happen after the session ends, or that occur in systems the browser never sees — a CRM stage change, a subscription renewal, an offline sale.
Endpoint and authentication
POST https://bzr.openai.com/v1/events?pid=<PIXEL-ID>
Authorization: Bearer <API-KEY>
Content-Type: application/json
Both the Pixel ID and API key are provisioned through Ads Manager's conversions tab. The request body takes an events array and an optional validation flag:
{
"validate_only": true,
"events": [ /* … */ ]
}
Use validate_only: true during implementation. It exercises the full validation path without recording data — the safest way to confirm your payloads are correct before sending live conversions.
Batch constraints
Up to 1,000 events per batch, and critically: the entire batch fails if one event fails. This is an all-or-nothing contract, and it has real architectural consequences. A single malformed event in a batch of 900 discards all 900. Validate each event before assembling the batch, and consider smaller batches so a poison record has a smaller blast radius.
Event object fields
| Field | Required | Notes |
|---|---|---|
id | Yes | Unique identifier; used with type for deduplication |
type | Yes | Standard event name or custom |
timestamp_ms | Yes | Milliseconds; within last 7 days, ≤10 min in future |
data | Yes | Must match the event's required shape |
custom_event_name | Conditional | Required when type is custom |
source_url | Conditional | Required when action_source is web |
action_source | Conditional | web, mobile_app, offline, physical_store, phone_call, email, other |
oppref | No | Not auto-captured server-side — you must pass it |
user | No | Identity matching object |
opt_out | No | true excludes from future personalization |
A complete server-side purchase event:
{
"events": [{
"id": "order_12345",
"type": "order_created",
"timestamp_ms": 1785000000000,
"action_source": "web",
"source_url": "https://example.com/checkout/complete",
"user": {
"obref": "<value of __oppref cookie>",
"email_sha256": "<64-char lowercase hex>",
"country": "US",
"ip_address": "203.0.113.42",
"user_agent": "Mozilla/5.0 …"
},
"data": {
"type": "contents",
"amount": 12999,
"currency": "USD",
"contents": [
{ "id": "sku_123", "name": "Product name", "content_type": "product", "quantity": 1 }
]
}
}]
}
Since September 2026 the identity fields in user also accept list values — hashed email, phone, external ID, first and last name, country, city, region and postal code can each carry several entries, and Android GAID is supported for mobile events. See the September 2026 field expansion for the shape and the caveats.
App events
App lifecycle events are Conversions API only and require action_source: "mobile_app":
{
"type": "app_installed",
"action_source": "mobile_app",
"data": { "type": "customer_action" }
}
Note that during the self-serve beta the Ads Manager data-source form offered Web, iOS, and Android types with only Web selectable — iOS and Android were marked "Coming soon." An advertiser documenting the beta called this "the single biggest limitation for anyone planning to run ChatGPT ads for an app." Verify current availability in your own account before planning an app-install campaign.
Deduplication: making pixel and API agree
If you run both the pixel and the Conversions API — which you should — the same purchase will be reported twice unless you deduplicate. The mechanism is simple and unforgiving: use the same identifier as the pixel's event_id and the API's id.
// Browser
oaiq("measure", "order_created",
{ type: "contents", amount: 12999, currency: "USD" },
{ event_id: "order_12345" }
);
// Server — same identifier in the `id` field
{ "id": "order_12345", "type": "order_created", … }
OpenAI's documentation states it directly: "If you send the same conversion from the pixel and the Conversions API, reuse the same value as the API id and pixel event_id." For custom events, custom_event_name must also match on both sides.
Choosing the identifier
The identifier must be stable across both systems and unique per conversion. Your order ID or transaction ID is almost always the right choice, because both the browser (on the confirmation page) and your backend (in the order record) know it.
What does not work: timestamps, random values generated independently on each side, session IDs, or anything the server cannot reconstruct. If the browser generates a UUID that the server never learns, the two events will never match and you will double-count every order.
For lead forms, use the CRM record ID if the browser can see it after submission; otherwise a hash of email plus form ID plus a coarse timestamp is a workable deterministic fallback — as long as both sides compute it identically.
Identity matching and hashing rules
The user object improves attribution by giving OpenAI more ways to connect a conversion back to an ad interaction. The rules about what gets hashed and what does not are strict, and getting them backwards means either rejected events or a privacy incident.
| Field | Format | Hashed? | Where |
|---|---|---|---|
obref | First-party cookie value, unmodified | No | Both |
email_sha256 | SHA-256, lowercase 64-char hex | Yes | Both |
phone_number_sha256 | SHA-256 of the normalized E.164 number | Yes | Both new |
external_id_sha256 | SHA-256, lowercase 64-char hex | Yes | Both |
first_name_sha256 | SHA-256 of the normalized given name | Yes | Both new |
last_name_sha256 | SHA-256 of the normalized family name | Yes | Both new |
country | ISO 3166-1 code, e.g. US | No | Both |
city | Max 128 chars, lowercased, trimmed | No | Both |
region | State, province or region code | No | Both new |
zip_code | Max 32 chars; letters, numbers, spaces, hyphens | No | Both |
| Android GAID | Google Advertising ID, standard UUID form | No | Conversions API new |
ip_address | IPv4 or IPv6 | No | Both |
user_agent | Non-empty browser string | No | Both |
The governing rule from OpenAI's documentation: send raw values for geographic, IP, and user-agent fields; hash email, phone, names and external IDs; and never send raw emails, IDs, or phone numbers.
The September 2026 field expansion
OpenAI widened both measurement paths in the same release, and the two got different treatment:
- Measurement Pixel: adds hashed phone, first name, last name, region and postal code to the identity payload it previously accepted.
- Conversions API: adds plural list fields for hashed email, phone, external ID, first and last name, country, city, region and postal code — meaning a single event can carry multiple values per field rather than one — plus Android GAID for mobile.
The plural list fields are the more interesting half, and the easiest to underuse. Real customers have more than one email, more than one phone, more than one shipping address and more than one account ID across your systems. Until now a conversion event had to nominate a single winner per field, and any match had to land on that one guess. Sending the set instead of the pick is a strictly better shape for matching, and it costs nothing but a schema change on your side.
A server-side event using the plural form:
"user": {
"obref": "<value of __oppref cookie>",
"email_sha256": ["<hash of primary>", "<hash of billing email>"],
"phone_number_sha256": ["<hash of mobile>", "<hash of landline>"],
"external_id_sha256": ["<hash of CRM id>", "<hash of legacy id>"],
"country": ["US"],
"region": ["CA"],
"zip_code": ["94107", "94110"]
}
Three cautions before you widen the payload. First, every added field is customer data leaving your systems: the consent and DPA review that applied to Automatic Advanced Matching now applies across a much broader surface, and names plus postal geography are a materially more identifying combination than a hashed email alone. Hashing is not anonymisation — a hashed email is still personal data under GDPR. Second, normalize before hashing, per field: names need the same trim-and-lowercase discipline as emails, and an unnormalized Smith hashes to something no smith will ever match. Third, more fields means more silent failure surface — a field you populate wrongly does not error, it just never matches, so add fields in tranches and watch match quality move rather than shipping all nine at once and guessing which one helped.
Normalizing before hashing
Hashing is only useful if both sides derive the same hash from the same person, which means normalization must be deterministic. Before hashing an email:
- Trim leading and trailing whitespace.
- Lowercase the entire address.
- Hash with SHA-256.
- Render as lowercase hexadecimal — 64 characters.
# Node
crypto.createHash("sha256")
.update(email.trim().toLowerCase())
.digest("hex");
# Python
hashlib.sha256(email.strip().lower().encode()).hexdigest()
A frequent and subtle error: hashing an already-hashed value. If your CRM stores hashed emails, do not hash again. Another: uppercase hex output. The spec asks for lowercase, and a mismatch here means the identity simply never matches.
Automatic Advanced Matching (and what changes on August 17, 2026)
Everything above describes manual advanced matching: you locate the customer's email in your own application state, hash it, and pass it explicitly. Automatic Advanced Matching (AAM) is the version where the pixel does that collection for you — it reads customer information visitors enter into forms on your site, hashes it in the browser with SHA-256, and attaches the digests to the conversion event.
Two dates govern it. AAM is already the default for newly created Web pixels. And on August 17, 2026, OpenAI enables it on existing Web pixels too. This is an opt-out: if you installed your pixel earlier and never touched the setting, it switches on that day without any action from you.
To opt out, go to Tools → Conversions → Data Source → Edit pixel in Ads Manager and turn Automatic Advanced Matching off. The setting is per-pixel, so a multi-property account needs the change applied to each one. Do it before the 17th if you want an existing pixel left alone.
Three implementation notes. First, AAM is a Web pixel feature only — it does nothing for Conversions API events or the image-tag fallback, so a primarily server-side stack is less affected than the announcement suggests. Second, keep your manual user object: an explicitly-supplied email_sha256 from data you control is the highest-quality identity signal available, and AAM covers the surfaces nobody instrumented. Running both is the strongest configuration. Third, watch for double-hashing — if a form contains a hidden field that already holds a hashed value, AAM detecting and hashing it again produces a digest that matches nothing.
Because AAM changes how many conversions can be matched without changing how many conversions actually happen, expect a step change in reported volume at the changeover. Annotate the date in your reporting so nobody later reads it as a performance improvement. The full decision framework, consent review, and verification checklist are in our dedicated guide to Automatic Advanced Matching.
The image tag fallback
For environments where JavaScript cannot run, OpenAI provides an image-tag method that fires a 1×1 pixel on page load.
<img
src="https://bzr.openai.com/v1/sdk/events?pid=<PIXEL-ID>&event=page_viewed&data[type]=contents"
width="1" height="1" style="display:none" alt=""
/>
Required parameters are pid, event, and data[type]. Optional ones include event_id, oppref, custom_event_name, and event-specific data[<field>] values. A successful request returns 200 image/gif, which you can confirm in your browser's network panel.
The limitations are significant and worth stating plainly:
- Fires only on page load — it cannot measure clicks, form submissions, or any post-load interaction.
- One event per request, with no batching.
- Subject to URL-length constraints.
- Cannot include personal data or session identifiers.
- Does not automatically capture
oppref.
That last point is the practical killer. Without oppref, attribution back to the ad click is materially weaker. Treat the image tag as a genuine last resort — wrapped in <noscript> as a fallback beneath a working pixel, rather than as your primary method.
Consent, CSP, and privacy handling
Consent
The SDK exposes a consent command. The correct pattern for a consent-gated jurisdiction is to disable tracking, initialize, and enable only after the user grants permission:
oaiq("consent", false); // disable
oaiq("init", { pixelId: "<YOUR-PIXEL-ID>" });
oaiq("consent", true); // enable after consent
Server-side, the opt_out field on an event — set to true — excludes that event from future personalization while still allowing it to be counted. These are different controls for different purposes; do not substitute one for the other.
Content Security Policy
If you run a CSP, three directives need entries or the pixel fails silently:
| Directive | Source | Purpose |
|---|---|---|
script-src | https://bzrcdn.openai.com | Load the SDK |
connect-src | https://bzr.openai.com | Send events |
img-src | https://bzr.openai.com | Fallback delivery |
A missing connect-src entry is a classic cause of "the pixel is installed but no conversions appear" — the script loads fine, and every event is then blocked by the browser at send time. Check the console for CSP violations before assuming a configuration problem in Ads Manager.
Implementing through Google Tag Manager
Most teams will deploy this through a tag manager rather than editing templates. There is no official OpenAI template at time of writing, so you are building custom HTML tags.
- Base tag. Create a Custom HTML tag containing the loader snippet and
oaiq("init", …). Fire it on All Pages, with tag firing priority set high so it initializes before event tags. - Set "Once per page" in advanced settings to prevent double-initialization on sites with history-change triggers.
- Event tags. One Custom HTML tag per conversion event, each calling
oaiq("measure", …)with values pulled from data layer variables. - Pass the dedup ID. Push your order ID into the data layer at purchase and read it into
event_id. If it is absent, deduplication fails — so add a data layer check rather than sending an undefined value. - Convert currency to minor units in the variable, not in the tag. A custom JavaScript variable returning
Math.round(price * 100)keeps the conversion in one auditable place. - Consent Mode. If you use GTM consent mode, gate the tag on your analytics or marketing consent type and call
oaiq("consent", true)on consent update.
For the server-side leg, the Conversions API call belongs in your backend — the order-confirmation handler, or a queue consumer subscribed to order events — not in a GTM server container tag unless you have already standardized on server-side GTM. The reason is the 7-day timestamp window and all-or-nothing batching: those require retry logic and dead-lettering that a tag manager is a poor place to implement.
Platform-specific implementation notes
The snippets above are platform-agnostic. Here is where each major stack puts them, and the gotcha that most often bites on each.
Shopify
Add the base pixel to theme.liquid in the <head>, or as a Custom Pixel under Settings → Customer events if you want it sandboxed and consent-aware. Fire order_created from the order status page using the checkout object.
The gotcha: Shopify prices are decimal strings ("129.99") while OpenAI wants integer minor units (12999). Convert explicitly and round — floating-point multiplication on prices produces values like 12998.999999, which will be rejected or silently truncated. Also note that themes render the order status page outside your normal template chain, so a pixel in theme.liquid alone will not fire on purchase. Ecommerce sellers should also read our product feed guide, since feed campaigns are a separate program with separate eligibility.
WordPress and WooCommerce
Inject the base tag via wp_head in your child theme's functions.php — not the parent theme, which updates will overwrite. Hook order_created to woocommerce_thankyou, and prefer the server-side Conversions API for the purchase event, firing from the same hook. WooCommerce order objects give you the order ID for event_id and the total for amount.
The gotcha: caching plugins routinely strip or defer inline scripts. If conversions stop after a performance-plugin update, check whether the pixel is being deferred past the point where your event calls execute.
Webflow and other no-code builders
Base tag goes in Project Settings → Custom Code → Head Code. Event calls go in per-page custom code or on form-submit handlers. Because most no-code platforms give you no server, the Conversions API is not available without an intermediary — a serverless function or an automation platform receiving a form webhook and posting to bzr.openai.com.
The gotcha: native form success states often do not trigger a page load, so a page-load-based event will never fire. Bind to the form's success event explicitly.
HubSpot, Salesforce, and CRM-driven funnels
This is where server-side tracking stops being optional. The valuable event is not the form submission — it is the moment a lead becomes qualified, which happens days later inside the CRM, in response to human judgement the browser never observes.
The pattern: capture the __oppref cookie value in a hidden form field at submission, store it on the CRM record, and when the record reaches a qualifying stage, fire a Conversions API event using that stored value as obref. This is what lets you optimize toward qualified pipeline rather than raw form fills.
The gotcha: the 7-day timestamp window. If a lead qualifies on day 10, you cannot backdate the event to the original submission — the timestamp will be rejected. Send it with the qualification timestamp instead, and accept that the event's time reflects when the outcome was confirmed rather than when the click happened.
Capturing __oppref for later CRM use:
document.querySelector('[name="oai_ref"]').value =
document.cookie.match(/__oppref=([^;]+)/)?.[1] || '';
Sending conversions through a measurement partner
Building the Conversions API yourself is the highest-control option, but it is an engineering project. If your conversion data already lands somewhere central, a partner integration turns that project into a configuration task.
Hightouch now supports sending conversion events to ChatGPT Ads. It reads an event model from your warehouse and syncs it to the Conversions API; you authenticate with your Pixel ID and an API key, the sync type is Events in insert-only mode, and your model must have a truly unique primary key. Use that same key as your event_id so warehouse events deduplicate cleanly against browser events — if the two identifiers differ, every conversion counts twice.
Triple Whale can connect ChatGPT Ads so its performance reports alongside your other channels, and Sonar Optimize is available for ChatGPT Ads to push stronger conversion signals back to OpenAI. These solve different problems: Triple Whale is the cross-channel reporting layer, while Hightouch and Sonar Optimize improve what OpenAI actually receives.
Whatever route you choose, keep the pixel. The browser event carries oppref, which is the strongest attribution signal available; server-side routes add completeness for offline, delayed, and consent-blocked conversions. Deduplication is what lets you run both without inflating your numbers. Setup detail for each route is in our measurement partners guide.
View-through conversions: VTA (1d)
For eligible accounts, Ads Manager now reports one-day view-through conversions at campaign, ad group and ad level. This is the platform's first attempt to credit impressions that influenced a conversion without earning the click — and it is the single easiest number on the platform to misuse.
What actually counts as a view-through conversion
A view-through conversion is counted when someone converts within one day after seeing an eligible ad impression, and no qualifying ad click receives credit for the same conversion. If both an impression and a click qualify, the click gets credit. There is no double counting inside the metric itself — the double counting happens when a human adds the two columns together.
Where to find it
- In the attribution breakdown shown when you hover over the Conversions column.
- As an optional standalone column named VTA (1d). To add it, open Campaigns, Ad groups or Ads, select Edit columns, and choose VTA (1d).
- In CSV exports, when available for your account.
The four rules that keep it honest
| Rule | What it means in practice |
|---|---|
| It is not in your Conversions total | The main Conversions column includes click-through conversions only. View-through is separate supplemental reporting and should never be added to Conversions. |
| It does not touch performance math | View-through does not affect CPA, post-click conversion rate, bidding, billing or conversion optimization. Your oCPC campaigns do not optimize toward it. |
| The window is fixed at one day | You cannot configure the view-through window, and it is independent of your configured click-through window. A 7-day click window does not give you a 7-day view window. |
| No implementation changes needed | The same submitted conversion events support both click-through and view-through reporting. No Pixel or Conversions API changes, and no attribution-setting changes, are required. |
How to actually use it
Treat VTA (1d) as directional evidence about upper-funnel contribution, tracked as a trend line rather than a KPI. A view-through count that grows steadily while click-through conversions hold flat is a reasonable signal that impressions are doing work your click attribution cannot see. A view-through count quoted inside a CPA calculation is a scorecard being inflated — and if an agency does this in a monthly report, it is worth asking what else is being blended.
The attribution gap nobody warns you about
Even with flawless implementation, ChatGPT Ads will under-report. This is not a bug in your setup; it is a property of how people use a conversational assistant.
Early advertiser analysis indicates roughly 40% of ChatGPT-ad-attributable conversions happen in the immediate post-click session, leaving about 60% to arrive later through channels with no traceable link back (AdVenture Media's attribution analysis). The r/PPC thread quoted at the top of this guide captured the pattern precisely: users "see something in ChatGPT, come back later through Google, type the brand directly, switch devices, send links around, etc so it ends up looking like random direct traffic."
There is a further structural constraint: OpenAI provides aggregated performance data only. Advertisers see views and clicks; they do not see chats or user-level details. One r/PPC commenter drew the right operational conclusion from this — measure it "like an emerging intent channel, not a mature paid channel," using a dedicated landing page, strict UTMs, a CRM source field, hidden form fields, self-reported attribution, and downstream quality metrics. Their closing observation is the one to keep: "If it shows as direct, that doesn't mean it failed… it means the tracking path is leaky."
A measurement stack that survives the gap
- Layer 1 — Platform tracking. Pixel plus Conversions API, deduplicated. This is what feeds oCPC bidding and it must be right, because the optimizer trains on it.
- Layer 2 — UTMs and dedicated landing pages. Tag every ad URL. A distinct landing page per campaign makes ChatGPT traffic legible in your own analytics even when the referrer is lost. Our UTM builder enforces consistent naming.
- Layer 3 — Self-reported attribution. A "How did you hear about us?" field on your form captures the 60% that no pixel will ever see. On an emerging channel this is not a soft metric; it is frequently the most accurate one you have.
- Layer 4 — CRM outcome tracking. Booked-demo rate, SQL rate, deal size, close rate. Push qualified outcomes back through the Conversions API so bidding learns from revenue quality rather than raw lead count.
- Layer 5 — Holdout and lift testing. When click attribution is structurally incomplete, geo holdouts and branded-search lift are the honest way to size total contribution.
Pixel validation diagnostics: why events get dropped
Ads Manager exposes additional Pixel validation detail, and it is now the first place to look when your event counts are lower than they should be. Go to Tools → Conversions and open the diagnostics panel on the right-hand side.
For events that were dropped before being sent, it shows you:
- Why the event was dropped.
- The affected field, so you know where in the payload the fault is.
- The error type.
- A recommended fix.
This materially changes the debugging loop. The classic failure on this platform was silent — events appeared to fire, conversions never appeared, and you bisected your own implementation with no signal about which field was at fault. A named field plus an error type plus a suggested fix turns that into a targeted change.
Treat it as a scheduled check, not an incident response. Review the panel after any deploy that touches checkout, forms, or consent; after Automatic Advanced Matching turns on; and after any change to a partner sync's field mapping. A dropped-event rate that drifts upward over a fortnight is far cheaper to catch in a weekly review than in a quarterly post-mortem.
The QA checklist
Run this before launching a conversion campaign, and again after any deploy that touches checkout, forms, or consent.
- Pixel loads.
bzrcdn.openai.com/sdk/oaiq.min.jsreturns 200 in the network panel. window.oaiqis defined in the console on every page that should track.- Init fires once, not twice. Double initialization is common with SPA route-change triggers.
- Events reach the endpoint. Filter the network panel to
bzr.openai.comand confirm requests on a real test conversion. __opprefcookie is set after arriving via an ad click.- Amounts are in minor units. A $25.99 order sends
2599. Verify the actual payload, not the intent. - Currency accompanies every amount.
event_idis present and equals the server-sideidfor the same conversion.- Server payloads validate. Send with
validate_only: trueand confirm a clean response. - Hashes are lowercase 64-char hex and derived from trimmed, lowercased input.
- No raw PII anywhere in a payload — check email, phone, and any ID fields.
- CSP allows all three directives.
- Consent gating behaves correctly in both accept and reject paths.
- Counts reconcile. Ads Manager conversions are within a plausible margin of your own order or lead counts for the same window.
Failure modes that silently corrupt your data
Conversion tracking rarely fails loudly. These are the failures that produce plausible-looking but wrong numbers — the dangerous kind, because they inform bidding.
| Failure | Symptom | Fix |
|---|---|---|
Missing event_id | Conversions roughly double | Add shared ID to both paths |
| Amount in major units | Revenue 100× too high | Send integer minor units |
CSP missing connect-src | Pixel loads, zero events | Allow bzr.openai.com |
| Batch import older than 7 days | Silent event loss | Move to a daily job |
| One bad event in a batch | All 1,000 rejected | Validate per-event; smaller batches |
| Uppercase or double hashing | Match rates collapse | Lowercase hex, hash exactly once |
| Custom event as oCPC goal | Event not selectable | Fire a standard event alongside |
| Consent blocks everything | Conversions drop sharply after a banner change | Verify both consent paths; add server-side |
| Checkout redesign drops tag | Gradual unexplained decline | Weekly automated verification |
The last row deserves emphasis. OpenAI's optimization guidance says to "keep conversion tracking healthy," noting that "incomplete or misconfigured tracking can make reporting and optimization less effective." In practice, tracking decays. A template change, a consent-tool update, a new checkout flow — each can sever the pipe without a single error appearing in Ads Manager. Schedule verification; do not rely on noticing.
How AI search monitoring improves the whole strategy
Conversion tracking answers "did this click pay off?" It cannot answer the two questions that matter more in an AI-mediated market: which conversations is my brand present in at all, and what is being said about me when I am not paying? That is the job of an AI search monitoring platform, and connecting the two datasets is where the real strategic advantage sits.
1. It substitutes for the search-term report you do not get
ChatGPT Ads has no search-term report and no placement report — a limitation advertisers complain about persistently. Monitoring which prompts return your brand, and which return competitors, is the nearest available equivalent. Those prompts are direct evidence for writing better context hints, which are the only targeting lever the platform gives you.
2. It separates a visibility problem from a conversion problem
When a campaign underperforms, there are two distinct diagnoses with opposite remedies. If you are absent from the relevant conversations, that is a visibility and content problem. If you are present and people are not converting, that is a landing-page and offer problem. Conversion data alone cannot distinguish them; conversion data plus visibility data can.
3. It stops you paying for what you already earn
If ChatGPT already recommends you organically for a given class of question, aggressive bidding there partly buys traffic you would have received anyway. Reallocating that budget toward contexts where a competitor is named and you are not is one of the few unambiguous efficiency wins available in this channel.
4. It turns conversion evidence into an SEO and AEO roadmap
This is the compounding one. Your conversion data tells you which claims, offers, and audiences actually produce revenue. Your visibility data tells you which of those high-converting contexts you have no organic presence in. The intersection is a ranked content brief backed by paid proof of demand — a far stronger prioritization signal than keyword volume, and one built specifically for a world where the "search result" is a generated answer. The structural work that follows — clear comparison pages, well-marked-up entities, retrievable factual content — is what earns citations, and citations are what make the next ad click cheaper and more likely to convert.
The practical loop: monitor the prompts that matter to your category, run oCPC campaigns with clean tracking against them, read cost per conversion by context, then feed both the winners and the absences into your content roadmap. Paid buys presence today; the content earns it permanently. Our guides on appearing in ChatGPT answers, visibility metrics, and buyer-evaluation visibility cover that side of the discipline.
The strategic summary
Conversion tracking makes ChatGPT Ads accountable. AI search monitoring makes it strategic. One tells you what a click was worth; the other tells you which conversations you are missing entirely — and in a channel with no search-term report, that second dataset is not a luxury, it is the missing half of the reporting.
Done for you: Conversion Tracking Implementation
If the conversion column reads zero and nothing in the interface says why, that is usually measurement. Our conversion tracking implementation installs the pixel and Conversions API, matches events exactly, and proves it with a written QA matrix — from $3,999.
ChatGPT Ads conversion tracking: frequently asked questions
How do I set up ChatGPT Ads conversion tracking?
Provision a Pixel ID in Ads Manager's conversions tab, add the oaiq loader snippet to your site's <head>, call oaiq("init", { pixelId: … }), then fire events with oaiq("measure", "<event>", { … }). For anything transactional, add the server-side Conversions API and deduplicate with a shared event_id.
What is the ChatGPT Ads pixel called?
The SDK is oaiq, loaded from https://bzrcdn.openai.com/sdk/oaiq.min.js. Events are sent to https://bzr.openai.com. Both hosts need to be allowed in your Content Security Policy.
What is the ChatGPT Ads Conversions API endpoint?
POST https://bzr.openai.com/v1/events?pid=<PIXEL-ID> with a bearer token and JSON body. It accepts up to 1,000 events per batch, and the entire batch fails if a single event fails validation.
Do I need both the pixel and the Conversions API?
For anything with real revenue attached, yes. The browser pixel loses events to ad blockers and tracking prevention; the server API cannot see browser-only interactions. Running both with matched event_id and id values gives the most complete picture without double counting.
How does deduplication work between the pixel and the API?
Use the same value as the pixel's event_id and the API's id for the same conversion — an order or transaction ID is usually ideal. For custom events, custom_event_name must match on both sides as well.
What is oppref and the __oppref cookie?
oppref is OpenAI's privacy-preserving attribution identifier. The JavaScript pixel captures it from the landing page URL automatically and stores it in a first-party cookie named __oppref. Server-side calls must pass it explicitly as obref inside the user object.
How should I format amounts and currency?
Monetary values are integers in ISO 4217 minor units — $129.99 is 12999, not 129.99. Always include currency whenever you send an amount. Sending major units is the most common implementation bug and it fails silently.
Which fields must be hashed?
Hash email and external IDs with SHA-256 as lowercase 64-character hex, derived from trimmed and lowercased input. Send country, city, ZIP, IP address, and user agent as raw values. Never send raw emails, IDs, or phone numbers.
Can I import historical conversions?
Only recent ones. Event timestamps must fall within the last 7 days and no more than 10 minutes in the future. Weekly or monthly CRM batch imports will silently drop data — move to a daily job at minimum.
Why does ChatGPT Ads under-report my conversions?
Partly implementation, partly structure. Published analysis suggests only around 40% of ChatGPT-attributable conversions happen in the click session; the rest arrive later via direct or branded search. Pair platform tracking with UTMs, self-reported attribution, CRM outcomes, and holdout tests.
Can I use a custom event to optimize a conversion campaign?
No. Custom events can be measured but cannot be an oCPC optimization goal. If a custom event represents your real outcome, fire a standard event such as lead_created alongside it so the campaign has something eligible to optimize toward.
Does ChatGPT Ads support app install tracking?
The Conversions API defines app_installed and app_opened with action_source: "mobile_app". During the self-serve beta, however, Ads Manager data sources offered Web, iOS and Android with only Web selectable. Confirm current availability in your own account before planning app campaigns.
What is Automatic Advanced Matching and when does it turn on?
Automatic Advanced Matching (AAM) lets the Web pixel capture customer information from forms on your site, hash it in the browser, and send it with conversion events to improve match rates. It is already the default for new Web pixels, and OpenAI enables it on existing Web pixels on August 17, 2026. To opt out, go to Tools → Conversions → Data Source → Edit pixel before that date. See our full AAM guide.
Why are my pixel events being dropped?
Check the Pixel validation diagnostics panel in Ads Manager under Tools → Conversions, on the right-hand side. It reports why events were dropped before being sent, which field was affected, the error type, and a recommended fix — which is usually enough to identify the problem without bisecting your own implementation.
Can I send ChatGPT Ads conversions from my data warehouse?
Yes. Hightouch supports sending conversion events to ChatGPT Ads through its OpenAI destination, using your Pixel ID and an API key. The sync type is Events in insert-only mode and your event model needs a truly unique primary key — use that same value as your event_id so warehouse events deduplicate against browser events.
Does AAM replace the user object I already send?
No, and you should keep both. An explicitly-passed email_sha256 derived from data you control is the highest-quality identity signal available; AAM covers the form surfaces you never instrumented. They corroborate rather than conflict. Just watch for double-hashing on hidden fields that already contain a digest.
Are view-through conversions included in the ChatGPT Ads Conversions total?
No. The main Conversions column includes click-through conversions only. One-day view-through conversions are reported separately as VTA (1d) and are supplemental — they should not be added to Conversions or used for core performance metrics like CPA. They also do not affect post-click conversion rate, bidding, billing or conversion optimization.
Can I change the view-through attribution window in ChatGPT Ads?
No. At this time the view-through window is fixed at one day and is independent of your configured click-through window. A longer click-through window does not extend the view-through window.
Do I need to change my Pixel or Conversions API setup to get view-through conversions?
No. The same submitted conversion events support both click-through and view-through reporting, and no attribution-setting changes are required. View-through reporting appears automatically for eligible accounts — in the attribution breakdown when you hover over the Conversions column, and as an optional VTA (1d) column you can add through Edit columns.
How is a view-through conversion attributed if the user also clicked?
The click wins. A view-through conversion is only counted when someone converts within one day of an eligible ad impression and no qualifying ad click receives credit for the same conversion. Where both an impression and a click qualify for the same conversion, credit goes to the click.
What conversion matching fields did ChatGPT Ads add in September 2026?
The Measurement Pixel added hashed phone, first name, last name, region and postal code alongside the hashed email and external ID it already accepted. The Conversions API added plural list fields for hashed email, phone, external ID, first and last name, country, city, region and postal code — so one event can carry several values per field instead of a single best guess — plus Android GAID for mobile. More identity means more conversions match the ads that caused them, which feeds oCPC bidding quality. It also widens the consent surface: hashed data is still personal data, and names plus postal geography identify people more precisely than an email hash alone.
Should I send every available identity field?
Send what you legitimately hold and have consent to send — not everything you can technically populate. The plural list fields are the highest-value addition because real customers have several emails, phones and addresses, and sending the set instead of one guess strictly improves matching. But each field is customer data leaving your systems, and a wrongly-populated field fails silently rather than erroring. Add fields in tranches, normalize each one before hashing, and watch match quality move so you know which additions actually earned their place.
Sources and further reading
- OpenAI Developers — JavaScript pixel (the
oaiqsnippet, init options, consent, event shapes, CSP directives). - OpenAI Developers — Conversions API (endpoint, batch limits, event fields, user matching and hashing rules, deduplication).
- OpenAI Developers — Image tag (URL format, parameters, and its limitations versus the JavaScript pixel).
- OpenAI Developers — Supported events (the 13 standard events and their required data types).
- OpenAI Help Center — Conversion-optimized Campaigns (why tracking health drives bidding quality).
- PPC Land — ChatGPT ads conversion optimization (measurement infrastructure and rollout dates).
- AdVenture Media — Measuring ROI on ChatGPT ads (the ~40% in-session / 60% delayed attribution split).
- r/PPC — ChatGPT ads are here, but can we measure their performance? (May 26, 2026 — the direct-traffic attribution problem, UTM and CRM workarounds).
- r/PPC — ChatGPT/OpenAI advertising (April 2026 — early reporting limited to spend, impressions and clicks).
- Context Hints — ChatGPT Ads conversion campaigns, measuring ROI, and visibility metrics.
Want your conversion tracking audited before you scale spend?
30 minutes with Tarun. We will walk your pixel and Conversions API setup, check deduplication and hashing, and find the events that are silently failing before they distort your bidding.
Book a discovery call