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. Email and external IDs must be pre-hashed with SHA-256; geographic fields are sent raw:
oaiq("init", {
pixelId: "<YOUR-PIXEL-ID>",
user: {
email_sha256: "<SHA256_HASH>",
external_id_sha256: "<SHA256_HASH>",
country: "US",
city: "San Francisco",
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 }
]
}
}]
}
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? |
|---|---|---|
obref | First-party cookie value, unmodified | No |
email_sha256 | SHA-256, lowercase 64-char hex | Yes |
external_id_sha256 | SHA-256, lowercase 64-char hex | Yes |
country | ISO 3166-1 code, e.g. US | No |
city | Max 128 chars, lowercased, trimmed | No |
zip_code | Max 32 chars; letters, numbers, spaces, hyphens | No |
ip_address | IPv4 or IPv6 | No |
user_agent | Non-empty browser string | No |
The governing rule from OpenAI's documentation: send raw values for geographic, IP, and user-agent fields; hash email and external IDs; and never send raw emails, IDs, or phone numbers.
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.
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] || '';
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.
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.
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.
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