ChatGPT Ads Conversion Tracking: Pixel, Image Tag & Conversions API (2026)

Complete ChatGPT Ads conversion tracking setup: the oaiq JavaScript Pixel, the image tag fallback, the Conversions API, every standard event, hashed user matching, deduplication, consent and CSP — plus the attribution gap nobody warns you about.

Tarun Kapoor 26 min readPublished July 25, 2026
ChatGPT Ads Conversion Tracking: Pixel, Image Tag & Conversions API (2026)

Short answer: ChatGPT Ads conversion tracking runs through three paths that all report to a single Pixel ID — the oaiq JavaScript Pixel in the browser, a no-JavaScript image tag, and the server-side Conversions API — and OpenAI deduplicates across them using the Pixel ID, the event name and a shared event identifier. Getting it right is not optional if you plan to run conversion campaigns, because under oCPC broken tracking degrades ad delivery, not just your dashboard.

Who this is for. Anyone installing tracking for ChatGPT Ads for the first time, and anyone who already installed it and does not fully trust the number. Everything below is drawn from OpenAI's own developer documentation with the source linked at each claim. Where we give operating judgement rather than documented behaviour, it is labelled as ours.

What ChatGPT Ads conversion tracking actually is

Until spring 2026 this channel had no conversion measurement at all. Digiday reported the pixel in pilot on 16 April 2026, quoting Adthena CMO Ashley Fletcher describing the absence of conversion attribution as "currently the most significant limitation of ChatGPT ads" and the pixel as a "welcome development" for justifying spend (Digiday). Conversion-optimized campaigns followed in early June (MediaPost). So this is a young system, and treating it with the confidence you extend to a decade-old ads platform is a mistake.

Structurally, it works the way every modern conversion system works: a Pixel ID identifies your dataset, events describe what happened, an attribution identifier links the event back to an ad click, and deduplication stops the same thing being counted twice when you send it from more than one place. The Pixel ID and the server-side API key are both provisioned from the conversions tab in Ads Manager.

Architecture diagram showing the JavaScript pixel and image tag collecting in the browser and the Conversions API collecting on the server, all sending to OpenAI's ingest endpoint keyed by Pixel ID, deduplicated on Pixel ID plus event name plus event id, then feeding Ads Manager reporting and oCPC delivery.
Three collection paths, one Pixel ID, one deduplication key — and two downstream consumers, only one of which is your dashboard.

The piece people miss is the second consumer. Your conversion data does not only populate a report; it steers delivery in oCPC campaigns. A misconfigured event in a plain Clicks campaign gives you a wrong number. The same misconfiguration in a Conversions campaign gives you a wrong number and an auction confidently buying the wrong clicks with your money.

The three collection paths, compared

JavaScript PixelImage tagConversions API
Runs inThe browserThe browserYour server
Endpointbzrcdn.openai.com/sdk/oaiq.min.jsbzr.openai.com/v1/sdk/eventsbzr.openai.com/v1/events?pid=…
AuthPixel ID onlyPixel ID onlyBearer API key
Fires onAny interaction you codePage load onlyAnything your backend knows
Events per requestBatched automaticallyOneUp to 1,000
Captures oppref automaticallyYesNo — pass it explicitlyNo — pass it explicitly
Survives ad blockersNoPartiallyYes
Offline / phone / in-store eventsNoNoYes
App eventsNoNoYes — the only path
Setup effortLowLowestEngineering project

Our recommendation, and it is not subtle: run the pixel and the Conversions API together, with deduplication, from day one. Retrofitting a shared event identifier after a month of live data means a month of numbers you cannot reconcile against the month that follows. The pixel alone is the fastest path to a number; the pair is the fastest path to a number you can defend in a budget meeting. The image tag is a fallback for genuinely constrained environments, not a third leg of the stack.

Installing the JavaScript pixel

The loader goes in your <head>, and OpenAI is specific about placement: near the top, so it catches conversions that happen early in the page lifecycle.

<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>",
    debug: true // remove before production
  });
</script>

That stub is the standard command-queue pattern: oaiq exists synchronously and queues calls, so measure calls made before the SDK finishes downloading are not lost. Which means you do not need to defensively wrap your event calls in load checks — a habit that causes more missed conversions than it prevents.

Four things the SDK handles for you once initialised, per OpenAI's documentation: it captures the oppref privacy-preserving attribution identifier from the landing URL, stores it in an __oppref cookie so it survives subsequent navigation, attaches source_url to events, and batches closely-timed calls into fewer requests. The oppref capture is the important one — it is the thread connecting a conversion back to an ad click, and it is why sending users through a redirect chain that strips query parameters silently destroys attribution.

Tracking an event

The signature is oaiq("measure", eventName, eventData, options). The event data object always carries a type property matching the event's data shape.

// A page view with content context
oaiq("measure", "page_viewed", {
  type: "contents",
  contents: [{ id: "pricing", name: "Pricing page", content_type: "page" }]
});

// A purchase — note the deduplication id in the fourth argument
oaiq("measure", "order_created", {
  type: "contents",
  amount: 2599,
  currency: "USD",
  contents: [
    { id: "sku_123", name: "Starter", content_type: "product", quantity: 1 }
  ]
}, { event_id: "order_12345" });
amount: 2599 is $25.99, not $2,599. Monetary values are integers in ISO 4217 minor units — cents for USD. This is the single most common data-quality bug we find in new installations, and it is invisible in the interface: the conversion count looks right while the revenue is off by two orders of magnitude. If you have ever wondered why a channel appeared to deliver implausible ROAS in its first week, check this first.

Single-page applications

In an SPA the loader runs once, but route changes do not reload the page — so nothing fires unless you fire it. Call measure from your router's navigation hook rather than relying on page load, and be careful not to double-fire on the initial render, where both the loader and your route handler may attempt the same page_viewed. Our practice is to let the router own every page_viewed and never emit one from the init path, so there is exactly one code path responsible.

Where to install it: tag manager, platform app, or hardcoded

OpenAI documents the snippet but not where it should live in a real stack, and the choice has consequences that are awkward to reverse. Three options, with the trade-off that actually decides it.

Hardcoded into your template. The most reliable option and the one we default to. The loader sits in the server-rendered <head>, so it is present before any client-side framework boots, it cannot be disabled by someone editing a container, and it is impossible for a tag manager's own load failure to take it down. The cost is a deploy for every change, which for a snippet that changes twice a year is not a real cost.

Via a tag manager. Attractive because marketing owns the deployment, and genuinely useful if you need to fire events on interactions your developers will not instrument for you. The trade-offs are real though: you have added a dependency that itself gets blocked by the same privacy tooling that blocks the pixel, you have moved the loader later in the page lifecycle — which cuts directly against OpenAI's instruction to place it near the top of <head> to catch early conversions — and you have created a second place where the Pixel ID can be wrong. If you go this route, hardcode the loader and init, and use the tag manager only for event calls.

Through a platform app or theme integration. On hosted ecommerce, an app that injects the pixel and maps standard events for you removes most of the work. Verify two things before trusting it: that it sends amount in minor units, and that it exposes the event_id so you can deduplicate against a server-side integration later. An integration that silently owns the event identifier is an integration you cannot pair with the Conversions API, and you will not discover that until the month you try.

Whichever you pick, keep the checkout-confirmation event out of the tag manager. The purchase event is the one that decides budgets. It should be emitted by the same code path that creates the order, with the order ID as its event_id, so that the conversion and the revenue record are generated from a single source of truth. Everything upstream of purchase is fair game for a container.

Three identifiers do the connective work between an ad click and a conversion, and confusing them is the root of most attribution loss we are asked to debug. They are worth separating clearly.

IdentifierWhere it livesWhat it does
opprefAppended to your landing URL on an ad click; captured by the SDKOpenAI's privacy-preserving attribution identifier — the link back to the click
__oppref cookieWritten by the SDK in the user's browserPersists the captured oppref across subsequent navigation on your site
obrefThe unmodified browser cookie value, read server-sideBridges browser-collected identity into Conversions API events

The chain is: user clicks an ad → lands with oppref in the URL → the SDK captures it and stores it in the __oppref cookie → subsequent events carry it automatically → and if you are also sending server-side, your backend reads that cookie's unmodified value and sends it as obref on the Conversions API event. That last word is doing real work. Do not URL-decode it, re-encode it, trim it, or normalise its case. Pass through exactly what the browser holds.

Where the chain breaks, in rough order of how often we find it:

  • Redirects that drop query parameters. Link shorteners, affiliate wrappers, marketing-automation click trackers, and naive www/locale redirects will all happily discard oppref. Every hop between the ad click and your landing page must preserve the query string. Test the live destination URL, not the one in your campaign sheet.
  • Consent interstitials that navigate before the SDK runs. If your CMP bounces the user to a consent page and back without carrying parameters through, the identifier is gone before anything captured it.
  • A landing page that immediately client-side redirects to a canonical URL without the parameters — common on frameworks that normalise routes on mount.
  • Cookie policies that block the __oppref write, so the identifier survives the landing page and nothing after it. This looks like conversions attributed only when the purchase happens in a single uninterrupted session.
  • Server-side code that sanitises cookie values before forwarding them. A well-meaning input filter is a common culprit.

There is a useful diagnostic here: if your single-page-session conversions attribute fine and your multi-page ones do not, the cookie write is your problem. If nothing attributes at all, the parameter is being lost before the landing page ever loads. Those two findings point at different teams, which is why it is worth distinguishing them before raising a ticket.

The complete standard event reference

Every supported event, its data type, and where it can be sent from. Reproduced from OpenAI's supported events documentation.

EventData typeOptional fieldsPixelAPI
page_viewedcontentscontents, amount, currencyYesYes
contents_viewedcontentscontents, amount, currencyYesYes
items_addedcontentscontents, amount, currencyYesYes
checkout_startedcontentscontents, amount, currencyYesYes
order_createdcontentscontents, amount, currencyYesYes
lead_createdcustomer_actionamount, currencyYesYes
registration_completedcustomer_actionamount, currencyYesYes
appointment_scheduledcustomer_actionamount, currencyYesYes
subscription_createdplan_enrollmentplan_id, amount, currency, contentsYesYes
trial_startedplan_enrollmentplan_id, amount, currency, contentsYesYes
app_installedcustomer_actionamount, currencyNoYes
app_openedcustomer_actionamount, currencyNoYes
customcustomplan_id, amount, currency, contentsYesYes

Items inside a contents array support id, name, content_type, quantity and amount. Populate them properly on ecommerce events even though they are optional — they are the only product-level context the system receives, and a retailer already maintaining a product feed has these values to hand.

app_installed and app_opened are Conversions-API-only, must be sent with action_source: "mobile_app" and data.type: "customer_action", and OpenAI notes there is no native mobile SDK at present. App-install measurement on this channel is therefore a backend integration, not a tag deployment — budget for it accordingly.

Custom events and their limits

oaiq("measure", "custom",
  { type: "custom", amount: 5000, currency: "USD" },
  { custom_event_name: "quote_requested" }
);

Custom event names must be 1–64 characters, letters, numbers, underscores and dashes only, must start and end with a letter or number, and must not duplicate a standard event name. The critical constraint is not in the naming rules though: custom events cannot be used as oCPC optimisation targets. If your measurement plan lives entirely in custom events, you will need at least one standard event firing in parallel before a Conversions campaign is possible at all.

Sending user matching data

Match quality determines how many conversions get connected back to a click. You can attach user data at init time, and update it once you know more — OpenAI's guidance is to call init again with the complete object if data arrives after the initial call, such as after login.

oaiq("init", {
  pixelId: "<YOUR-PIXEL-ID>",
  user: {
    email_sha256: "…",        // SHA-256, lowercase 64-char hex
    external_id_sha256: "…",  // your stable customer ID, hashed
    country: "US",            // ISO 3166-1 alpha-2, sent raw
    city: "san francisco",     // max 128 chars, trimmed, lowercased
    zip_code: "94107"          // max 32 chars
  }
});

The hashing rules are strict and asymmetric, so read them carefully:

  • Hash: email and external ID. SHA-256, output as lowercase 64-character hex. Trim and lowercase the email before hashing — " Alice@Example.com " and "alice@example.com" must produce the same digest, and they only do if you normalise first.
  • Do not hash: country, city, zip_code, ip_address, user_agent. These are sent as raw values. Hashing them does not make you safer; it makes them useless.
  • Never send: raw personal data of any kind. Phone numbers are not a supported matching field — do not improvise one into external_id_sha256.
  • On the server only: ip_address and user_agent are Conversions API fields, taken from the originating client request, and obref carries the unmodified browser cookie value.
A normalisation bug is silent. If your backend hashes a trimmed-and-lowercased email while your frontend hashes the raw input, the two produce different digests, deduplication across paths degrades, and match rates quietly fall — with no error anywhere. Write the normalisation once, in one shared function, and unit-test it against a known fixture on both sides.

The image tag: when JavaScript is not an option

For server-rendered pages, email-adjacent contexts, or platforms where you cannot inject script, OpenAI supports a 1×1 image tag.

<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="" />
ParameterRequiredPurpose
pidYesYour Pixel ID
eventYesA supported event name, or custom
custom_event_nameOnly when event=customThe custom event name
data[type]YesThe event's required data type
data[<field>]NoEvent fields, e.g. data[amount], data[currency]
event_idNoDeduplication identifier — send it anyway
opprefNoAttribution identifier; not captured automatically here

A successful request returns 200 image/gif. The limitations are real and you should read them as disqualifying for most use cases: it fires only on page load, sends one event per request with no batching, is bounded by URL length so large contents arrays will not fit, does not capture oppref automatically, and must not carry personal data, secrets or session identifiers in its parameters.

That last point deserves emphasis because URLs leak — into referrer headers, server logs, analytics and browser history. The image tag is the one collection path where a careless engineer can trivially put customer data somewhere it will persist for years. Treat its query string as public.

The Conversions API: full request anatomy

The server-side path is where a serious measurement setup lives. Endpoint, method and authentication:

POST https://bzr.openai.com/v1/events?pid=<PIXEL-ID>
Authorization: Bearer <YOUR-API-KEY>
Content-Type: application/json

The payload has two top-level keys — an optional validate_only boolean and a required events array of up to 1,000 events. One critical behaviour: if any event in the batch fails, the full batch fails. Batching is a throughput optimisation, not a resilience one; a single malformed event takes 999 good ones down with it, so validate before you send and keep batch sizes modest while you are still stabilising an integration.

{
  "validate_only": false,
  "events": [
    {
      "id": "order_12345",
      "type": "order_created",
      "timestamp_ms": 1785000000000,
      "action_source": "web",
      "source_url": "https://example.com/checkout/success",
      "oppref": "<value captured from the landing URL>",
      "user": {
        "obref": "<unmodified __oppref cookie value>",
        "email_sha256": "…",
        "external_id_sha256": "…",
        "country": "US",
        "city": "san francisco",
        "zip_code": "94107",
        "ip_address": "203.0.113.10",
        "user_agent": "Mozilla/5.0 …"
      },
      "opt_out": false,
      "data": {
        "type": "contents",
        "amount": 2599,
        "currency": "USD",
        "contents": [
          { "id": "sku_123", "name": "Starter", "content_type": "product", "quantity": 1 }
        ]
      }
    }
  ]
}
FieldRequiredNotes
idYesUnique event identifier; used with type for deduplication
typeYesA standard event name, or custom
custom_event_nameWhen type is customSubject to the same 1–64 character naming rules
timestamp_msYesMilliseconds. Within the last 7 days, and no more than 10 minutes ahead
action_sourceConditionalweb, mobile_app, offline, physical_store, phone_call, email, other
source_urlWhen action_source is webThe page the conversion happened on
opprefNoOpenAI's privacy-preserving attribution identifier
userNoMatching fields — see the hashing rules above
opt_outNoOpt out of personalisation. Defaults to false
dataYesMust match the event's data shape
The seven-day window is an architectural constraint, not a footnote. timestamp_ms must fall within the last seven days. That means offline conversions imported on a monthly finance cycle are structurally unsendable — a B2B deal that closes 40 days after the click can never be attributed through this API. If offline conversion import matters to you, your ingestion job has to run at least weekly, and ideally daily. Design for it now rather than discovering it during a quarterly reconciliation.
Overhead editorial photograph of two parallel ropes on dark wood, the upper one severed at the centre and the lower one running unbroken, illustrating browser-side collection breaking where server-side collection holds.
The browser path breaks in ways you cannot see from inside the browser. The server path is the one that holds.

Rate limits and batching architecture

OpenAI's Advertiser API publishes limits of 600 requests per minute per endpoint and 1,200 requests per minute overall. For conversion events specifically, the 1,000-events-per-batch allowance means those limits are generous — a well-batched integration sending 50 events per request needs 20 requests to move a thousand conversions, which is nowhere near the ceiling. The pressure comes from the opposite direction: teams that fire one API call per order, synchronously, inside the checkout request.

Do not do that. Two failure modes follow immediately. First, you have put an external HTTP call on your checkout critical path, so an OpenAI latency spike becomes a checkout latency spike. Second, a single malformed event now fails a batch of one, and you have no retry queue, so that conversion is simply gone. Write conversion events to a durable queue at order creation and drain the queue on a short interval with a worker that batches, retries with backoff, and — because the batch is all-or-nothing — bisects a failing batch rather than dropping it.

Keep the batches modest while an integration is new. A hundred events per request is plenty, and when one of them is malformed you lose a hundred instead of a thousand while you are still learning which of your event shapes are wrong.

Deduplication: the single most important setting

If you run both the pixel and the API — and you should — deduplication is what stops every hybrid conversion being counted twice. OpenAI's rule: matching uses the Pixel ID + event name + event identifier, where the identifier is event_id on the pixel side and id on the API side (or custom_event_name for custom events). Both paths must report the same Pixel ID.

Diagram showing the same order sent by the browser pixel with event_id order_12345 and by the server with id order_12345, matched on Pixel ID plus event name plus event id, and recorded as one conversion rather than two.
One order, two senders, one shared identifier — recorded once. Omit the identifier and both copies count.

The implementation rule that makes this reliable: generate the identifier once, at the moment of the business event, in a place both paths can read. Your order ID is usually the right value — it already exists, it is already unique, and it is already available to the browser at the confirmation page and to the server at the point of fulfilment. What does not work is generating an identifier in the browser and hoping the backend can reconstruct it, or using a timestamp, or using anything derived from a session.

The consequence of getting it wrong is worse under oCPC than it looks in a report. A doubled conversion count halves your apparent cost per conversion, which makes a mediocre campaign look excellent, which invites you to scale it — while the auction, optimising toward an inflated signal, keeps buying more of whatever produced the phantom. You can lose a quarter's budget to a missing four-character parameter.

The pixel exposes a consent command. Note the default carefully: consent defaults to true, so a pixel installed without any consent wiring will track.

oaiq("consent", false);                        // block before the user decides
oaiq("init", { pixelId: "<YOUR-PIXEL-ID>" });   // safe to initialise
oaiq("consent", true);                         // release once consent is granted

For any jurisdiction with prior-consent requirements, the ordering above is the pattern: deny first, initialise, then release on the consent event from your CMP. When consent is false, the pixel does not send events. On the server side, the opt_out boolean on each event opts that event out of personalisation, defaulting to false — that is a distinct control from consent and should be wired to a distinct user preference, not the same one.

If you run a Content Security Policy, three directives need entries or the SDK will fail silently in exactly the browsers whose users care most about this:

script-src   https://bzrcdn.openai.com
connect-src  https://bzr.openai.com
img-src      https://bzr.openai.com

A CSP violation here produces no conversions and no obvious error in most monitoring — the events simply never leave. If your conversion count is zero rather than low, check CSP before you check anything else.

Testing before you trust a number

Both paths ship a validation mode, and it is worth being disciplined about using them. The cost of launching spend against unverified tracking is not a bad report — it is a week of optimisation against a fiction.

  1. Pixel: set debug: true in the init call. The SDK logs its activity to the browser console. Walk your entire funnel and watch each event fire, checking the event name, the type, the amount in minor units, and the presence of your event_id.
  2. Conversions API: send your real payload with "validate_only": true. It validates the batch without persisting anything, so you can iterate on schema errors before a single fake conversion enters your account.
  3. Image tag: confirm the response is 200 image/gif. Anything else means the request was rejected.
  4. End to end: confirm the events appear in the conversions tab of Ads Manager. Console logs prove the browser sent something; only the interface proves OpenAI accepted it.
  5. Deduplication: deliberately fire one test order through both paths with a shared identifier and confirm it appears once. This is the test everyone skips and the one that matters most.
  6. Then remove debug: true before production.
Add one more step nobody documents: reconcile a full day of platform conversions against your own order table before you scale. Not to make them match — they will not — but to record how far apart they are while the account is small and the discrepancy is diagnosable. That ratio is the most useful number you will own on this channel, and it is much harder to establish later.

The attribution gap: what this pixel cannot see

Every conversion tracking system undercounts. This one undercounts for reasons specific to conversational AI, and being honest about them is the difference between a defensible business case and a fragile one.

Overhead photograph of five staggered translucent vellum sheets on dark stone, each successive sheet falling further into shadow, illustrating conversion influence fading beyond the measurable window.
Influence does not stop where measurement does. The further from the click, the less the pixel can see — and none of it is zero.

1. Browser-side collection is structurally degraded

Digiday's reporting names this plainly: JavaScript pixel reliability has been degraded industry-wide by privacy protections and ad blockers. Nothing about OpenAI's implementation exempts it. This is the gap the Conversions API exists to close, and the strongest single argument for the server-side integration.

2. Conversational journeys are not linear

This is the one that has no technical fix. A user asks ChatGPT about a category, reads an assistant answer, sees your ad, absorbs what you offer, compares alternatives across several turns — and then converts three days later via a branded search or by typing your URL directly. The influence was real and the ad caused it. No click chain records it. Digiday's coverage flags exactly this risk: non-linear journeys in conversational AI may undercount ChatGPT's contribution.

3. The seven-day event window

The Conversions API rejects events whose timestamp_ms falls outside the last seven days. Any genuine consideration cycle longer than a week cannot be reported through this path at all. For most B2B and considered-purchase categories, that is the majority of revenue.

4. The surrounding measurement ecosystem does not exist yet

Mature channels are measured with media mix modelling, geo holdouts and incrementality tests layered on top of platform reporting. Digiday notes ChatGPT lacks that established attribution ecosystem. There is no third-party consensus yet on what a ChatGPT-attributed conversion is worth relative to a Google-attributed one — which means anyone quoting you a precise multiplier is guessing.

How to talk about this internally. Present ChatGPT Ads conversions as a floor, never as the total. "This channel produced at least N conversions at a measured cost of $X, and the structure of the medium means the true figure is higher by an amount we are working to bound." That sentence survives scrutiny. "ChatGPT delivered N conversions" does not, and it collapses the first time someone asks why the platform number and the analytics number disagree.

Building a defensible measurement stack around it

Platform conversion tracking is one input, not the answer. Four layers around it, in the order we would build them:

  1. Rigid UTM discipline on every ChatGPT Ads destination URL. This is what lets your own analytics identify the traffic independently of OpenAI's reporting — the only way to compare two systems is to have two systems. Our UTM builder enforces a consistent scheme; the specific convention matters far less than never deviating from it.
  2. Server-side collection as the source of truth. Where the pixel and the API disagree, the API is closer to right. Build your internal reporting off your own order data joined on the identifier you already generate for deduplication.
  3. A branded-demand baseline. Record branded search volume and direct traffic before you scale ChatGPT Ads. When a chunk of the influence lands as branded search, a movement against a recorded baseline is the only evidence you will have — and you cannot construct the baseline retroactively.
  4. A holdout, once you are spending enough to afford one. Geo holdouts are crude, but on a channel with no incrementality tooling they are the only causal evidence available. Given ads serve in the US, Canada, Australia and New Zealand, a market-level holdout is at least structurally possible.

If you are also running Google Ads, our guide on connecting ad platforms covers how to keep the two data sets legible side by side rather than accidentally double-counting the same conversion in two dashboards.

How AI search monitoring improves the strategy around this

Conversion tracking measures the paid path. An AI search monitoring platform — the tooling category that tracks how a brand is surfaced and cited across ChatGPT, Google's AI Overviews, Perplexity and Gemini — measures the demand environment those ads run inside. Since the two answer different questions, running one without the other leaves a predictable blind spot. Four specific ways the monitoring layer improves an SEO and paid strategy on this channel:

  • It maps the prompts your category is actually answered on. Assistants answer questions, not keywords. Knowing the real prompt patterns tells you which conversations exist to be advertised beside, and gives you far better raw material for context hints than a keyword tool built for a search box ever will.
  • It quantifies citation share, which is the organic counterpart to impression share. If competitors are cited in the assistant answers your category generates and you are not, you are paying for clicks into a conversation where someone else supplied the trusted context. That is a content problem, and no bid fixes it.
  • It catches stale or wrong claims about you at the source. If the assistant describes a discontinued plan or an old price, your paid clicks land pre-poisoned. This shows up in Ads Manager as a conversion-rate problem with no visible cause — monitoring is what makes the cause visible.
  • It provides the branded-demand signal that closes the attribution gap from the other side. Because a meaningful share of ChatGPT-influenced conversions arrive later through branded search and direct, tracking movement in AI visibility and branded demand is one of the few observable traces of the influence the conversion pixel structurally cannot record.

The operating principle we work to: conversion tracking tells you whether the click paid for itself; AI search monitoring tells you whether you are in the conversation at all. Optimising the first while ignoring the second produces a locally efficient campaign inside a category you are steadily losing — which is the most expensive kind of good report.

Wiring tracking into conversion campaigns

Once tracking is verified, it becomes the input to oCPC campaigns. Three connections are worth being explicit about.

Your event choice is constrained by what you instrumented. An oCPC campaign optimises toward exactly one standard conversion event, chosen at creation and permanent thereafter. If you only ever instrumented page_viewed, that is your only option — and it is a terrible one, because it optimises toward people who load a page rather than people who buy. Instrument the depth of your funnel before you need the choice, not after.

Tracking health is delivery health. OpenAI's own optimisation guidance for conversion campaigns is to "keep conversion tracking healthy" because "incomplete or misconfigured tracking can make reporting and optimization less effective." Under oCPC the system is steering on your signal. Corrupt the signal and you have not just lost visibility — you have actively misdirected the auction.

Signal density decides which event you can use. Because the optimiser needs examples, the deepest event in your funnel is often not the usable one. That trade-off, with a full ladder of the standard events ranked by volume against meaning, is worked through in the conversion campaigns guide.

Troubleshooting: the failure modes we actually see

SymptomLikely causeFix
Zero conversions, everCSP blocking the SDK, or the Pixel ID is wrongCheck script-src/connect-src/img-src entries and re-copy the Pixel ID from Ads Manager
Conversions roughly double the truthPixel and API sending without a shared identifierSet event_id on the pixel and the matching id on the API, from the same source value
Revenue off by 100×Currency sent in major unitsAmounts are integers in ISO 4217 minor units — 2599 is $25.99
Events fire in console but never appearBatch rejected, or consent is falseSend with validate_only: true to surface schema errors; confirm the consent state
Whole batch rejectedOne malformed eventA failing event fails the full batch — validate first and reduce batch size while stabilising
Older conversions silently missingtimestamp_ms outside the 7-day windowRun the import job daily; anything past 7 days is unsendable
Match rate falling over timeInconsistent email normalisation between client and serverTrim, lowercase, then SHA-256 — one shared function, unit-tested
Attribution lost after redirectoppref stripped from the landing URLPreserve query parameters through every redirect hop and any consent interstitial
SPA reports one page view per sessionLoader fires once; routes do notEmit page_viewed from the router hook, and only from there
Conversions but no oCPC optionCustom events only, or conversion bidding not enabledAdd a standard event; ask your OpenAI representative about account enablement

Sources and further reading

Field names, endpoints, limits and constraints above come from OpenAI's documentation. Implementation practices, the troubleshooting table and the measurement-stack recommendations are our own operating judgement from running this channel, and are labelled as such in the text.

Next step. With tracking verified, read ChatGPT Ads Conversion Campaigns for the oCPC mechanics — event selection, Bid Cap derivation, the seven-day budget envelope and a 30-day launch plan. If you would rather have the whole stack installed, validated and run for you, book a call.
People Also Ask

Quick answers.

What is ChatGPT Ads conversion tracking?

ChatGPT Ads conversion tracking is OpenAI's system for recording what happens on your site or server after someone clicks a ChatGPT ad. It has three collection paths that all report to the same Pixel ID: the oaiq JavaScript Pixel loaded in the browser, a no-JavaScript image tag, and the server-side Conversions API. Conversion tracking is also the prerequisite for running conversion-optimized (oCPC) campaigns.

How do I install the ChatGPT Ads pixel?

Add the oaiq loader snippet near the top of your site's <head>, pointing at https://bzrcdn.openai.com/sdk/oaiq.min.js, then call oaiq("init", { pixelId: "<YOUR-PIXEL-ID>" }). The Pixel ID is created in the conversions tab of Ads Manager. Track events with oaiq("measure", eventName, eventData, options). Set debug: true during setup to log SDK activity to the browser console.

What is the difference between the ChatGPT Ads pixel and the Conversions API?

The pixel runs in the user's browser and captures interaction-driven events automatically, including the oppref attribution identifier from the landing URL. The Conversions API runs on your server, accepts up to 1,000 events per batch, and can send offline, in-store, phone, email and mobile app events the browser never sees. The pixel is easier; the API is more reliable because ad blockers and privacy protections cannot intercept it. Most serious advertisers run both and deduplicate.

How does ChatGPT Ads deduplicate conversions between the pixel and the API?

Matching uses three things together: the Pixel ID, the event name, and the event identifier — event_id on the pixel side, id on the Conversions API side (or custom_event_name for custom events). Send the same order under the same identifier from both paths and it is recorded once. Omit the shared identifier and every hybrid conversion is counted twice.

Which conversion events does ChatGPT Ads support?

The standard events are page_viewed, contents_viewed, items_added, checkout_started, order_created, lead_created, registration_completed, appointment_scheduled, subscription_created, trial_started, plus app_installed and app_opened which are Conversions-API-only. There is also a custom event type. Each event carries a data type of contents, customer_action, plan_enrollment or custom.

Do I need to hash data before sending it to ChatGPT Ads?

Email addresses and external customer IDs must be hashed with SHA-256 into lowercase 64-character hex, after trimming and lowercasing the email. Country, city, zip code, IP address and user agent are sent as raw values. Never send raw personal data, and phone numbers are not a supported matching field.

How do I test ChatGPT Ads conversion tracking before trusting the numbers?

On the pixel, set debug: true in the init call and watch the browser console as you walk the funnel. On the Conversions API, send your payload with validate_only set to true — it validates without persisting anything. Then confirm the events appear in the conversions tab of Ads Manager before you launch spend against them.

Why are ChatGPT Ads conversions lower than my analytics suggests?

Several structural reasons compound. Browser-side pixels are degraded by ad blockers and privacy protections. Conversational journeys are non-linear — users often absorb information in the chat, then convert later via branded search or direct, with no traceable link back. And the Conversions API only accepts events with a timestamp inside the last seven days, so genuinely long consideration cycles fall outside the window entirely.

Tarun Kapoor
About the author

Tarun Kapoor

Founder · GPT Ads AI

Performance marketer with 12+ years in paid acquisition. Former senior media buyer at Neil Patel Digital. Alumni of GroupM, WPP, Ogilvy & Mather, and Toptal's Growth Collective. Fractional CMO to Fortune 500 brands and venture-backed startups.

Book a call with Tarun

Want this run
for you?

Book a discovery call