ChatGPT Ads conversion tracking, installed and actually proved

Tarun Kapoor, founder of Context Hints, seated at a wooden desk with a soft city light behind him.Tarun Kapoor Updated 1 September 2026 Full reference · 30,000 words

Every way ChatGPT Ads conversion tracking can break is silent. A missing content-security-policy directive, a conversion event whose name does not exactly match, a redirect that strips the click reference — each of them produces the same symptom, which is a column of zeros and no error message anywhere in the interface. Worse, corrections do not backfill: the history lost to a mismatched setup is gone permanently. This page is the whole install, the whole verification, and an honest account of what still cannot be measured afterwards.

The short version

  • What it is: the OpenAI Pixel and Conversions API installed, wired to the right conversion events, deduplicated, and proved — by watching the network, not by reading your tag manager and assuming.
  • Why it needs doing properly: every failure mode on this platform is silent. The conversion column reads zero, no error appears anywhere, and corrections do not backfill the history you lost.
  • What it costs: from $3,999, fixed before we start, with a written QA matrix and a documented handover.
  • What it will not do: make this channel fully measurable. Roughly 60% of attributable conversions arrive by paths no pixel can see. We say what the ceiling is before you buy, not after.
  • The honest part: the whole install sequence is published on this page. A competent developer can follow it, and many should.

What is a ChatGPT Ads conversion tracking implementation?

A ChatGPT Ads conversion tracking implementation is a one-off engineering engagement that builds and verifies the complete signal path from your website, app or CRM into OpenAI Ads Manager — the JS pixel, the Conversions API, the three Content Security Policy directives the pixel depends on, the survival of the oppref click identifier through your redirect and consent layers, the mapping of every event to the campaign configured to count it under Tools → Conversions, the deduplication key shared between browser and server, identity hashing, and the four URL macros that carry campaign detail into your own analytics. The deliverable is a working measurement system with each hop proved by a traced test conversion — not a diagnosis, not a document, not a list of recommendations for someone else to build. Ours is from $3,999 as a one-off engagement. It is narrower than an account build and shorter than a retainer on purpose: nobody writes a context hint, sets a bid or launches a campaign inside this scope.

That definition names eight components rather than saying “we set up your tracking” because seven of the eight can be complete and correct while the eighth returns a permanent zero, and Ads Manager will not tell you which one. Conversion tracking here is not a tag you paste; it is a chain in which every link fails the same way, by reporting nothing and raising nothing. The mechanics of each link are documented in our guide to ChatGPT Ads conversion tracking, which is free and which most readers should work through before paying anyone.

What does the engagement actually install and verify?

The work runs in a fixed order, because a later step cannot be verified until an earlier one is proved, and each item ends in evidence rather than a checkbox: a request observed leaving the browser, a payload accepted by the API, a cookie present after a real redirect chain, a test conversion landing against the right campaign.

  1. Pixel installation. The SDK at https://bzrcdn.openai.com/sdk/oaiq.min.js, the oaiq("init", { pixelId: "<ID>" }) call, and a debug: true pass confirming in the console that events are constructed as intended.
  2. Content Security Policy. All three directives in your response headers: script-src https://bzrcdn.openai.com, connect-src https://bzr.openai.com, img-src https://bzr.openai.com.
  3. Click identifier survival. A real click walked through your full redirect chain, CDN rules and consent banner, ending in a check that oppref arrived and was written to the first-party __oppref cookie.
  4. Event mapping. Each outcome mapped to one of the 13 standard events across their four data shapes, or to a correctly declared custom event, then matched against the event configured on the campaign under Tools → Conversions.
  5. Conversions API. Server events to POST https://bzr.openai.com/v1/events?pid=<PIXEL-ID> with Bearer auth, rehearsed with validate_only: true, batched under the 1,000-event ceiling, with obref passed explicitly inside the user object because nothing captures it for you server-side.
  6. Deduplication. One identifier shared as the pixel’s event_id and the API’s id — normally the order ID — tested against real orders to prove it is unique per event and identical on both sides.
  7. Identity and hashing. Email and external IDs normalized then hashed to lowercase 64-character SHA-256 hex; country, city, zip, IP and user-agent sent raw; automatic advanced matching audited on every pixel, since it is per-pixel and opt-out.
  8. Consent, macros and reconciliation. The oaiq("consent", false) → init → oaiq("consent", true) sequence wired to your consent tool, the four URL macros {campaign_id}, {ad_group_id}, {ad_id} and {ad_account_id} set at the right level, and a documented reconciliation of clicks against analytics sessions on a matched date range and time zone.

Is this the same as a ChatGPT Ads account build?

No — an account build creates the thing that spends money, and a tracking implementation creates the thing that tells you what the money did. Account setup and launch covers Persona verification, the account name and logo that ads will not serve without, billing, objective selection, ad group architecture, context hints, bids and creative. This engagement touches none of it. The two are frequently bought together and are still two engagements, because one is a media buyer’s job and the other is an engineer’s.

Is this the same as ongoing management?

No — this engagement ends when the measurement is proved, and management never ends. White-Glove ChatGPT Ads is $1,499/month with Tarun personally operating the account, covering up to $10,000 in managed monthly ad spend, and it answers “who changes the bids on Monday?” A tracking implementation answers “is the number on Monday true?” Plenty of in-house teams should buy the second without the first: they can run their own account perfectly well, they just do not want to spend three weeks of engineering time learning one platform’s measurement quirks.

EngagementWhat it ends withWhat it explicitly does not includeBuy it when
Conversion Tracking Implementation
from $3,999, one-off
A verified signal path, with test conversions traced end to end and a written record of what was provedCampaign strategy, context hints, bids, creative, and any ongoing operation of the accountYou already know the measurement is absent or wrong, and you want it built rather than described
ChatGPT Ads Audit
from $3,999, one-off
A written findings document, every issue ranked by cost and effort, and a remediation planAny change made on your behalf. It is a diagnosis, not a repairThe account spends, the numbers are disappointing, and you cannot tell which of eleven things is wrong
Account Setup & Launch
from $3,999, one-off
A verified, funded, structured account with campaigns live and servingOngoing optimization after launch, and deep custom measurement engineering beyond the standard installYou have no account, or an account that never got past verification
White-Glove Management
$1,499/month
Nothing — it does not end. Weekly operation of a live account, month to month, 14 days’ noticeA one-off engineering sprint. Deep implementation work is scoped separatelyYou want the account run, not reported on

Should you just do this yourself?

If your team has shipped a server-side conversions integration for another platform, your site has no consent wall and no redirect chain in front of the landing pages, and your outcomes map cleanly onto the standard events, you should do this yourself and you will probably do it in two days. That is a real answer, not a rhetorical one: the whole implementation is documented publicly, our conversion tracking guide walks the path, and $3,999 is a poor use of money for a problem you can solve on a Wednesday afternoon.

The engagement earns its fee where the environment is hostile rather than where the platform is hard. A consent management platform that rewrites URLs. A CDN allowlist that drops any query parameter it does not recognize. A checkout on a separate domain. A warehouse that owns the truth about revenue while the browser owns the truth about the click. Subscription revenue that arrives days after the event you can actually fire. Four properties on four pixels, each with its own advanced-matching state. In those accounts the difficulty is never the SDK — it is that eight systems have to agree on one identifier, and the platform gives you no error message when they do not.

Two filaments of blue light arriving from the left along separate arcs and converging cleanly into a single translucent blue glass node on a white field, with one brighter filament continuing beyond it.
Two paths, one record. The browser pixel and the Conversions API travel separately and have to arrive at the same conversion carrying the same key, or the account either counts it twice or drops it without comment. The filament continuing past the node is the part this engagement deliberately does not cover: building the campaigns and running them week to week are separate purchases, and a perfect install does not buy a single click.

Why ChatGPT Ads tracking fails silently

ChatGPT Ads conversion tracking fails silently because every layer that can break reports its breakage as a zero in the Conversions column rather than as an error anywhere in Ads Manager. There is no red banner, no warning triangle, no delivery diagnostic that says the pixel is unreachable. An account whose measurement broke on day one and an account with no demand at all produce the identical readout, and the difference between them is usually three months of budget and a decision that the channel does not work. Seven failure modes account for most of it, and all seven are recoverable in an afternoon by someone who knows where to look.

Why does a correctly installed pixel transmit nothing?

A pixel that is present in the page source and initializes without complaint will still transmit nothing if your Content Security Policy is missing connect-src https://bzr.openai.com. The three directives do three different jobs: script-src https://bzrcdn.openai.com permits the SDK to load, img-src https://bzr.openai.com permits the image path, and connect-src https://bzr.openai.com permits the actual event transmission. A site that has whitelisted the CDN but not the collection endpoint therefore passes every check a marketer knows how to run. The script tag is there. The oaiq global exists. View source looks perfect. Nothing leaves the browser.

It is also the fastest of the seven to confirm, because the browser does record it — just nowhere OpenAI can see. Open DevTools on the page that fires the conversion, complete the action, and watch the Network and Console panes: the request to bzr.openai.com appears as a Content Security Policy violation and never as an outbound request. Confirm the fix in your response headers rather than in Ads Manager, which can only report on events that reached it. Open the Tools → Conversions pixel validation diagnostics panel in the same session — it reports why events were dropped, the affected field, the error type and a recommended fix — because in most accounts nobody ever has.

Why does an event that fires correctly still report zero?

An event can fire, be accepted and be forwarded and still show zero in the Conversions column if it does not match the event configured for the campaign. This is a naming problem, not a plumbing problem, and it is the reason a team can spend two weeks proving the pixel works and still have nothing to show. For standard events, the event type you send must match the event type configured on the campaign. For custom events, Ads Manager must be set to Event type: Custom and the name must match exactly — a matching display name is not enough, and this is where most of these faults live, because the display name is the field a human reads and assumes is the field that matters.

The consequence is worse than the fault. Correcting the configuration does not backfill. A Conversions campaign that has been optimizing against a mismatched event for six weeks does not recover those six weeks when someone fixes the name; it starts learning from the moment of the fix. Every week the mismatch survives is a week of history permanently gone and a week of oCPC training on nothing. There are 13 standard events across four data shapes — order_created, checkout_started, items_added, contents_viewed and page_viewed on the contents shape; lead_created, registration_completed, appointment_scheduled, app_installed and app_opened on customer_action; trial_started and subscription_created on plan_enrollment; plus custom — and mapping your real outcomes onto that list is a decision worth ten minutes of argument before anyone writes code. Custom names are 1–64 characters, alphanumeric plus underscore and dash, starting and ending alphanumeric.

What happens when oppref is stripped at the edge?

Stripping oppref anywhere between the ad click and your landing page breaks conversion tracking entirely, and the three usual culprits are a redirect, a consent wall and a CDN rule. oppref is OpenAI’s privacy-preserving click identifier: it is appended to the landing-page URL, the pixel captures it automatically and stores it in the first-party __oppref cookie, and it is how OpenAI credits a conversion to a specific ad interaction. Remove it and every conversion you record becomes an unattributed one. The pixel keeps working. The events keep arriving. The campaign keeps reading zero.

The strip happens in exactly the places nobody tests. A vanity redirect through a link shortener drops the query string. A CDN rule that allowlists known marketing parameters for cache-key normalization discards an identifier it has never heard of. A consent tool that blocks the tag until acceptance and then reloads the page loses it on the reload. A checkout that hops to a payment domain and back arrives clean. Server-side the problem differs but ends the same way: the Conversions API does not capture oppref for you — you pass it yourself as obref inside the user object, which means your application must have persisted it from the landing session. Confirm the whole path by clicking a live ad, letting every redirect complete, then reading document.cookie for __oppref. Present on the first URL and gone by the last means the strip is inside your own infrastructure, and no change in Ads Manager will fix it.

Why do the pixel and the Conversions API disagree?

Running the pixel and the Conversions API together requires one shared deduplication key, and getting the key wrong produces either double-counting or silent loss depending on which way it is wrong. The rule is narrow: use the same identifier as the pixel’s event_id and the API’s id, and for custom events make custom_event_name match on both sides too. The order or transaction ID is the right choice. Timestamps, random values and session IDs do not work — a random value is different on each side by definition, and a timestamp generated independently in a browser and on a server will never agree to the millisecond.

The two failure directions look nothing alike. If no shared key exists, the same purchase arrives twice and is counted twice, which inflates conversions, deflates the CPA you derive as spend ÷ conversions, and teaches oCPC that a mediocre ad group is your best one. If the key exists but is not unique per event — a customer ID, cart ID or subscription ID reused across repeat purchases — genuinely distinct conversions collapse into one and the account under-reports for reasons no diagnostic surfaces. Two more traps sit next to it. timestamp_ms must be within the last seven days and no more than ten minutes in the future, so a first-sync backfill of historical orders is rejected rather than imported. And a batch carries up to 1,000 events, where the entire batch fails if one event fails, so one malformed row can cost you a day. If you are routing events through Hightouch, Triple Whale, Sonar Optimize or WorkMagic rather than writing the integration yourself, the same key discipline applies one layer up — the warehouse model needs a truly unique primary key to reuse as event_id — and our guide to the ChatGPT Ads measurement partners lists the failure modes specific to each.

Why does hashing break the identity match?

Two hashing errors account for most identity failures: double-hashing a value that was already hashed, and emitting uppercase hex. The contract is exact. Hash email addresses and external IDs with SHA-256 and send them as lowercase 64-character hex, normalizing before hashing — email lowercased and trimmed, phone numbers in E.164. Send country, city, zip, IP and user-agent raw. Never send raw emails, IDs or phone numbers under any circumstance. Neither error produces a rejection; both produce a hash that simply never matches anything, which is indistinguishable from a customer OpenAI has no record of.

Automatic Advanced Matching makes this more likely rather than less, because it is on by default and most teams do not know it is running. It is already the default for new Web pixels, and OpenAI enabled it on existing Web pixels on 17 August 2026. It is opt-out, so no action means on. It hashes client-side in the browser, it is set per pixel so a multi-property account has to be checked one pixel at a time, and it does nothing for Conversions API events or the image tag. The path is Tools → Conversions → Data Source → Edit pixel. Running manual advanced matching alongside it is the strongest configuration and also the one where a value gets hashed twice, once by your code and once by something nobody knew was enabled. Expect a step change in reported volume at the changeover and annotate the date before comparing a July conversion count to a September one; our advanced matching guide covers the before-and-after problem in full.

Is it broken, or has it just not landed yet?

Allow 24 to 48 hours for attributed conversions to appear in Ads Manager. Day-one reporting is noise. This single fact causes errors in both directions, and the second is the expensive one. In the first, a working implementation gets torn apart the afternoon it ships because the column still reads zero at 6pm — an engineer re-installs a pixel that was fine, changes three things at once, and now genuinely has a problem. In the second, a broken implementation is explained away for a fortnight with “it just hasn’t landed yet”, which is the same sentence on day one and on day fourteen and is only true on day one.

The discipline that ends the argument is a test conversion with a known identity. Fire one real conversion with an order ID you have written down, at a time you have written down, through a real ad click rather than a direct visit. Then stop looking for 48 hours. If it has not appeared against the right campaign by then, the pipe is broken and further waiting changes nothing. If it has, every subsequent zero is a demand or delivery problem rather than a measurement one. The same rule governs trend reading: discard the trailing 48 hours of any trend line, and never report yesterday’s conversion performance, because it has not finished arriving.

Why can a Conversions campaign not optimize toward your real outcome?

custom is not eligible as an oCPC goal, so a business whose actual outcome is a custom event cannot point conversion bidding at it — and the interface expresses this by simply not offering the event, which reads as a bug rather than a rule. A qualified-demo-booked, a credit-approved, a quote-accepted: if you modelled it as a custom event because none of the 13 standard events described it well, you have measurement without optimization. The workaround is deliberate redundancy: fire a standard event alongside the custom one, choose the standard event whose definition is least dishonest for your funnel, and point oCPC at that while you report on the custom one.

Three other gates sit alongside it, and all four must be open before oCPC will run. Conversion tracking must be live through the pixel, the Conversions API or both, and the Conversions API is more reliable than the pixel alone. At least one standard event must exist. The event must belong to the current ad account with an active conversion source — events do not travel between accounts, so an agency that built the pixel inside its own account has built you nothing. And the account must be enabled for conversion bidding, or the API returns 403 Conversion bidding is not enabled. One thing to settle before planning the work: the objective is locked permanently at campaign creation, and an existing Clicks campaign cannot be converted into a Conversions campaign. Cloning a CPC campaign into a new oCPC campaign has been possible since August 2026 and prefills the conversion event when exactly one eligible event is available, but it relaxes none of the four gates. The campaign-side mechanics are in our guide to ChatGPT Ads conversion campaigns.

SymptomWhat it usually isHow to confirm it in ten minutes
Pixel visible in page source, Conversions column has never left zeroMissing connect-src https://bzr.openai.com in your Content Security PolicyDevTools on the conversion page: complete the action and look for a blocked request to bzr.openai.com logged as a CSP violation. Then read your response headers for all three directives.
Events fire and are accepted, conversion column still zeroEvent-name mismatch between what you send and what the campaign countsTools → Conversions: read the exact event type and name on the campaign’s conversion event and compare it character by character with the string in your oaiq("measure", …) call. For custom events confirm Event type: Custom.
Conversions recorded for direct visits but never for ad clicksoppref stripped by a redirect, consent wall or CDN ruleClick a live ad, let every redirect finish, then read document.cookie for __oppref. Present on the first URL and gone by the last means the strip is yours.
Conversion count roughly double your order countPixel and Conversions API both firing with no shared deduplication keyTake one order and compare the pixel’s event_id against the API’s id. They must be the same string.
Conversion count well below order count while both paths are liveA deduplication key that is not unique per event — a customer, cart or subscription ID reusedPull ten orders and list the key each one produced. Any repeat is a collapse.
A Conversions API batch is rejected in full and no single event looks wrongOne malformed event in the batch, which fails the entire batchResend with validate_only: true and bisect the batch until the failing event is isolated.
Match rates collapse after an identity change nobody thinks was riskyDouble-hashed values, or hex emitted in uppercaseTake one known email, lowercase and trim it, SHA-256 it, and compare your 64-character lowercase hex against what the payload actually sends.
Reported volume steps up or down on one date with no release that dayAutomatic Advanced Matching switched on, or per-pixel drift across propertiesTools → Conversions → Data Source → Edit pixel on every pixel in the account. Check the state of each and annotate 17 August 2026 on the chart.
Yesterday’s conversions look catastrophicThe 24 to 48 hour attribution delay, read as breakageRe-pull the identical date range 48 hours later. If the number moved, the pipe works and the panic was premature.
Zero for a week, and someone keeps saying it has not landed yetReal breakage being explained away by the reporting delayFire one test conversion through a real ad click with a written-down order ID and time. Absent 48 hours later means broken, and the sentence stops being available.
Your actual outcome does not appear in the campaign’s conversion event dropdownIt is a custom event, which is not eligible as an oCPC goalOpen the conversion event selector at campaign level. If the event is missing, fire a standard event alongside the custom one and point oCPC at the standard event.
403 Conversion bidding is not enabled, or no Conversions objective availableOne of oCPC’s four gates is shut — usually no standard event, or an event belonging to a different ad accountCheck all four in order: tracking live, at least one standard event, the event owned by this account with an active source, account enabled for conversion bidding.
Three glass gates in a row on a white field; the first and third stand open, the second is a solid frosted panel, and blue light entering from the left pools against that panel with the channel beyond it left dark.
script-src open, connect-src https://bzr.openai.com missing, img-src open: two gates of three, which is the same as none. Every event pools against a panel that is invisible from Ads Manager, and what arrives downstream is not an error but a zero — the identical number a campaign with no demand at all produces. That ambiguity, repeated across all seven failure modes above, is the entire argument for verifying each hop rather than assuming it.

What you cannot measure, even with a perfect install

Even with a flawless implementation, ChatGPT Ads will not tell you who converted, what they were talking about, which conversation your ad appeared in, how your buyers break down demographically, or whether the person who converted today is the same person who saw the ad last week on another device. Those are not gaps in your setup and they are not features on a roadmap — they are deliberate consequences of how OpenAI built the channel, and no vendor, no partner integration and no amount of engineering closes them. We put this section before the sales material because the most expensive thing an implementation can do is create the belief that the conversion column is now complete. It is not complete. It is honest in one direction only, and that is worth a great deal less than complete but a great deal more than nothing.

What data does the platform never expose?

Reporting in ChatGPT Ads is aggregated by design, and advertisers do not get access to chats, chat history, memories or personal details. There is no user-level export, no conversation content, no prompt text and no per-prompt breakdown. There is nothing resembling a search-term report, because there are no search terms: context hints guide matching but are not exact-match targeting rules, and no query log exists to be exported. There are no demographic breakdowns, because there is no age or gender targeting to report against — users declared or predicted under 18 are excluded platform-wide, and ads serve only to Free and Go plan users, never to Plus, Pro, Business, Enterprise or Education. Conversations about politics, health and mental health carry no ads at all.

Identity stops at the ad interaction. oppref identifies an individual ad interaction, not a person and not a session series; there is no cross-device identity in the platform and no retargeting audience, so nothing joins one visit to the next. A buyer who reads a sponsored card on a phone at lunch, thinks about it overnight and purchases from a laptop leaves no thread anything can follow. That is a privacy decision rather than an oversight, and treating it as an implementation problem — buying a tool, adding an identity graph, chasing a vendor who claims to resolve it — is money spent against a wall.

Which reports do not exist?

There is no placement or “where ads showed” report, no auction insights, and no ad-level A/B testing primitive. The effects are specific. Brand-safety reporting cannot be built on placement data, because placement data does not exist; your inventory boundary is set by plan eligibility and content policy, not by an exclusion list you manage. Competitive overlap and impression share are unavailable, so any share-of-voice slide in this channel is an estimate presented as a measurement. And no controlled creative test can be run inside the platform: two ads in one ad group is not an experiment, because there is no traffic split and no control. Native segmentation ends at Device and Country, which is what Segment controls; Edit columns adds metric columns including individual conversion events and the optional VTA (1d) column, and the two menus are not interchangeable. Cost per conversion is not a first-class metric in all views — derive it as spend ÷ conversions.

How much of the truth does a perfect install actually capture?

This channel under-reports by design. AdVenture Media’s analysis of ChatGPT-ad-attributable conversions found that roughly 40% happen in the immediate post-click session and roughly 60% arrive later, through paths that cannot be traced. That is not a defect anyone implements their way out of. Someone can engage with your category across a long conversation, form a firm preference, never click the ad at all, and convert three days later through a branded search. There is no pixel, no server event and no partner integration that recovers that conversion for this channel, because the platform was never given the identity to connect it.

Here is illustrative arithmetic — not a client result and not a benchmark. Suppose an account reports 40 conversions in a month against $6,000 of spend, which is a reported cost per conversion of $150. If AdVenture Media’s split holds for that account, those 40 traceable conversions correspond to roughly 100 attributable conversions in total, and true cost per acquisition is nearer $60. Read the same numbers the other way and the reported figure is roughly 2.5× the real one — which is more than enough to get a profitable channel killed in a spreadsheet by someone who has never been told the split exists. The reported CPA in your account is an upper bound on true cost per acquisition, not an estimate of it. The five-layer response — platform tracking, then UTMs and dedicated landing pages, then self-reported attribution, then CRM outcome tracking, then holdout and lift testing — is worked through in our guide to measuring ChatGPT Ads ROI. A tracking implementation builds layer one properly. It cannot build the other four for you, and layer one alone will always be the smallest of the five.

What does VTA (1d) add, and what does it not?

VTA (1d) is counted when someone converts within one day of an eligible impression and no qualifying click receives credit for the same conversion; where both a click and a view qualify, the click wins. Four rules govern it and every one of them is a limit rather than a capability. It is not included in your Conversions total. It does not touch performance math — no effect on CPA, conversion rate, bidding or billing, and oCPC does not optimize toward it. The window is fixed at one day, not configurable, and independent of the click window, so a longer click window does not extend the view window to match. And no implementation changes are needed to receive it: nothing to install, no tag to add, no configuration to buy. If a vendor is quoting implementation work to enable view-through on this platform, that work does not exist.

The honest reading is that view-through is supplemental evidence, never performance. It belongs in the interpretation and never in the total, and the inflation it causes always happens downstream in a spreadsheet, when somebody adds the two columns together to make a quarter look better. An account reported that way for a few months has no usable baseline left.

Why do some conversions land as direct traffic?

Some ChatGPT Ads conversions arrive in your analytics as direct traffic because of how the click is handed off and how your redirects handle it, and this is separate from whether the OpenAI pixel recorded them. Ad clicks and analytics sessions measure different things and will never fully agree: a click is an ad interaction, while a session depends on page load, redirects, consent handling, browser blocking, UTM handling, attribution windows and time zones. A redirect chain that drops the query string, a consent tool that reloads the page clean, or a payment domain hop can leave a genuine ad-driven visit indistinguishable from someone typing your URL.

The correct response is reconciliation rather than explanation. Compare the same date range in the same time zone, then check campaign and ad-level activity in the CSV to see where the difference concentrates — a gap sitting in one campaign is usually a redirect or a consent wall on one landing page, while a gap spread evenly is usually a time-zone offset or ad blocking. Time zone cannot be fixed: your account’s country, billing currency and time zone are set once at account creation and cannot be changed, so an analytics platform on a different zone offsets every daily comparison permanently and the remedy is to account for it. This is also why the four URL macros matter more here than they would elsewhere — {campaign_id}, {ad_group_id}, {ad_id} and {ad_account_id} push the segmentation Ads Manager will not give you into a system that will. They return IDs and not names, so keep a mapping table.

What can you benchmark your numbers against?

OpenAI publishes no performance benchmarks across advertisers or industries, so there is no authoritative external number to compare your account against. What exists instead is third-party reporting that disagrees with itself. First Page Sage reported conversion rates spanning 0.2% to 5.8% across 19 industries on 12 June 2026. Published CTR figures include 0.91%, 3.8–4% and 1.5–6%. Opascope documented 1.49× ROAS at a $1.72 CPC over 15 days — one public case, not a benchmark. Observed CPCs run from that $1.72 up to $8–15 in B2B. An account landing anywhere inside those ranges can be told any story its owner prefers.

Your own account’s history is therefore the only comparison set that means anything, which is the strongest practical argument for getting the measurement right early and leaving it alone: a clean series that starts today is worth more in three months than a contested one that starts a year ago. It is also why we annotate every measurement change with its date. If you already have a history and are trying to work out whether it is trustworthy, that is a diagnosis rather than a build, and the ChatGPT Ads audit is the right purchase. Where you run several measurement tools alongside each other, expect them to disagree — they will not agree, and they are not supposed to, as our measurement partners guide sets out.

What you can measureHowWhat you cannotWhy not
Conversions by campaign, ad group and adAds Manager table view, with individual events added under Edit columns → Conversions & eventsWhich conversation produced the conversionReporting is aggregated. Advertisers do not get access to chats, chat history, memories or personal details.
Device and country splitsSegment, which controls those two breakdowns onlyAge, gender or any demographic breakdownThere is no age or gender targeting, so there is nothing to report against. Under-18 users are excluded platform-wide.
Each conversion event as its own columnEdit columns, under Conversions & eventsA search-term report or any per-prompt equivalentThere are no keywords. Context hints guide matching but are not exact-match rules, and no query log exists.
Post-click conversions tied to one ad interactionoppref captured into the __oppref cookie, or passed as obref server-sideThe same person across devices or sessionsoppref identifies an individual ad interaction. There is no cross-device identity and no retargeting audience.
View-through conversions, as supplemental evidenceThe optional VTA (1d) column, or the attribution breakdown on hoverView-through inside CPA, conversion rate, bidding or billingVTA is excluded from the Conversions total by design, and oCPC does not optimize toward it.
Offline, CRM and server-side outcomesConversions API, POST https://bzr.openai.com/v1/events?pid=<PIXEL-ID>A backfill of historical conversionstimestamp_ms must be within the last seven days and no more than ten minutes in the future.
Ad-group and ad detail inside your own analyticsThe four URL macros, set through the three-dot menuHuman-readable names in those parametersMacros return IDs, not names, and are substituted at delivery time. Keep a mapping table.
Spend, impressions, clicks, CTR, avg CPC, avg CPMTable view, Insights charts, and CSV export including Export daily valuesWhere the ad appeared, or who else was biddingThere is no placement report and no auction insights.
Sequential creative comparisons you run by handLaunching and pausing ads yourself and reading the differenceA controlled ad-level A/B testThere is no ad-level A/B testing primitive, so there is no traffic split and no control group.
Your own account’s trend, on a matched time zoneCSV export with the trailing 48 hours discardedYour performance against other advertisers or your industryOpenAI publishes no performance benchmarks across advertisers or industries, and third-party figures disagree wildly.

What a perfect install actually buys you

A verified implementation buys you a conversion column that is trustworthy in one direction: everything in it is real, and not everything real is in it. That is enough to open oCPC’s first gate, enough to tell a demand problem apart from a measurement problem, enough to make one ad group comparable with another, and enough to give you a floor under your true performance rather than an unknown. It does not buy complete attribution, and on AdVenture Media’s split roughly 60% of what this channel produces will still arrive by paths you cannot trace. It does not buy the conversation, the competitor, the placement or the benchmark, because none of those exist to be bought. Anyone selling you a ChatGPT Ads implementation that promises full-funnel visibility is selling you something the platform does not have.

The pixel install

Installing the ChatGPT Ads pixel means loading OpenAI’s JavaScript SDK from https://bzrcdn.openai.com/sdk/oaiq.min.js, calling oaiq("init", { pixelId: "<ID>" }) once before any other code touches the global, and calling oaiq("measure", "<event>", <data>, <options>) at the moment each measurable action completes. There is one global, oaiq, and every interaction with the pixel goes through it as a string command plus arguments. The Pixel ID comes from Tools → Conversions in Ads Manager, which is also where the data source, the conversion events and the pixel validation diagnostics live — if you are hunting for a measurement control and cannot find it, it is in Tools.

The install itself is twenty minutes of work. Coverage takes the rest: the SDK present on every page that can start or finish a conversion, initialized against the right Pixel ID, and initialized before anything asks it to measure. Those three properties determine whether the Conversions column is telling you the truth, and all three fail quietly.

What does the SDK do without being told?

The ChatGPT Ads SDK handles the click identifier, the page context, event timing and network batching automatically, which is the reason a browser-side install is worth having even when you also run server-side. Once oaiq("init", ...) has run on a page, five things happen without any further code from you:

Four things it does not do. It does not decide which events to fire — every oaiq("measure", ...) call is yours to place. It does not survive an ad blocker; a blocked SDK produces no error your analytics will ever see. It does not help the Conversions API, which never captures oppref for you. And it does not transmit anything if your Content Security Policy has not allowed the collection host, which is the subject of the next section and by some distance the most common reason a technically correct install reports zero.

One behaviour is easy to miss: automatic advanced matching is a per-pixel setting under Tools → Conversions → Data Source → Edit pixel, it hashes client-side in the browser, and it is opt-out — taking no action means it is on. OpenAI enabled it on existing Web pixels on 17 August 2026, so an account with data either side of that date should expect a step change in reported volume and annotate it before comparing anything across the boundary. It applies to the Web pixel only and does nothing for Conversions API events or the image tag. Our guide to automatic advanced matching covers running it alongside manual matching, which is the strongest configuration.

<!-- In the document head, in your base template, on every page -->
<script src="https://bzrcdn.openai.com/sdk/oaiq.min.js"></script>
<script>
  oaiq("init", { pixelId: "<PIXEL-ID>" });
</script>
// At the moment the order is confirmed, not on page load
oaiq("measure", "order_created", orderPayload, {
  event_id: order.id,   // the same value the Conversions API will send as "id"
  debug: true           // console confirmation while you are implementing
});

debug: true gives you console confirmation of what the SDK sent, which is the only feedback loop you have during implementation — attributed conversions take 24 to 48 hours to appear in Ads Manager, so day-one reporting cannot tell you whether the install works. Turn debug off before the code ships to production traffic.

If you gate on consent, the documented order is oaiq("consent", false), then oaiq("init", ...), then oaiq("consent", true) once the user has agreed. Initializing in a denied state and flipping the flag later is not the same as delaying initialization until after consent, and the difference matters: the SDK has to exist on the landing page to see oppref at all.

Where does the snippet belong?

The snippet belongs in the document head of your base template or layout component, executing before any application code that might call oaiq, on every page the site serves. Putting it anywhere else creates one of two problems: per-page installs drift, because the next page someone builds will not have it; and installs placed late in the body race against whatever fires the conversion.

A tag manager is acceptable but adds a dependency: a second place the Pixel ID can be wrong, a load-order variable you do not control, and a deployment path invisible to anyone reading the repository. Document the ID in the codebase anyway. In a single-page app, initialize once at bootstrap and call measure on the events themselves rather than on route changes — and initialize before your router rewrites the URL, because a router that cleans the query string before the SDK reads it destroys the click reference. That failure is covered under preserving the click reference.

Which pages have to carry the pixel?

Every page that can either receive an ad click or complete a measurable action has to carry the pixel, which in practice means a site-wide install rather than a list of pages. The list below is what we walk through on an implementation, and the third and fourth rows are the ones that are missing in most accounts we look at, because they are usually rendered by something other than the main application.

PageWhy it has to carry the SDKWhat breaks without it
Every entry page — ad landing pages, home, pricing, blog postsThis is where oppref arrives on the URL and where the __oppref cookie gets writtenThe conversion still fires, but with no click reference attached. The event is accepted and never credited to the ad.
Every step of the checkout or application flowMid-funnel standard events — items_added, checkout_started — fire hereYou can see the outcome but not the drop-off, so a broken step three looks identical to a demand problem.
The post-payment redirect pageA hosted checkout returns the buyer to a URL on your domain; that page is the first thing you control after payment and the natural home for order_createdThe purchase event never fires at all. This is the single most common gap in ecommerce installs, because the checkout is somebody else’s product.
The thank-you page behind a formlead_created, registration_completed and appointment_scheduled complete hereNothing fires. Confirmation pages are frequently a separate template, a separate subdomain, or a page rendered entirely by the form or scheduling vendor.
Every subdomain in the journey (app., go., checkout.)The SDK has to be present on each host, and the __oppref cookie has to be readable thereA click reference written on one host and unreadable on the next is the same as no click reference. Open the console on the second host and inspect document.cookie before you assume it carries.
Trial, signup and subscription pagestrial_started and subscription_created fire in the product, not on the marketing siteSaaS accounts routinely instrument the marketing site perfectly and measure nothing that happens after signup.

How does pixel coverage actually go wrong?

Pixel coverage fails in three ways — partial coverage, the wrong Pixel ID, and initialization order — and two of the three produce no error message anywhere. All three end at the same place: a Conversions column reading zero, which is indistinguishable from an account nobody wants to buy from.

FailureWhat you seeHow to confirm it in ten minutes
Partial coverageZero conversions, or a conversion count that is credible but consistently lowView source on the landing page and on the confirmation page. Both must contain the SDK. If only the confirmation page has it, events fire with no click reference; if only the landing page has it, nothing fires at all.
Wrong Pixel IDZero on the campaign, while the events themselves look healthy in the browserCompare the ID in the page source, character by character, against the data source in Tools → Conversions. Search the rendered DOM for more than one init call while you are there.
Initialization orderZero, plus a console error on the page that was supposed to convertOpen the console on the conversion page and look for a ReferenceError naming oaiq, or a measure call logged before the init call.

Partial coverage is asymmetric, and the asymmetry is what people get wrong. The instinct is to install the pixel on the pages where conversions happen. That is exactly backwards: the conversion page fires the event, but the landing page is where oppref is captured and written to __oppref. A site with the pixel only on its thank-you pages produces events that are technically valid, arrive intact, and can never be attributed to an ad. The symptom is not zero — it is a conversion count in your own analytics that Ads Manager does not agree with, which sends teams looking for an attribution problem when what they have is a coverage problem.

The wrong Pixel ID is the failure that looks most like success. Staging IDs promoted to production, an ID copied from a different property, a tag manager container carrying a value nobody has revisited in months — each produces a page that fires clean, well-formed events into a data source no campaign is reading. Events do not travel between ad accounts, so an ID belonging to a different advertiser account is not a partial credit situation; the campaign sees nothing. Correcting the configuration does not backfill, either. Everything measured against the wrong ID stays lost.

Initialization order is the only one of the three that is loud, and it is still routinely missed, because the error appears on a page almost nobody has open with devtools. If oaiq does not exist at the moment you call it, the call throws and the event is gone. Two patterns cause it: loading the SDK with async or defer while init runs from inline script that executes first, and calling measure from application code that evaluates before the bundle carrying init. Load the SDK synchronously ahead of the init call, or defer every call until the script’s onload. Do not wrap the calls in a try block — that converts a visible failure into a silent one, which is the wrong direction on a channel where silence is already the problem.

What this means for your account

If your site has a single codebase, one base template and no hosted checkout, the pixel install is an afternoon and you should do it yourself — the guide at ChatGPT Ads conversion tracking is written to be executed, not admired. What we get paid for starts where the install crosses a boundary you do not own: a hosted checkout, a form vendor’s confirmation page, a separate app subdomain, a consent platform, or a CSP maintained by a team that has never heard of this channel.

The content-security-policy allowlist

The ChatGPT Ads pixel needs three Content Security Policy directives to function, and all three have to be present: script-src https://bzrcdn.openai.com to load the SDK, connect-src https://bzr.openai.com to transmit events, and img-src https://bzr.openai.com for image-based transmission. Two different hosts are involved, and they differ by three characters. That is the entire trap.

Content-Security-Policy:
  default-src 'self';
  script-src  'self' https://bzrcdn.openai.com;
  connect-src 'self' https://bzr.openai.com;
  img-src     'self' data: https://bzr.openai.com;

A missing connect-src is the classic cause of “the pixel is installed but no conversions appear”

The SDK loads, because script-src was allowed. oaiq is defined, the measure call executes, the page renders normally and nothing in Ads Manager indicates a problem. The events never leave the browser. Ads Manager can only report on data that arrived, so a transmission that was blocked at the client is invisible to every diagnostic on OpenAI’s side — the only place the failure exists is the browser console of the person who converted. Check this before you change a bid, a hint, or a budget.

DirectiveHostWhat it permitsSymptom when it is missing
script-srchttps://bzrcdn.openai.comFetching and executing oaiq.min.jsLoud. The script never loads, oaiq is undefined, and every call throws a ReferenceError in the console.
connect-srchttps://bzr.openai.comThe outbound event beacon — fetch, XMLHttpRequest and navigator.sendBeacon all fall under this directiveSilent. Everything appears installed and working. No event is ever received.
img-srchttps://bzr.openai.comImage-based transmission, including the HTML-only image tagSilent. Image-tag deployments — the fallback for environments where you cannot run JavaScript — transmit nothing.

Why is connect-src the one that gets missed?

connect-src gets missed because nobody has to add it to make the pixel appear to work. Somebody allowlists bzrcdn.openai.com under script-src, because without that the console fills with errors and the install visibly fails within seconds. The beacon host is different, it is only exercised when an event actually fires, and if connect-src is not declared at all the browser falls back to default-src — which on any policy worth having is 'self'. The block is therefore inherited from a directive that was written before this channel existed, by someone who was not thinking about it.

The organizational half is worse than the technical half. The people who install pixels and the people who own the CSP are almost never the same team. Marketing or growth installs the tag; platform, security or whoever owns the edge maintains the policy header. The tag install is a ticket that closes in a day. The CSP change is a ticket that goes into a security queue, gets asked what bzr.openai.com is, and sits. Meanwhile the pixel is reported as live, the campaign launches against it, and by the time anyone questions the conversion count there is a month of spend measured against nothing. On an implementation we treat the CSP as a launch blocker on the same footing as the pixel itself — the account does not go live until a real event has been observed leaving a real browser.

Three more ways a CSP defeats an otherwise correct install, all of them worth checking before you conclude the directives are fine:

Before any of that can be checked, somebody has to find the policy, and on a mature site it is rarely in one place. Read the live value first — curl -sI https://www.example.com/order/confirmation returns the enforced header, which is the only version that counts. Then find where it is authored: a framework middleware, a reverse-proxy or web-server config, an edge worker, a WAF or security product injecting headers, or a <meta> tag in the template. More than one of those can be in play at once, which is why teams sometimes ship the fix three times before it takes. Also check for a Content-Security-Policy-Report-Only header sitting alongside the enforcing one: adding the hosts to the report-only policy changes nothing, and it looks exactly like a completed ticket.

How do you verify the allowlist in five minutes?

Verify the CSP by watching a real event leave a real browser on the actual conversion page, with the network panel filtered to bzr.openai.com. Open devtools on the page where the conversion completes — not the homepage — enable Preserve log, because confirmation pages are usually arrived at through a redirect that would otherwise clear the panel, then complete a test conversion.

  1. Filter to bzr.openai.com and look for the request. A successful transmission appears as a request to the collection host. No row at all, with the measure call demonstrably executing, means the browser refused to make it.
  2. Read the console for the refusal. A CSP block reports itself — a “Refused to connect” message naming the offending directive. It appears at the moment the event fires, not at page load, which is why nobody sees it: the only person with the console open is rarely the person completing a purchase.
  3. Filter to bzrcdn.openai.com separately. That confirms script-src. Two hosts, two filters, two separate confirmations. Confirming one and assuming the other is how half-finished allowlists reach production.
  4. Repeat on every host in the journey. Marketing domain, app subdomain, checkout return page. Each may carry a different policy.
  5. Then check the diagnostics panel. Tools → Conversions reports why events were dropped, the affected field, the error type and a recommended fix. It is the right tool for malformed events — it cannot tell you about events that never left the browser, which is precisely why the browser check comes first.

The Conversions API

The ChatGPT Ads Conversions API is a server-to-server endpoint at POST https://bzr.openai.com/v1/events?pid=<PIXEL-ID>, authenticated with a Bearer token and sending Content-Type: application/json. It reports to the same Pixel ID as the browser pixel, which is what makes running both simultaneously coherent rather than duplicative — provided deduplication is set up correctly, which is the next section. The API is not an alternative to the pixel so much as the half of the implementation that survives conditions the browser does not.

curl -X POST "https://bzr.openai.com/v1/events?pid=<PIXEL-ID>" \
  -H "Authorization: Bearer $CONVERSIONS_API_TOKEN" \
  -H "Content-Type: application/json" \
  --data @batch.json
{
  "validate_only": true,
  "events": [
    {
      "id": "A-10482",
      "type": "order_created",
      "timestamp_ms": 1788267600000,
      "action_source": "web",
      "source_url": "https://www.example.com/order/confirmation",
      "opt_out": false,
      "user": {
        "obref": "<value of the __oppref cookie>",
        "email": "<sha-256, lowercase hex>"
      },
      "data": { }
    }
  ]
}

Why should validate_only be on for the whole build?

Send validate_only: true on every request until the payload is clean, because it lets you exercise the real endpoint with real data without writing anything into your conversion history. This matters more here than on most APIs for one reason: correcting a measurement configuration does not backfill. A week of malformed production events is not a week you can re-send once you understand the schema — the timestamp rules below will refuse most of it. Build against validation, flip the flag once, and treat the flip as the actual go-live.

The same discipline applies to the first sync from any warehouse or partner tool. Backfilling history on first sync is one of the documented ways measurement-partner integrations go wrong, and it fails twice over: the historical timestamps are outside the accepted range, and the events that do land distort the baseline you were about to measure against.

How large should a batch be?

A batch may carry up to 1,000 events, and the entire batch fails if one event fails — which makes batch size a reliability decision rather than a throughput one. At the maximum, one malformed row takes 999 valid conversions down with it. The question is not how many events you can fit into a request; it is how many you are willing to lose to a single bad one.

Batch sizeBlast radius of one bad eventWhere it belongs
1,000 (the maximum)999 good events rejected alongside itSteady-state streams from a single validated code path, where the payload shape cannot vary
50 to 100Tens of events, and a failure you can bisect quicklyThe sensible default for most implementations, including anything sourced from a CRM
1One eventRetries after a batch failure, migrations, and the first days of any new integration

Build the failure path before you need it. On a rejected batch, split it and retry the halves — a binary search isolates the offending event in a handful of requests instead of a morning of log reading. Derive every id from the underlying record rather than generating it at send time, so a retry carries exactly the same identifiers as the attempt that failed; an implementation that mints fresh identifiers on retry has built a duplicate generator. And read the pixel validation diagnostics in Tools → Conversions while you are debugging: it reports why events were dropped, which field was responsible and what to do about it, and on the server-side path it is genuinely the fastest route to the answer.

What does every event have to carry?

Every Conversions API event carries an identifier, an event type, a timestamp, a data object and an action source, plus conditional fields that depend on those choices. The rules below are where implementations break, and none of them are negotiable at the endpoint.

FieldWhen it appliesThe ruleWhat a violation costs
idEvery eventThe deduplication key. It must be the same value the pixel sends as event_id for the same conversion.The conversion is counted twice — once from the browser, once from your server — and every derived number inherits the error.
typeEvery eventOne of the 13 standard event names, or custom. The four data shapes are contents, customer_action, plan_enrollment and custom.An event can fire, be accepted and be forwarded and still report 0 if it is not the event the campaign is configured to optimize toward.
custom_event_nameWhen type is custom1–64 characters, alphanumeric plus underscore and dash, starting and ending alphanumeric. Ads Manager must be set to Event type: Custom and the name must match exactly — a matching display name is not enough.Zero reported, no error raised, and no backfill once you fix it. custom is also not eligible as an oCPC goal, so fire a standard event alongside it if this is your real outcome.
timestamp_msEvery eventWithin the last 7 days, and no more than 10 minutes in the future.Rejected — and because rejection is at batch level, it takes the whole batch with it. This is what kills naive backfills and servers with drifting clocks.
action_sourceEvery eventOne of web, mobile_app, offline, physical_store, phone_call, email, other.Mislabelling a CRM outcome as web pulls in the source_url requirement and forces you to invent a URL that was never visited.
source_urlWhen action_source is webThe URL where the action occurred.Rejection, and with it the batch. The browser pixel adds this for you; the server does not.
dataEvery eventThe shape belonging to the event family. Monetary amounts in ISO 4217 minor units.See below — this is the field that produces confidently wrong numbers rather than errors.
userEvery eventCarries obref — the click reference — along with hashed identifiers. Hash email and external IDs with SHA-256 as lowercase 64-character hex; send country, city, zip, IP and user-agent raw. Never send raw emails, IDs or phone numbers.Two errors dominate: hashing a value that was already hashed, and emitting uppercase hex. Both produce a hash that matches nothing, silently.
opt_outEvery eventThe event’s opt-out flag, set from the user’s consent state.Set it from the same source of truth your consent platform drives, not from a constant.

What happens when amounts are sent in major units?

Money on the Conversions API is expressed in ISO 4217 minor units: $129.99 is 12999. An integration that sends major units instead reports that same order as 129 or 130 minor units — $1.29 or $1.30 — understating revenue by roughly a factor of one hundred. Nothing errors. The event count is perfect, the conversion appears on schedule, and every revenue figure downstream is wrong by two orders of magnitude.

The consequences run further than a bad dashboard. Conversion value feeds the reasoning you use to set bids: a break-even CPC is target CPA multiplied by landing-page conversion rate, so an account that believes its average order is $1.30 will conclude the channel cannot support a $3 bid, which is roughly the delivery floor. It will then reduce bids, lose delivery, and read the lost delivery as further evidence the channel does not work. We have seen the reverse too — an integration sending minor units into a system expecting major ones — and it inflates ROAS a hundredfold, which nobody investigates because good news audits itself.

Amounts in major units is one of the documented failure modes across every measurement-partner integration, along with turning the pixel off after connecting a partner, mismatched event_id, double-hashing warehouse emails, non-unique primary keys, backfilling history on first sync, and ignoring the diagnostics panel. Our guide to ChatGPT Ads measurement partners covers all four of the current partners and what each one gets wrong by default. The test to run is trivial and almost nobody runs it: send one order of a known value through the integration and confirm the number that arrives is the one you expect, before you connect the other nine thousand.

Why does the server never see oppref?

The Conversions API does not capture oppref for you — you must pass it, as obref inside the user object. The browser SDK reads the click identifier off the landing URL and stores it in the __oppref cookie automatically; your server has no equivalent, because it was never handed the landing URL and has no access to that cookie unless you arrange it. This asymmetry is responsible for a large share of server-side implementations that transmit perfectly and attribute nothing.

Note the spelling. The query parameter and the cookie are oppref and __oppref; the API field is obref. They are not the same string, and a transcription that assumes they are produces a payload the endpoint has no reason to complain about. Getting the value from the browser to your server is a capture problem rather than an API problem, and the pattern for it is in preserving the click reference.

When is server-side not optional?

Server-side measurement stops being an upgrade and becomes a requirement in three situations, and the Conversions API is more reliable than the pixel alone in all of them.

That last case is the one worth planning for early. Roughly 40% of ChatGPT-ad-attributable conversions happen in the immediate post-click session and roughly 60% arrive later by paths the click never sees, according to AdVenture Media — which means the events your warehouse computes are not a supplement to the browser events, they are the larger half of the picture. Our conversion tracking guide lays out the full five-layer stack this implies, from platform tracking through UTMs and self-reported attribution to holdout testing.

A thin upright glass pane and a denser glass block set apart on a white field, each sending a filament of blue light toward the lower centre where the two merge into one; the pane's filament is finer and thins along its length, the block's is thicker and steady.
Two signal paths, one Pixel ID. The browser path is thinner and loses strength along the way — ad blockers, a missing connect-src, a consent gate, a device that never returns. The server path carries the events the browser cannot see at all: offline outcomes, CRM closes, conversions a warehouse model computes days later. Neither is a substitute for the other, and running both is only safe if the merge is deduplicated.

Deduplication

Deduplication in ChatGPT Ads works by sending the same identifier as the pixel’s event_id and the Conversions API’s id for the same real-world conversion, and for custom events the custom_event_name must match on both sides as well. Get it right and a browser event and a server event describing one purchase collapse into one conversion. Get it wrong and every conversion measured by both paths is counted twice — which is worse than counting it once, because a doubled conversion count halves your apparent cost per conversion and every decision made from that number is confidently wrong in the expensive direction.

The order or transaction ID is the right choice, and in almost every business it already exists. It is generated once, at the moment the conversion becomes real; it is stored on the record; it is available to the browser on the confirmation page and to the server whenever the order is processed, retried or reconciled from a warehouse weeks later. That last property is the one that matters and the one that eliminates most alternatives: a deduplication key has to be reproducible by two systems that never talk to each other, at two different times.

What makes a candidate key work or fail?

A deduplication key works only if it is derived from the conversion itself rather than from the act of reporting it. Every failing candidate in the table below fails the same test in a different costume: two systems observing one conversion independently generate two different values, so the platform sees two unrelated events.

Candidate keyWorks?Failure mode
Order or transaction IDYesNone, provided it is genuinely unique. The one hazard is per-tenant or per-day sequences that reset — an id of 1001 that recurs next month will merge two unrelated conversions into one and silently undercount.
TimestampNoThe browser and the server observe the same conversion at different instants and to different precisions. The pixel fires when the confirmation page renders; the server writes when the payment settles or when a queue drains. Two timestamps, two identifiers, two conversions. Rounding to the second does not fix it and creates a new problem — two genuine conversions in the same second collide and one disappears.
An independently generated random value (a UUID minted at send time)NoRandomness is the property you do not want. A UUID generated in the browser and a UUID generated on the server are guaranteed to differ, so deduplication can never match them. Worse, it is invisible in testing: fire the pixel alone and it looks perfect; fire the API alone and it looks perfect; run both and the count doubles. It also breaks retries — a resent batch mints new values and duplicates events that were already accepted.
Session IDNoWrong cardinality in both directions. One session can contain several conversions — two orders, or a lead form and a booking — and they would all share one key, so all but one vanish. One conversion can also span several sessions: a checkout completed on a phone after a cart built on a laptop, or a subscription renewed weeks later. The server frequently has no session concept at all, which makes the key unreproducible on the path that needs it most.
Customer ID or hashed emailNoIdentifies the person, not the event. Every repeat purchase by the same customer carries the same key and gets absorbed into the first one, so your best customers count once and the account looks least valuable exactly where it is most valuable.
Warehouse model primary keyYes, conditionallyWorks when the key is truly unique and stable across model runs — this is what Hightouch reuses as the event_id. It fails when the model is rebuilt with a different surrogate key, or when the “unique” key turns out to be unique only within a partition.

What extra rule applies to custom events?

For custom events, custom_event_name must match on both sides in addition to the identifier — matching ids alone will not merge two custom events with different names. This is the second of two exact-match requirements that surround custom events, and they compound: the name must match between pixel and API for deduplication, and it must match the value configured in Ads Manager, with Event type: Custom selected, for the campaign to count the event at all. A matching display name does not satisfy either requirement.

Names are constrained to 1–64 characters, alphanumeric plus underscore and dash, starting and ending alphanumeric. Define the string once in shared configuration and reference it from both the browser and the server. Hand-typing it in two codebases is how quote_requested and quote-requested end up in the same account, and neither will match the third spelling somebody entered into Ads Manager. Remember also that a custom event is not eligible as an oCPC goal, so if your real outcome is custom, fire a standard event alongside it — and then dedupe both, because you now have two events per conversion by design. The guide to ChatGPT Ads Conversions campaigns covers the four gates an account has to pass before oCPC will run at all.

Should the pixel be turned off once the API is live?

No — turning the pixel off after connecting a server-side source is one of the documented ways measurement-partner integrations go wrong, and it is usually done for the right-sounding reason. Someone connects a partner or ships the Conversions API, watches the conversion count rise, concludes the two sources are double-counting, and disables the pixel to make the number look sane again. That trades a deduplication bug for a coverage loss: the browser path is what captures oppref into __oppref in the first place, and it observes conversions from visitors your server never learns about individually.

If the count rose when you connected the second source, deduplication is what to fix. Define the identifier once, at the point the conversion record is created, and pass the same value to both paths — not one value computed in the frontend and another computed in a job. Then verify with a single real conversion before you trust volume: fire it through both paths, wait the 24 to 48 hours attributed conversions take to appear, and confirm Ads Manager reports one.

Two identical translucent blue glass tokens approaching from opposite sides of a white field and resolving at the centre into a single brighter token no larger than either original.
One conversion observed twice must resolve to one conversion counted. That only happens when both observers carry identical identifiers — the pixel’s event_id and the API’s id — derived from the order itself rather than from the moment of reporting. Two tokens that differ by a single character do not merge; they double your conversion count and halve your apparent cost per conversion.

Preserving the click reference

oppref is OpenAI’s privacy-preserving click identifier: it is appended to the landing-page URL when someone clicks a ChatGPT ad, captured by the SDK into the first-party cookie __oppref, and it identifies an individual ad interaction. It is the thread connecting a click to whatever that person does next. Stripping it at a redirect, a consent wall or a CDN rule breaks conversion tracking entirely — not partially, and not in a way that announces itself. Events still fire, still validate, still arrive, and cannot be credited to the ad that paid for them.

This is the failure we find most often on accounts that were confident their tracking was fine. The pixel was installed by someone competent, the CSP was allowlisted, the Conversions API is transmitting — and somewhere between the ad and the landing page, a rule written years ago for an unrelated reason removes a query parameter nobody has audited since.

Where does oppref actually get stripped?

oppref is lost at any hop between the ad click and the SDK reading the URL, and there are usually more hops than the marketing team believes. Every row below is something we have had to fix; the fix is nearly always five minutes once you know which hop is responsible, and finding out is the work.

Where it is lostWhat it looks likeThe fix
A marketing redirectThe ad points at a vanity or campaign URL that 301s to the real page. Most redirect rules build the destination from the path alone and discard everything after the ?.Preserve and append the incoming query string on redirect. Better: point the ad at the final URL and delete the hop.
A link shortenerThe short link resolves to a clean canonical destination. Some shorteners forward query parameters, some do not, and which one yours does is not usually documented where anyone reads it.Test it with a real parameter and watch what arrives. If it does not forward, do not put a shortener between a paid click and a landing page.
A consent gateThe banner is accepted, the page reloads, and the reload lands on the bare path. The SDK now initializes on a URL with nothing to read.Preserve the query string across the reload, and use the documented consent order — oaiq("consent", false), then init, then oaiq("consent", true) — so the SDK is present for the original URL rather than arriving after it is gone.
An edge or CDN normalization ruleQuery strings are stripped or allowlisted to improve cache hit rates, or unknown parameters are dropped for cache-key stability. A www-to-apex or trailing-slash redirect at the edge does the same thing as a side effect.Add oppref to the parameters the edge preserves. This is usually a one-line change owned by a team that does not know the parameter exists.
A single-page app rewriting historyThe router calls replaceState to tidy the URL on boot, sometimes within milliseconds of first paint, and the parameter is gone before the SDK looks for it.Initialize the SDK before the router runs, or read and stash oppref in your own bootstrap before any history rewrite.
A cross-subdomain hopThe click lands on the marketing host and the conversion completes on an app or checkout host, where the __oppref cookie is not readable.Confirm by inspecting document.cookie on the second host. If the cookie does not carry, pass the value forward explicitly — in the link, or in the session your server already maintains.
A server-rendered form postThe lead is created by your backend, which never saw the browser’s cookie, so the CRM record has no click reference and neither does the API event fired from it.The hidden-field pattern below.

How do you carry oppref into a form and onward to the server?

Capture the click reference into a hidden form field on render, post it with the form, and store it on the record the form creates — that is the whole pattern, and it is what lets a lead that converts later be attributed at all. Read the cookie first, because it survives internal navigation between the landing page and the form; fall back to the query string for the case where the form is on the landing page itself.

<form method="post" action="/leads">
  <!-- ... your fields ... -->
  <input type="hidden" name="oppref" id="oppref-field" value="" />
</form>

<script>
  var m = document.cookie.match(/(?:^|;\s*)__oppref=([^;]*)/);
  var ref = m ? decodeURIComponent(m[1])
              : new URLSearchParams(location.search).get("oppref");
  if (ref) document.getElementById("oppref-field").value = ref;
</script>

Three rules govern what happens next. Persist it at record creation, not at conversion time. A lead that becomes a customer six weeks later has long since lost the cookie and may well be on a different device; if the value was not written to the CRM record on day one, it does not exist to send. Store it as a first-class field, not appended into a notes blob or a free-text source field, because you will need to read it programmatically from a system nobody expects to talk to an ad platform. And send it as obref inside the user object when the outcome event goes to the Conversions API.

One interaction is worth stating plainly because it causes hesitation on every implementation: the requirement that timestamp_ms falls within the last 7 days constrains when you report an event, not how old the click reference may be. A deal that closes today is timestamped today and carries an obref captured whenever the click happened. Send outcomes promptly — a nightly job is fine, a monthly export is not — and the age of the stored reference is not your problem.

If a form is hosted by a third party and you cannot add a hidden field, you have two remaining options: pass the value in the query string of the embedded form’s source URL if the vendor supports mapping parameters to fields, or move the conversion event to your own server by having the vendor webhook into an endpoint you control. Both are ordinary integration work. Neither is possible after the fact, which is why this is the first thing we scope on a form-driven business.

How is oppref different from a URL macro?

oppref is how OpenAI credits a conversion to an ad; URL macros are how you credit a customer to an ad group.

They solve different problems and neither substitutes for the other. oppref is issued by OpenAI, arrives on the landing URL, and exists so that the platform can connect a conversion you report back to the ad interaction that produced it — it is the input to attribution inside Ads Manager. The four URL macros — {campaign_id}, {ad_group_id}, {ad_id} and {ad_account_id} — are substituted at delivery time into parameters you define, and they exist so that your systems can tell which ad group a customer came from. Ads Manager has no search-term report and no placement report; macros carried into your analytics and your CRM are how you recover the granularity the interface will not give you.

The practical consequence is that both have to survive the same journey, and the edge rule that strips oppref strips your macros too, so test them together. Three macro rules cause most of the damage: landing-page query parameters are set through the three-dot menu rather than the creation flow, so people cannot find them; the braces must not be URL-encoded, because %7Bcampaign_id%7D arrives at your server literally; and macros return IDs rather than names, so you need a mapping table to read your own reports. Our guide to ChatGPT Ads URL parameters and macros covers the precedence order and the rest, and the UTM generator builds the strings without the encoding mistakes.

What this means for your account

Before anything else on this page is worth doing, click one of your own live ads, watch the address bar, and see whether oppref is still on the URL when the page finishes loading. Then open the console and check that __oppref is in document.cookie. If either check fails, no amount of pixel work, CSP allowlisting or server-side engineering will produce an attributed conversion, and every optimization decision made since launch was made on a number that could not have been right.

Two parallel channels crossing a white field, each carrying a thread of blue light through three glass rings; the upper thread passes all three intact, the lower stops dead at the second ring leaving the rest of its path dark.
Every hop between the ad click and the conversion is a ring the click reference has to pass through intact: the redirect, the consent gate, the edge rule, the router, the subdomain boundary, the form post. Passing five of six is the same outcome as passing none. The conversion still fires and still arrives — it simply arrives anonymous, and the ad that paid for it is never credited.

Identity matching and hashing

ChatGPT Ads identity matching splits customer data into two groups and treats them in opposite ways: email addresses and external IDs are SHA-256 hashed before they leave your systems, while country, city, zip, IP address and user agent are sent raw. Raw emails, raw IDs and raw phone numbers are never sent at all. Getting that split wrong is not a policy problem you hear about later — it is a match-rate problem you never hear about, because a badly derived hash is still a well-formed hash, and nothing in the interface can tell you that a perfectly valid 64-character string matched nobody.

The reasoning behind the split is worth holding on to, because it tells you which mistakes cost money. Hashed fields are identity: they exist so that OpenAI can recognise a person it already knows without you handing over the plaintext. Raw fields are context: country, city, zip, IP and user agent are coarse enough to be useful unhashed and completely useless hashed. Hash an IP address and you have destroyed the only property that made it worth sending. Hashing everything is not a cautious default — it is a silent downgrade, and it is the single most common thing we undo on arrival.

Which fields are hashed, and which are sent raw?

Two fields are hashed and five are sent raw, and the click identifier is a category of its own. The table below is the specification we implement against on every engagement; it goes into the runbook we hand over, because the person who maintains this in eighteen months will not be the person who built it.

FieldFormat sentHashed or raw
Email addressLowercase, trim leading and trailing whitespace, then SHA-256Hashed — lowercase 64-character hex
External ID (your own customer or user identifier)Your canonical form, applied identically everywhere, then SHA-256Hashed — lowercase 64-character hex
Phone numberE.164 first, then SHA-256 — the normalization OpenAI publishes for phone identifiersHashed — never raw
CountryAs collectedRaw
CityAs collectedRaw
Zip or postal codeAs collectedRaw
IP addressAs observed by your serverRaw
User agentThe full string, unmodifiedRaw
oppref (sent as obref inside the user object server-side)Verbatim, as it arrived on the landing URL or as stored in the __oppref cookieRaw — it is a click identifier, not customer information, and hashing it breaks attribution outright

Normalize first, then hash. The order is not stylistic. Tarun@Example.com  and tarun@example.com produce two entirely unrelated digests, so a system that lowercases on the website and does not lowercase in the warehouse export will produce two populations of hashes for the same person and match a fraction of them. Output must be lowercase 64-character hex. The same convention runs through the rest of the platform: custom-audience uploads accept an optional CSV header that must be exactly email, phone_number, email_sha256 or phone_number_sha256, and the hashed variants expect the same normalize-then-SHA-256 treatment. If your audience uploads and your conversion events disagree about normalization, they are describing two different sets of people.

What are the two errors that quietly destroy match rate?

Two errors account for most broken identity matching, and both survive every format check the platform can run. The first is hashing a value that is already hashed. The second is emitting uppercase hex.

Both failures are silent in a specific and expensive way. The Tools → Conversions pixel validation diagnostics panel reports events that were dropped before being sent, the affected field, the error type and a recommended fix — but a double-hashed email is not a dropped event. It is an accepted event carrying a well-formed identifier. The loss shows up as a match rate you cannot see and a conversion count that is lower than reality by an amount nobody can quantify from inside Ads Manager. That is why we verify hashes against known test values during implementation instead of inferring correctness from the absence of errors.

What is Automatic Advanced Matching, and is it already on in your account?

Automatic Advanced Matching is a per-pixel setting at Tools → Conversions → Data Source → Edit pixel that hashes customer information entered into forms in the browser, client-side, and sends the hashes with your Web pixel events. It is the default for new Web pixels, and OpenAI enabled it on existing Web pixels on 17 August 2026. It is opt-out: if nobody in your organisation has touched that screen, it is on. Full mechanics are in our guide to ChatGPT Ads advanced matching.

Four properties of that setting decide how it should be handled in a real account.

The other failure modes are less obvious. Double-hashing reappears here in a new form: a team that already hashes an email into a hidden field, plus an automatic setting that hashes what it finds in the form, can produce two digests of two different things and be confident that both are right. Internal traffic noise is the second: staff logging into the production site through the same forms all day contribute matched identities that are not customers. Per-pixel drift is the third, and assuming server-side coverage is the fourth.

Why the 17 August 2026 changeover has to be annotated

Turning Automatic Advanced Matching on produces a step change in reported conversion volume, because more events now carry an identifier that can be matched. Annotate 17 August 2026 in whatever reporting you keep — the spreadsheet, the BI dashboard, the monthly deck, the internal wiki page that the next media buyer will read. Without that annotation, a before-and-after comparison that straddles the date is not a like-for-like comparison, and it will be misread as a performance change.

What actually changed on 17 August 2026

Nothing changed about how many people bought from you. What changed is how many of those purchases could be matched back to an ad interaction and therefore counted. An account that reads a lift on that date and concludes the creative refresh worked has drawn a conclusion from a measurement change. An account that reads it and concludes nothing, because the date is annotated and the analyst knows why, has kept its judgement intact. This is the cheapest possible piece of measurement hygiene and almost nobody does it.

What this means for your implementation

Two fields hashed, five sent raw, one click identifier passed through verbatim, lowercase 64-character hex enforced in code with a test, a guard against re-hashing digests, an inventory of every pixel and its Automatic Advanced Matching state, deliberate server-side identity on Conversions API events, and a dated annotation at 17 August 2026. That list is not long. It is simply never written down anywhere, which is why the same six mistakes appear in account after account.

Event configuration and exact matching

ChatGPT Ads supports 13 standard events across four data shapes, and the event you fire must match the event configured on the campaign exactly or the Conversions column reads zero while everything else in the account looks healthy. Event configuration is the part of a tracking build where a small naming decision made in an afternoon determines whether three months of spend is measurable, and it is the reason this work is sequenced before media rather than alongside it.

What are the 13 standard events?

The 13 standard events are grouped into four data shapes, and the shape determines the structure of the data an event carries rather than being a label you choose separately. You pick the event; the shape follows.

Standard eventData shapeTypically fired when
order_createdcontentsA purchase completes. Monetary amounts travel in ISO 4217 minor units — $129.99 is 12999.
checkout_startedcontentsA buyer enters the checkout flow.
items_addedcontentsAn item is added to a cart or basket.
contents_viewedcontentsA product or content detail view.
page_viewedcontentsA page view. Almost never the right optimization goal.
lead_createdcustomer_actionA form submission that creates a lead record.
registration_completedcustomer_actionAn account is created.
appointment_scheduledcustomer_actionA booking, demo or consultation is confirmed.
app_installedcustomer_actionAn install completes.
app_openedcustomer_actionAn app session begins.
trial_startedplan_enrollmentA free or paid trial begins.
subscription_createdplan_enrollmentA recurring plan is taken out.
customcustomAnything the standard set does not describe — and not eligible as an oCPC goal.

custom is not eligible as a Conversions-objective goal. That single line reshapes a lot of implementations, because the outcome a business actually cares about is frequently not one of the twelve. If your real outcome is a qualified demo, a completed onboarding step, a quote above a value threshold or a second purchase, the answer is not to argue with the platform — it is to fire a standard event alongside your custom one at the same moment, from the same code path, with the same identifier. The custom event carries the nuance into your own reporting; the standard event carries the optimization. If you skip the standard event, you have built a measurement system that oCPC cannot use.

Custom event names have their own rules: 1 to 64 characters, alphanumerics plus underscore and dash, starting and ending with an alphanumeric character. So demo_qualified and quote-over-5k are fine; _demo, demo_, demo qualified and demo.qualified are not. Pick the name once, write it into the runbook, and never edit it casually — the next paragraph explains what an edit costs.

Why does a firing event still report zero?

A conversion event can be firing correctly, accepted by the platform and forwarded, and still report 0 if it does not match the event configured for the campaign. This is the exact-match rule, and it has two halves. For standard events, the event type must match what you send. For custom events, Ads Manager must be set to Event type: Custom and the name must match exactly — character for character, case included. A matching display name is not enough; the label a human reads in the interface is not the string being compared.

Then comes the sentence that governs the entire sequencing of this engagement: correcting the configuration does not backfill. Fix a mismatch on the eleventh day and you have measurement from the eleventh day. The first ten days of clicks, spend and conversions are not reprocessed, not recovered and not available for a retrospective read. There is no reconciliation job, no support ticket, no export that repairs them. They are gone as measured data even though the money was real.

That property is why this work happens before spend rather than in parallel with it, and it is worth being blunt about the arithmetic. An account that launches on Monday and discovers a configuration mismatch a fortnight later has not lost a fortnight of learning — it has lost a fortnight of spend as evidence, and every optimization decision taken in that period was taken against a Conversions column reading zero. Illustratively: a mid-market pilot at $10,000 to $30,000 over the test period, sized to produce somewhere around 50 to 100 conversions, is a pilot whose entire purpose is to generate a defensible answer. Run half of it against a mismatched event and the answer is not half as good; it is unusable, because you cannot tell which half of the data is real.

What are the four gates on the Conversions objective?

The Conversions objective (oCPC) has four prerequisites, and all four must be satisfied before a campaign can use it. Cloning an existing CPC campaign into an oCPC campaign relaxes none of them.

GateWhat it requiresSymptom when it fails
1. Tracking is liveConversion tracking working through the pixel, the Conversions API, or both. The Conversions API is more reliable than the pixel alone.No eligible event to select; the objective cannot be completed.
2. A standard event existsAt least one standard event. custom events are not supported as an oCPC goal.Your real outcome is present in reporting but cannot be chosen as the goal.
3. The event belongs to this accountThe event belongs to the current ad account with an active conversion source. Events do not travel between accounts.A correctly built event in a sibling or agency-owned account is invisible here.
4. The account is enabled for conversion biddingAn account-level entitlement.403 Conversion bidding is not enabled — a hard, explicit error rather than a silent zero.

Gate four is the one worth calling out, because it is the only failure in this whole area that announces itself. A 403 Conversion bidding is not enabled is not a bug in your implementation and not something a redeploy fixes. It is an account-level state. Everything else on this page fails quietly; this one does not, which makes it the easiest problem on the list to resolve and the one that panics teams the most.

Which event should you optimize on?

Choosing the conversion event is a business decision disguised as a configuration field, and it turns on two tests that pull in opposite directions: the event has to correlate with revenue, and it has to happen often enough to be learnable. Deep events correlate beautifully and occur rarely. Shallow events occur constantly and correlate with nothing. The right answer is the deepest event that still occurs at a workable frequency, and for most accounts that is one step upstream of the outcome the finance team cares about.

Two pieces of arithmetic make the choice concrete, and both are illustrative rather than promised results. First, break-even CPC = target CPA × landing-page conversion rate. A $50 target CPA at a 4% landing-page conversion rate gives a $2.00 break-even CPC. Now set that against the delivery floor: OpenAI’s guidance is a starting maximum CPC bid of $3 to $5, and bidding much below $3 wins little or no delivery. A break-even CPC of $2.00 is therefore below the price of participation — and no event configuration fixes that. Either the landing page converts better, the average order value supports a higher CPA, or you optimize on a shallower event and accept a looser link to revenue. Second, max allowable CPA ≈ average order value × gross margin × close rate, which is the number the deep-event argument has to survive.

The frequency test is the one teams skip. An event that occurs six times a month is not a signal a bidding system can learn from at any budget you are likely to run, and configuring it as your goal converts an optimization problem into a waiting problem. In that situation the honest recommendation is to optimize on the qualified step above it — lead_created, trial_started, appointment_scheduled — and to carry the deep outcome through your own CRM reporting instead, which is exactly what the wider stack described in our guide to ChatGPT Ads conversion tracking exists to do. Deduplication between the pixel and the Conversions API uses the same identifier on both sides, and an order or transaction ID is the right choice there; timestamps, random values and session IDs do not work.

How do you prove the configuration matches before spend starts?

You prove it by comparing the string you send against the string configured on the campaign, in both directions, before the campaign is unpaused. Because corrections do not backfill, this verification is the last gate in the build rather than a check performed after launch, and it takes under an hour.

  1. Fire the event with debug: true. Read the event name out of the console rather than out of your own source code — the point is to see what actually left the page, including any transformation a tag manager applied on the way.
  2. Read the campaign configuration character by character. Open Tools → Conversions and compare. For a custom event, confirm the configuration uses Event type: Custom and that the name matches exactly; a display name that reads the same is not the same thing.
  3. Confirm the event belongs to this ad account with an active conversion source. An event built in a sibling account, an agency account or a previous advertiser account does not travel.
  4. Validate the server side separately. Send the same event through the Conversions API with validate_only: true until the payload is accepted, then send one real event and confirm both sides carry the same identifier.
  5. Run one live click, then wait. Allow 24 to 48 hours for attributed conversions to appear before drawing any conclusion. Day-one reporting looks identical whether the configuration is right or wrong, which is precisely why teams launch on a broken setup and only find out in week two.

Tag managers, and why there is no official template

There is no official OpenAI-published tag-manager template for the ChatGPT Ads pixel. Templates do circulate, and some of them are decent, but they are community-built rather than vendor-maintained — this is the observed consensus among practitioners working in the channel rather than a statement OpenAI has made. The practical consequence is ownership: a community template has no upgrade path guaranteed to you, no support contract behind it, and no commitment to track changes to the SDK. If you deploy one, you own it, and you should know that on the day you deploy it rather than the day it breaks.

It also changes what due diligence looks like. Before we deploy a community template into a client account we read it for four things: which SDK URL it loads, whether it calls init itself or assumes initialization has already happened, whether it hardcodes a pixel ID or takes one as a variable, and what it does with customer data on the page. A template that quietly reads form fields is making a privacy decision on your behalf, and that decision belongs to you. Version-pin whatever you adopt, record who maintains it and where, and put a calendar reminder against it, because the thing that breaks a community template is a change nobody told you about.

What is the pattern we implement?

The pattern we install by default is to hardcode the loader and the init call in the page head, and use the tag manager only for measure calls. The loader is https://bzrcdn.openai.com/sdk/oaiq.min.js, the global is oaiq, initialization is oaiq("init", { pixelId: "<ID>" }), and each event is oaiq("measure", "<event>", <data>, <options>). Splitting them along that line gives the engineering team deterministic control over the part that has an ordering requirement, and gives the marketing team self-service control over the part that changes often.

ComponentWhere it belongsWhat breaks if you move it into the container
SDK loader (oaiq.min.js)Hardcoded in the page headLoad timing becomes dependent on container delivery, consent categories and network conditions you do not control.
oaiq("init", …)Hardcoded in the page head, immediately after the loaderEvents can fire on pages where initialization has not run. The loss is partial and route-dependent, which is the hardest kind to reproduce.
oaiq("measure", …)Tag manager, triggered off your data layerNothing — this is the piece a tag manager handles well, and the piece marketing legitimately needs to change without a deploy.
Consent callsWherever your consent platform authoritatively resolves, before init for the initial falseConsent state and pixel state can disagree, which is worse than either failure alone.
CSP directivesServer response headersA tag manager cannot grant itself network permission. This is not moveable at all.

The half that lives in the container needs a contract to work against, and that contract is your data layer. A measure tag should read named values that your application pushes deliberately — the event name, the order or transaction ID reused as event_id, the monetary amount in minor units — rather than scraping them out of the rendered page. Scraped values are the reason tracking breaks on a template change that nobody thought was a tracking change: a developer renames a CSS class, the selector stops matching, and the conversion value silently becomes zero. A data-layer contract turns that into a code review comment instead of a quarter of bad data.

What goes wrong when the whole thing lives in the container?

Three failure classes account for nearly all of it, and they share one property: they cause partial loss rather than total loss, so the account keeps reporting conversions and nobody investigates.

None of that is exotic. The reason it is worth designing against is that intermittent measurement loss is extraordinarily hard to diagnose after the fact — there is no error to find, the diagnostics panel reports only events that were dropped before being sent, and the events that never fired at all leave no trace anywhere. Debugging is done live, with debug: true in the console, walking real routes on real devices. That is billable hours you can avoid entirely by putting two lines of JavaScript in the head.

The Content Security Policy point deserves its own sentence because a tag manager creates a false sense of exemption. All three directives are required regardless of who injects the script: script-src https://bzrcdn.openai.com, connect-src https://bzr.openai.com and img-src https://bzr.openai.com. A missing connect-src is the classic cause of “the pixel is installed but no conversions appear” — the script loads, the tag fires, the console looks fine, and the event never leaves the browser. No container setting fixes that; it is a server header.

Is running everything through a tag manager actually wrong?

No — plenty of teams run the entire implementation through a tag manager and it works. We would rather state the trade-off than pretend there is one right answer, because the correct choice depends on facts about your organisation rather than about the pixel.

Where we install the hardcoded pattern, the head snippet is small, version-controlled and reviewed like any other production code, and everything downstream of it stays in your container where your marketing team can reach it. Where a client’s constraints genuinely rule that out, we implement in the container and document the specific risks that decision accepts. What we will not do is put it all in the container and call it best practice.

The ChatGPT Ads pixel exposes an explicit consent gate, and the supported sequence is oaiq("consent", false) before initialization, then oaiq("init", { pixelId: "<ID>" }), then oaiq("consent", true) once consent has actually been given. Implemented in that order, the pixel is present on the page but transmitting nothing until your consent platform says otherwise. Implemented in any other order, you have a consent banner that decorates a pixel which was never listening to it.

Before anything else in this section, the boundary: we implement to your legal position, and we do not provide legal advice. What follows describes mechanisms — what the pixel does, when it transmits, how to prove it stopped. Which of those mechanisms your business is permitted to use, in which markets, on what legal basis, is a decision for you and your counsel. An agency that configures your tracking does not thereby own your compliance decision, and a supplier who implies that buying an implementation makes them responsible for your privacy position is overselling. We will build precisely what your policy says, we will tell you plainly what each configuration does, and we will not sign off on whether it is lawful.

Automatic Advanced Matching is a live consent question

Automatic Advanced Matching hashes customer information entered into your forms, in the browser, by default. It is the default for new Web pixels, OpenAI enabled it on existing Web pixels on 17 August 2026, and it is opt-out — taking no action is a decision to leave it on. The plaintext is not transmitted; what leaves the browser is a hash. Whether that distinction matters for your obligations is exactly the sort of question we do not answer.

What we insist on is that the answer is given deliberately rather than inherited. A setting that switched itself on across an entire population of existing pixels is, by construction, a setting most advertisers never chose. On every engagement we surface the current state of the switch on each pixel in the account, describe what it does in one paragraph a lawyer can read, and ask for a written decision before we touch anything. If the decision is to disable it, that is a per-pixel change at Tools → Conversions → Data Source → Edit pixel on each pixel individually, and the change will produce a visible step down in matched volume that needs annotating in the same way the enablement did.

Where consent engineering collides with measurement

The collision has a specific shape: a consent wall that reloads the page without the original query string strips oppref. OpenAI’s click identifier arrives appended to your landing-page URL, and the pixel captures it automatically into a first-party __oppref cookie. Strip it — with a redirect, a consent wall, a CDN rule, a geo-router, an A/B testing tool that rewrites the URL — and conversion tracking breaks entirely. Not partially. The conversions still happen; they simply cannot be credited to the ad interaction that produced them.

The engineering fix is to preserve the query string across whatever redirect your consent flow performs, so that the landing URL the pixel eventually sees still carries the parameter. That fix is trivial to write and sometimes impossible to adopt, because it means the identifier survives a page load that happened before the visitor made a consent choice. Whether that is acceptable depends on how your policy classifies the parameter and the cookie, and reasonable privacy teams land in different places. This is the point where privacy engineering and measurement genuinely conflict, and the resolution is a decision your counsel makes, not a technique we can supply. What we can supply is a clear statement of what each option costs in measurement terms, so the decision is made with the trade-off visible.

This must appear in your own privacy policy. The pixel, the cookie, the click identifier, the Conversions API if you run one, the hashing of form-entered information if Automatic Advanced Matching stays on, and any measurement partner receiving data on your behalf are all things your policy has to describe accurately, in your voice, reviewed by your people. We will give your legal team a plain-language technical description of everything we implemented, including the fields listed in the identity matching section above, and they will decide what the policy says.

One geographic complication is worth flagging early rather than discovering during a review. You can currently buy ChatGPT Ads in nine markets — the US, UK, Canada, Australia, New Zealand, Japan, South Korea, Brazil and Mexico — with 31 European countries announced. A single site therefore serves visitors sitting under several different consent regimes at once, while the advertising account itself has one country, one billing currency and one time zone, all fixed permanently at account creation. Which behaviour your site adopts for which visitor is a policy question with a technical implementation, and the order of those two words matters: your counsel writes the policy, we build to it, and we make sure the build can express more than one behaviour if the policy calls for it.

How do you verify the reject path actually suppresses transmission?

You verify by watching the network, not the cookie jar. The common false positive is a consent banner that successfully blocks a cookie while the script continues to beacon — the compliance checklist passes, the storage inspector is clean, and events are leaving the browser the whole time. The test that matters is whether requests to bzr.openai.com occur at all after a refusal.

Consent stateWhat should fireHow to verify
Before any decision — banner displayedLoader may load and init may run with consent set to false. No event transmission.Open DevTools, filter the network panel to bzr.openai.com, load the page and wait. Zero event requests. Check storage for __oppref and confirm its presence matches how your policy classifies it.
Consent grantedoaiq("consent", true) is called; subsequent measure calls transmit.Same network filter: requests appear on the events you expect. Reload with debug: true and confirm the console logs each event. Confirm the conversion appears in Ads Manager — allow the normal reporting delay before concluding anything.
Consent refusedNothing transmits for the remainder of the session, on any page.Refuse, then walk the entire conversion path to completion with a test order. Network panel filtered to bzr.openai.com must stay empty across every step, including the confirmation page. Then confirm no conversion appears against that test order ID.
Consent withdrawn mid-sessionTransmission stops from the point of withdrawal.Grant, browse, withdraw, then continue browsing with the network panel open. Any request after withdrawal is a defect in your consent wiring, not in the pixel.
Refused — server-side eventsWhatever your own code decides. The browser’s consent state does not reach your server on its own.Refuse in the browser, complete a test order, then inspect your Conversions API logs. If an event was sent, your server is not honouring consent — and only your code can make it do so.

The last row is the one that catches sophisticated teams. The Conversions API runs on your server. It is not blocked by ad blockers, it is not governed by the browser’s consent state, and it will send exactly what your code tells it to send. A consent implementation that is immaculate in the browser and absent in the backend is a consent implementation with a hole in it, and the hole is on the side that carries your most reliable events. Gating server-side transmission on a stored consent decision is application code somebody has to write, and it belongs in the scope of the build rather than in a follow-up ticket.

Test the refusal path on a real conversion, not a page load

Most consent verification stops at the homepage, because that is where the banner is. The failure modes live further in: a checkout that loads a payment provider’s script which re-injects a tag, a confirmation page served from a different template that was never wired to the consent platform, a thank-you page reached by redirect from a third-party form. Refuse consent, then buy something. It takes twenty minutes and it is the only version of this test that proves anything.

Shopify, headless, warehouses and measurement partners

Server-side conversion coverage stops being optional in three situations: the purchase completes on a hosted checkout you do not fully control, the storefront is headless and the browser context is not where the truth lives, or the conversion does not happen in a browser at all. In each case the Conversions API — or a measurement partner that writes into it on your behalf — is the mechanism that carries the event, because the JS pixel can only report what happens on a page you can put script on.

Hosted checkouts, including Shopify

A hosted checkout moves the moment of conversion onto infrastructure whose scripting surface belongs to the vendor rather than to you. Two things follow. First, your ability to place and control the pixel on the final confirmation step depends on what the platform permits, and that constraint is set by the commerce vendor rather than by OpenAI. Second, and more important, the authoritative record of what was purchased and for how much lives in the commerce platform’s order object, not in a JavaScript variable on a confirmation page. Sending the order from the server that owns it is simply more accurate than reconstructing it in a browser.

The specific trap on hosted checkouts is oppref. The pixel captures it automatically from the landing URL into the __oppref cookie, but the Conversions API does not capture it for you — you must pass it, as obref inside the user object. If the conversion is reported by your server, then your server needs the value, which means your application has to read it on the landing page, carry it through the session, attach it to the order record, and hand it to the event. That chain is the actual engineering work in an ecommerce tracking build, and it is the part that gets skipped when a team assumes an integration is doing it for them.

Running both mechanisms together is the correct configuration for a store, not a belt-and-braces indulgence: the pixel captures the click identifier and browser context automatically, the Conversions API is not blocked by ad blockers and carries the authoritative order, and deduplication resolves the overlap when both sides use the same identifier — the pixel’s event_id and the API’s id. Use the order or transaction ID. Timestamps, random values and session IDs do not work. If your catalogue also runs Product feed campaigns, note that the feed is a separate pipeline entirely — SFTP delivery, a stable filename overwritten in place, a full snapshot at least daily, removals explicit only — and it is covered in our guide to the ChatGPT product feed rather than here.

Headless storefronts

On a headless storefront the failure is almost always oppref, and it happens in the first two hundred milliseconds. The identifier arrives on the landing URL; a client-side router that rewrites or normalizes the URL before the pixel has initialized removes it before anything reads it. The pixel then works perfectly and measures traffic it cannot attribute. Nothing errors. The correct pattern is to capture the parameter as early as the application allows, persist it in your own session, and treat the __oppref cookie as a convenience rather than the source of truth — particularly where the front end and the API are on different hosts, in which case the first-party cookie is not visible to the service that will send the event and the value has to be passed explicitly.

The second headless-specific point is initialization order, which is the same argument made in the tag manager section with higher stakes: in an application that never performs a full page load after the first one, an init that runs late runs late exactly once and every subsequent event on that session is affected. Hardcoding the loader and init in the document head is not a stylistic preference on a headless build; it is the difference between a session being measurable and not.

Warehouse-side and offline conversions

The Conversions API is the only one of the three ingestion mechanisms that carries offline and CRM events at all. If your real outcome is a closed deal recorded in a CRM, a subscription that survives its first billing cycle, a booking honoured rather than made, or a return-adjusted order, the event has to be sent from a system that knows the outcome — which means from your warehouse or your CRM, not from a browser.

Two ingestion rules shape how those jobs must be built, and neither is an attribution rule. timestamp_ms must be within the last seven days and no more than ten minutes in the future. That is a freshness constraint on the payload, and it means a warehouse job running weekly against a table that lags by several days will begin emitting events that fall outside the accepted range. Batches carry up to 1,000 events, and the entire batch fails if a single event fails. Combine the two and the consequence is stark: one stale row can take down a thousand good events. Build the job to filter on timestamp range before it sends, size the batches deliberately, and use validate_only: true throughout implementation so that you are proving the payload shape without writing anything.

Which measurement partners exist?

There are four measurement partners as of August 2026, and they solve different problems. Choosing between them is a question about where your data already lives rather than about which is better. Our guide to ChatGPT Ads measurement partners goes further on selection.

PartnerWhat it doesSuits you when
HightouchSyncs events from your data warehouse into the Conversions API.Your outcomes are already modelled in a warehouse and you want the warehouse to be the source of truth.
Triple WhaleShopify-centric cross-channel reporting.You run a Shopify store and already read the day in a cross-channel dashboard.
Sonar OptimizeSignal strength.You want an external read on the quality of the signal you are sending rather than another reporting surface.
WorkMagicCross-channel view plus a Conversions API return path.You want reporting and ingestion from one connection rather than two.

The Hightouch details that decide whether it works

A Hightouch connection to the Conversions API uses sync type Events and mode insert-only, and the model must have a truly unique primary key, which is reused as the event_id. Those three settings are the whole configuration and they are also the whole risk.

insert-only means the sync is an append stream, not a reconciled table. It emits rows; it does not go back and correct what it emitted. There is no update path and no repair pass, so the primary key is doing double duty — it identifies the row for the sync and it identifies the event for deduplication. That gives you two symmetrical ways to fail. A key that repeats causes skipped events, because the second occurrence looks like something already sent. A key that changes when nothing meaningful changed causes duplicates, because the same conversion arrives twice under two identities. The second is the sneakier one: a surrogate key derived from a row hash will change when someone edits a shipping address or a back-office process rewrites a status field, and you will have inflated your conversion count with an operation nobody considered a data change. Derive the key from the order or transaction ID and keep it immutable.

How do partner integrations actually fail?

Seven failure modes account for most broken partner connections, and every one of them is preventable at build time.

Your tools will not agree, and they are not supposed to

Cross-tool conversion numbers will not match, and expecting them to match is a misunderstanding rather than a bug to be fixed. Ads Manager, your analytics, your commerce platform and your measurement partner each count a different thing under the same word. Ad clicks measure ad interactions; analytics sessions depend on page load, redirects, consent, browser blocking, UTM handling, attribution windows and time zones. Where a reconciliation is genuinely needed, do it on the same date range and the same time zone and check campaign-level and ad-level activity in the CSV export — and remember that the account’s time zone was fixed permanently at account creation, so it may not be the time zone your other tools use.

The workable discipline is to name one system of record for decisions before you look at the numbers, not after. Everything else becomes a diagnostic: useful for finding faults, not for settling arguments about performance. A tracking implementation that ships without that decision made explicitly will generate a reconciliation meeting every month for the rest of its life. It is also worth knowing that cost per conversion is not a first-class metric in every view — derive it as spend ÷ conversions and keep the formula the same everywhere it appears.

When you probably do not need this build

If you run a single hosted store on a stock theme, sell one thing, optimize on order_created, and have no CRM step between click and revenue, then the Web pixel plus a partner connector will get you a long way without an engineering engagement, and you should try that first. This work earns its fee where the conversion is not the click’s immediate consequence: multiple properties and multiple pixels, a headless front end, a hosted checkout your script cannot fully reach, offline or CRM outcomes, a warehouse that already knows the answer, or a consent regime with real teeth. If your situation is the first paragraph rather than the second, we would rather tell you that on the call than sell you an implementation you do not need.

The validation pass

The validation pass is the stage of a ChatGPT Ads tracking install where every claim made about the implementation is tested against an instrument capable of disproving it, rather than assumed from the fact that the code was deployed without errors. It exists because every failure in this measurement stack reports itself as a zero rather than as an error: a missing Content-Security-Policy directive, an event name that does not match the campaign configuration, a stripped click identifier and a genuine absence of demand all produce the same empty column. Deployment proves that code shipped. Validation proves that a conversion which happened in a browser arrived in Ads Manager attached to the right ad interaction, exactly once.

Everything below can be run by the person who did the install, on a laptop, in under two hours. The mechanical background sits in our guide to ChatGPT Ads conversion tracking; this is the proving procedure that goes on top of it.

What does the Pixel validation diagnostics panel actually tell you?

The Pixel validation diagnostics panel sits on the right-hand side of Tools → Conversions and reports why events were dropped before being sent, which field was responsible, what type of error occurred, and a recommended fix. It is the only place in Ads Manager that describes a measurement failure in words instead of as an absence, and in most accounts nobody has opened it. That is not carelessness so much as habit: the conversion column is on the campaign screen, the explanation for the conversion column is two menus away, and nothing on the campaign screen points at it.

Read it first, before you touch a network panel, because it changes what you are looking for. A panel full of dropped-field errors tells you the events are reaching the SDK and failing validation — a payload problem. A panel that is completely empty while the conversion column reads zero tells you the events are not reaching the SDK at all — a transport or firing problem, which is a different afternoon’s work. Two diagnoses, two fixes, separated in about thirty seconds.

Treat “empty” as a pass only when you have also confirmed events are arriving. An empty panel on an account with no conversions is not evidence of health; it is evidence that nothing got far enough to be diagnosed.

How do you confirm the event was actually sent, not merely queued?

Open the browser network panel, filter requests to bzr.openai.com, complete a real conversion on the live site, and confirm that a request appears and completes. This is the single most valuable check in the pass, because the pixel batches events by design — it collects them, adds source_url and timestamps, and sends them together. A queued event and a sent event look identical from inside the page; only one of them counts.

The failure this catches is a conversion that fires on a page which immediately navigates away: a form submit that posts and redirects, a checkout button that hands off to a payment processor on another domain, an app deep link. The event enters the batch, the page unloads, the batch never flushes. Nothing errors, and the diagnostics panel stays empty, because the event was never offered to the platform to be dropped.

While the request is open, read the payload. Every hashed value must be a lowercase 64-character hex string. Count the characters rather than eyeballing them — the two errors that produce well-formed-looking nonsense are uppercase hex output and hashing a value that was already hashed upstream, and both yield a string of the right shape that will never match anything. Confirm at the same time that country, city, ZIP, IP and user-agent are sent raw, and that no raw email address, phone number or user ID appears anywhere in the request.

What are the two dry-run instruments?

There are exactly two dry-run instruments in this stack: debug: true in the options argument of the pixel’s oaiq("measure", "<event>", <data>, <options>) call, which prints console confirmation of what the SDK received, and validate_only: true on a Conversions API request, which runs the batch through validation without recording anything.

Use them for different questions. debug: true answers “did my site hand the SDK the object I think it did?” — useful when a template variable is empty, a currency is missing, or a value arrives as a string. validate_only: true answers “would the platform accept this batch?” — useful because the entire batch fails if a single event fails, and a batch may carry up to 1,000 events. Validate per event during development; you do not want to discover on a Monday morning that one order with a null email killed nine hundred and ninety-nine good ones.

Both instruments are for implementation, not for production. Leaving debug: true on a live site is not dangerous, but it is a reliable sign that nobody went back.

How do you prove the consent reject path suppresses transmission?

Decline consent on your own banner, complete a conversion, and confirm that no request to bzr.openai.com appears in the network panel at all. Checking that the cookie was not written is not the same test and does not substitute for it. A cookie is storage; a request is transmission; a consent implementation can suppress the first while permitting the second, and that gap is where regulatory exposure actually lives.

The correct sequence is oaiq("consent", false), then initialize, then oaiq("consent", true) once consent is granted. Ordering matters: initializing before the deny call opens a window in which events can leave. Test the grant path in the same session, because a consent implementation that suppresses everything is compliant, useless, and very quiet about it.

How do you prove deduplication works?

Fire both the pixel and the Conversions API for one real order, wait the attribution delay, and confirm Ads Manager records one conversion rather than two. That is the only proof that counts. Reading the code and confirming that both paths reference an order ID is a review, not a test, and it will not catch the case where one path reads the ID before it exists and falls back to something else.

The requirement is that the pixel’s event_id and the API’s id carry the same identifier, and that for custom events custom_event_name matches on both sides. An order or transaction ID is the right choice. Timestamps, random values and session IDs do not work — they differ between the browser and the server by construction, so every conversion is counted twice and the account looks better than it is. Double-counting is the most flattering failure mode in this stack, which is precisely why it survives so long.

Does oppref survive the whole journey?

Walk the entire path from ad click to conversion in one sitting — every redirect, every consent gate, every CDN rule, every locale switch, every handoff to a hosted checkout — and confirm the first-party __oppref cookie is still present at the moment the conversion event fires. oppref is OpenAI’s privacy-preserving click identifier, appended to the landing-page URL and captured into that cookie by the pixel; it identifies an individual ad interaction. Stripping it at your edge breaks conversion tracking entirely, and nothing warns you.

Test the real journey, not the tidy one. The hops that strip it are almost always the ones nobody thinks of as part of the funnel: a marketing redirect that rebuilds the destination URL from a template rather than forwarding the query string, a CDN rule that normalizes URLs for cache efficiency, a consent wall that reloads the page after a choice, a trailing-slash or locale redirect, an app deep link. Server-side, remember that oppref is not captured for you — you have to read it and pass it as obref inside the user object on every Conversions API event.

Why wait 24 to 48 hours before judging the number?

Allow 24 to 48 hours for attributed conversions to appear in Ads Manager before drawing any conclusion from a conversion count. Day-one reporting is noise, and it is noise shaped exactly like a broken install — which is how teams end up rolling back a correct implementation on the afternoon they shipped it. That delay is why the matrix below is ordered the way it is: the network panel, the diagnostics panel and the two dry-run instruments all answer within seconds, so exhaust every one of them first and spend the wait confident about everything except the single thing that genuinely requires waiting.

The QA matrix

Each row names the check, the instrument that settles it, what a pass looks like, and the failure that row exists to catch. A row is not passed because it looks fine; it is passed because the named instrument was opened and produced the named result.

CheckInstrumentPass conditionCommon failure
SDK loads Browser console: type oaiq Returns a function, not undefined; network tab shows oaiq.min.js completing Tag manager container never fires on the checkout or confirmation template
Pixel ID matches The argument in oaiq("init", { pixelId: "<ID>" }) against the data source in Tools → Conversions Character-for-character identical A staging pixel left in production, or a second pixel installed by a previous vendor
CSP allows all three directives Response headers, plus the console for CSP violations script-src https://bzrcdn.openai.com, connect-src https://bzr.openai.com and img-src https://bzr.openai.com all present A missing connect-src — the script loads, the page looks perfect, events never leave the browser
Diagnostics reviewed Tools → Conversions, right-hand panel Empty, or every entry read and explained Never opened, so a dropped-field error goes unread for months
Event is sent, not queued Network panel filtered to bzr.openai.com, firing a real conversion A request appears and completes Batch never flushes because the page navigates away on submit
Event name matches the campaign The string in oaiq("measure", …) against the event configured in Tools → Conversions Exact string match; for custom events, Event type: Custom and an exactly matching name A matching display name mistaken for a match — the event fires, is accepted, and still reports zero
Hashed values well-formed Payload inspection in the network panel Every hashed field is a lowercase 64-character hex string Uppercase hex output, or a value hashed twice because it arrived pre-hashed
Raw fields sent raw, sensitive fields never raw Payload inspection Country, city, ZIP, IP and user-agent unhashed; no raw email, phone number or user ID anywhere Email hashed correctly in one field and passed raw in a debug or diagnostic field
Pixel dry run debug: true in the options argument Console prints the event with the payload you expected An empty template variable that reads as present in the code and absent in the object
Conversions API dry run validate_only: true on POST https://bzr.openai.com/v1/events?pid=<PIXEL-ID> Batch validates with no errors One malformed event failing an otherwise good batch of up to 1,000
Monetary values in the right unit Payload inspection or the validation response ISO 4217 minor units — $129.99 is 12999 Major units, reporting revenue a hundredfold low and going unnoticed because the count is right
Timestamps accepted validate_only: true response timestamp_ms within the last 7 days and no more than 10 minutes in the future Server clock drift, or a first sync that backfills historical orders
Deduplication Fire both paths on one real order, then read Ads Manager after the delay One conversion, not two event_id/id populated with a timestamp, random value or session ID
oppref captured Load a landing page carrying oppref, then inspect cookies First-party __oppref cookie is set A redirect, consent wall or CDN rule strips the parameter before the pixel sees it
oppref survives end to end Walk the full journey: click → redirect chain → consent gate → checkout Cookie still present at the moment the conversion event fires A marketing redirect that rebuilds the URL from a template instead of forwarding the query string
oppref reaches the server Conversions API payload inspection obref present inside the user object on every event Assuming the server captures it the way the pixel does — it does not
Consent reject path Decline consent, fire a conversion, watch the network panel No request to bzr.openai.com at all Cookie suppressed while the request still goes out
Consent grant path Accept consent, fire a conversion, watch the network panel Request appears and completes A consent implementation that suppresses everything, in both directions
Reported count Ads Manager conversion column, 24 to 48 hours after the test The test conversions are present and the count is what you fired Judging the install on day-one numbers and rolling back a correct implementation

An install is not finished when the code deploys. It is finished when every row above has been opened, run and signed off by name.

Reconciling with GA4, Shopify and your CRM

ChatGPT Ads, GA4, Shopify and your CRM will not report the same numbers for the same days, and the reconciliation job is to separate the gaps that are structural from the gaps that are faults. Each system is counting a different object. Ads Manager counts ad interactions and conversions credited to them. GA4 counts sessions, which require a page to load and a tag to run. Shopify counts orders. Your CRM counts whatever your sales process decided was real. Four honest systems, four different definitions, four different numbers — and none of them wrong.

The point of a reconciliation is therefore not to make the numbers agree. It is to establish, once, what the normal gap between them looks like in your account, so that a change in that gap becomes a signal you can act on. Teams that skip this step end up doing the opposite: treating every discrepancy as a crisis in the good months and as a rounding error in the bad ones.

Why do ad clicks never equal analytics sessions?

Ad clicks and analytics sessions measure different events: a click is an interaction with the ad, while a session requires the landing page to load and the analytics tag to execute. Between those two moments sit page load itself, every redirect in the chain, the consent configuration, browser and extension blocking, whether the UTM parameters survived, the attribution settings of the analytics tool and the time zones of both systems. Any one of those removes sessions without removing clicks. The gap is structural and permanent.

Two disciplines make the comparison meaningful. First, reconcile on the same date range and the same time zone. The account time zone in Ads Manager is set once at account creation and cannot be changed — it drives the reporting day boundary, and it is frequently not the time zone your analytics or your store runs on. A single day’s offset will move a Monday’s spend into Sunday and make a healthy week look broken. Second, check campaign and ad-level activity in the CSV export rather than comparing two headline totals; a whole-account gap that turns out to live in one campaign is a tagging bug, while a gap spread evenly across every campaign is the structural one.

Getting the tagging right in the first place removes most of the avoidable variance. Landing-page query parameters are set through the three-dot menu rather than the creation flow, the four macros are {campaign_id}, {ad_group_id}, {ad_id} and {ad_account_id}, and our UTM generator builds the strings so that the braces are not URL-encoded and the macros sit in values rather than keys.

Where do the reporting numbers actually come from?

Ads Manager reports impressions, clicks, spend, CTR, average CPC, average CPM and conversions at campaign, ad group and ad level, plus the optional VTA (1d) column where the account is eligible. Three details about that surface cause most reconciliation confusion, and all three are worth internalising before you open a spreadsheet.

One further rule when you build the sheet: VTA (1d) is reported separately and is not part of the Conversions total. Do not add it in during reconciliation to close a gap against Shopify. It is supplemental evidence, not performance, and folding it into a total is how a reconciliation quietly becomes a justification.

What does an acceptable variance look like?

There is no published tolerance for how far these systems may diverge, so the only workable standard is your own baseline. Reconcile in the first fortnight after the install, on aligned dates and time zones, record the ratios — clicks to sessions, Ads Manager conversions to store orders, store orders to CRM-qualified outcomes — and treat those ratios as the account’s normal. From that point on the signal is movement, not magnitude. A click-to-session ratio that sits stable for two months and then drops sharply is worth an afternoon; the same ratio sitting stable at an unflattering number is worth documenting and leaving alone.

Expect the largest and most permanent gap to sit between the platform and your store or CRM, and expect it to be structural. AdVenture Media’s analysis of ChatGPT-ad-attributable conversions found roughly 40% occurring in the immediate post-click session and roughly 60% arriving later by paths that cannot be traced back to the click. That is their figure, not ours, and it is a reason to build the wider measurement stack — platform tracking, UTMs on dedicated landing pages, a self-reported attribution question on the form, CRM outcome tracking, and holdout or lift testing — rather than a reason to distrust the platform’s number.

One dated discontinuity deserves an annotation in whatever sheet you keep: OpenAI enabled Automatic Advanced Matching on existing Web pixels on 17 August 2026. Accounts that were running before that date should expect a step change in reported volume across it, and any month-over-month comparison spanning it is comparing two different match rates. Mark the date in the sheet the way you would mark a tracking change, because that is what it was. The mechanics are in our guide to automatic advanced matching.

Discrepancy patterns and what to do about them

Discrepancy patternLikely causeAct?
Ads Manager clicks exceed GA4 sessions by a stable margin Structural: clicks are ad interactions, sessions require a page load, a surviving tag and consent No. Record the ratio as your baseline and watch it for movement
The same gap widens month over month A new redirect, a slower landing page, a changed consent banner, or UTMs dropped from one campaign Yes. Walk the journey and compare the gap campaign by campaign in the CSV
GA4 sessions exceed Ads Manager clicks Tagging, not tracking: hand-written UTMs on a URL also used in email or organic, internal traffic, or a self-referring redirect Yes. The channel is being credited with traffic it did not buy
Totals move when you change which tool you export from first Different reporting day boundaries — the Ads Manager time zone is fixed at account creation No. Align the date range and time zone, then re-run
Table-view totals do not equal the sum of the daily export Three export modes: current table view, cumulative values, and Export daily values No. Choose one export mode and reconcile only within it
Shopify orders exceed Ads Manager conversions Structural: a large share of attributable conversions arrive later by paths that carry no click identifier No, unless the gap moves. Address it with the wider measurement stack, not the pixel
Ads Manager conversions exceed Shopify orders Double counting: pixel and Conversions API firing with different deduplication identifiers Yes, urgently. This is the failure that flatters the account
A step change in conversions around 17 August 2026 Automatic Advanced Matching enabled on existing Web pixels No. Annotate the date and stop comparing across it
Revenue is right in the store and a hundredfold low in Ads Manager Monetary values sent in major units instead of ISO 4217 minor units Yes. Fix the sender; the conversion count being correct is what hid it
A conversion event you can watch fire reports zero Event-name mismatch, or the event configured after traffic started Yes. Correcting the configuration does not backfill, so the delay costs real spend
CRM shows closed business Ads Manager never recorded Offline outcomes with no return path into the platform Yes, if that outcome is what you optimize toward. Send it via the Conversions API
Two measurement partners disagree with each other and with Ads Manager Different models, different identity resolution, different definitions No. They will not agree, and they are not supposed to

Where a partner tool is in the stack, the disagreement is expected rather than diagnostic, and the partner-specific failure modes — a pixel switched off after the connection was made, mismatched event_id values, warehouse emails hashed twice, a primary key that is not truly unique, a first sync that backfills history, amounts sent in major units — are set out in our guide to ChatGPT Ads measurement partners.

Choose the system of record before you look at the numbers

Decide, in writing, which system is authoritative for conversions and which for revenue, and write down the reason. Do it before the first month closes, while nothing is riding on the answer. Choosing afterwards — picking whichever tool happens to flatter the month — is not reconciliation, it is reporting, and everyone in the room can tell the difference by the third time it happens.

Do this yourself: the full install sequence

A competent developer can install ChatGPT Ads conversion tracking correctly without hiring anyone, and a good number of the people reading this page should do exactly that. What follows is the complete sequence we work through on a paid engagement — the same order, the same menu paths, the same pass conditions — published in full rather than summarized. There is no withheld step and no secret. The engagement sells the verification and the accumulated familiarity with how this particular stack fails, not privileged knowledge of how it works.

Budget a day for a straightforward site with a single domain and a single conversion, and considerably more for a stack with a hosted checkout, multiple properties, a consent management platform and offline outcomes. You need deploy access, the ability to change response headers, admin access to Ads Manager, and someone who can place a real order and refund it. Work in order. Each group depends on the one before it, and the validation pass at the end is worthless if the groups before it were done in parallel by different people.

Group 1 — Install the pixel

  1. Read your Pixel ID from Tools → Conversions. Open Tools → Conversions and find the data source. Pass: you can read the ID and you know which property it belongs to. If there is more than one data source, establish which one the campaign will use before you write any code.
  2. Load the SDK on every page that can be part of a conversion journey. The script is https://bzrcdn.openai.com/sdk/oaiq.min.js. Pass: typing oaiq in the console on a landing page, a cart page and a confirmation page all return a function rather than undefined. Installing it only on the thank-you page is the most common shortcut and it breaks batching and identity capture.
  3. Initialize once per page load. Call oaiq("init", { pixelId: "<ID>" }) before any measure call. Pass: no duplicate init in the console, and the ID matches Tools → Conversions character for character.
  4. Decide between a tag manager and hard-coded placement, then verify the decision on the template that matters. Pass: the container or the snippet demonstrably fires on the checkout confirmation template specifically. Many containers are excluded from checkout by a theme or a platform restriction, and nobody notices until the conversions do not arrive.

Group 2 — Allowlist in your Content-Security-Policy

  1. Establish whether you send a CSP at all, and from where. Check the response headers and any Content-Security-Policy meta tag; a CDN or a security layer may add one your application code does not contain. Pass: you can name the file or the console setting that produces the header.
  2. Add all three directives. script-src https://bzrcdn.openai.com, connect-src https://bzr.openai.com and img-src https://bzr.openai.com. Pass: all three present in the live header. A missing connect-src is the classic cause of “the pixel is installed but no conversions appear”, and it fails silently.
  3. Confirm staging and production send the same policy. Pass: the header diff between environments is empty. A policy tested only on staging is a policy you have not tested.
  4. Watch the console for violations during a real journey. Pass: zero CSP violation entries from the ad click through to the confirmation page.

Group 3 — Configure the conversion event

  1. Choose from the 13 standard events. They fall into four data shapes: contents events (order_created, checkout_started, items_added, contents_viewed, page_viewed), customer-action events (lead_created, registration_completed, appointment_scheduled, app_installed, app_opened), plan-enrollment events (trial_started, subscription_created) and custom. Pass: the event you pick names a business outcome, not a page.
  2. Check oCPC eligibility before you commit. custom is not eligible as a Conversions-objective goal. Pass: if your real outcome is a custom event, you fire a standard event alongside it and point the campaign at the standard one.
  3. Configure the event in Ads Manager before traffic starts. Pass: the date the event was configured precedes the campaign start date. Correcting a mismatch later does not backfill, so every day of spend before the fix is unrecoverable.
  4. Match the string exactly. For standard events the event type must match what you send. For custom events Ads Manager must be set to Event type: Custom and the name must match exactly — a matching display name is not enough. Pass: the two strings, side by side, are identical.
  5. Send the right data shape. Pass: a contents event carries contents, and monetary values are in ISO 4217 minor units — $129.99 is 12999.

Group 4 — Set the deduplication keys

  1. Pick the identifier before you write either integration. The order or transaction ID is the right choice. Pass: it is unique, stable, and already exists at the moment both the browser and the server need it. If the ID is generated after the pixel fires, you have a sequencing problem to solve now rather than a deduplication problem to discover later.
  2. Use the same identifier as the pixel’s event_id and the API’s id. Pass: for one real order, both payloads carry the same string.
  3. Match custom_event_name on both sides for custom events. Pass: identical strings in browser and server payloads.
  4. Ban timestamps, random values and session IDs. Pass: a code search finds no Date.now(), UUID generator or session identifier feeding the deduplication key. These differ between browser and server by construction, so every conversion is counted twice.

Group 5 — Preserve oppref

  1. Inventory every hop between the ad click and the conversion. CDN rules, marketing redirects, locale and trailing-slash redirects, consent gates, hosted checkouts, app deep links. Pass: a written list, produced by walking the journey rather than by reading the routing config.
  2. Forward the full query string through every hop. Pass: oppref is still on the URL at the page where the pixel initializes. The redirect that rebuilds the destination from a template rather than forwarding parameters is the usual culprit.
  3. Confirm the cookie is written. The pixel captures oppref from the landing URL into a first-party cookie named __oppref. Pass: the cookie exists after the landing page loads, and still exists at the conversion step.
  4. Check cookie scope against where the conversion fires. Pass: the pixel that fires the conversion can read __oppref. A checkout on a different subdomain or a different registrable domain will not inherit it by accident.
  5. Carry it to the server if you use the Conversions API. Pass: obref is present inside the user object on every server-sent event. oppref is not auto-captured server-side.

Group 6 — Advanced matching and hashing

  1. Open Tools → Conversions → Data Source → Edit pixel and look at the setting on every pixel. Automatic Advanced Matching is opt-out: it is the default on new Web pixels, and OpenAI enabled it on existing Web pixels on 17 August 2026. The setting is per-pixel, so a multi-property account must be checked one pixel at a time. Pass: you have looked at each one rather than assumed the account is uniform.
  2. Run manual advanced matching alongside it. That combination is the strongest configuration. Pass: you are passing hashed identity deliberately as well as relying on the automatic layer.
  3. Normalize before hashing. Email lowercased and trimmed; phone numbers in E.164. Pass: normalization happens in one place in the codebase, not in three.
  4. Produce lowercase 64-character hex. SHA-256, output lowercase. Pass: count the characters on a real payload and confirm the case.
  5. Never hash something that is already hashed. Pass: a spot check on one known email confirms exactly one hash layer. Double-hashing produces a perfectly well-formed string that matches nothing, and it is the most common identity bug in this stack.
  6. Send raw what should be raw. Country, city, ZIP, IP and user-agent go unhashed. Pass: no raw email, phone number or user ID appears anywhere in any payload.
  7. Do not expect it to cover your server events. Automatic Advanced Matching is Web pixel only — it does nothing for Conversions API events or the image tag. Pass: your server-side identity handling is implemented explicitly.
  8. Annotate the changeover in reporting. Pass: 17 August 2026 is marked in whatever sheet or dashboard your team reads, so the step change in reported volume is not mistaken for performance.

Group 7 — Add the Conversions API where it is needed

  1. Decide honestly whether you need it. The Conversions API earns its build when ad blockers materially affect your audience, when the outcome happens offline or in a CRM, or when you need states the browser never sees — a refund, a qualified lead, a subscription that survived its first renewal. Pass: you can name the specific events the pixel cannot capture. If you cannot, the pixel alone is a defensible stopping point.
  2. Build the request. POST https://bzr.openai.com/v1/events?pid=<PIXEL-ID> with Bearer authentication. Pass: a validate_only: true call returns clean.
  3. Validate per event, then batch. Up to 1,000 events per batch, and the entire batch fails if one event fails. Pass: your sender validates each event before assembling the batch, and logs the rejected one rather than the batch.
  4. Respect the timestamp bounds. timestamp_ms must fall within the last 7 days and no more than 10 minutes in the future. Pass: NTP is running on the sending host, and no first sync attempts to backfill history.
  5. Send money in minor units. Pass: a known order reconciles exactly against the store. Major units are the quietest revenue bug here because the conversion count stays correct.
  6. Consider a measurement partner instead of a bespoke sender. Four are available: Hightouch for warehouse-to-API delivery, Triple Whale, Sonar Optimize and WorkMagic. Pass: if you use Hightouch, the sync type is Events, the mode is insert-only, and the model has a genuinely unique primary key that you reuse as the event_id. The trade-offs are in our measurement partners guide.

Group 8 — Wire consent correctly

  1. Sequence the calls. oaiq("consent", false), then initialize, then oaiq("consent", true) when consent is granted. Pass: the deny call precedes init in execution order, not just in the source file.
  2. Prove the reject path suppresses transmission, not just storage. Pass: declining consent and completing a conversion produces no request to bzr.openai.com in the network panel.
  3. Prove the grant path still works. Pass: accepting consent and completing a conversion produces a request that completes. A consent implementation that blocks everything is compliant and useless.
  4. Confirm the consent gate does not strip oppref. Pass: the __oppref cookie survives the banner interaction and any reload it triggers. Consent walls are one of the three classic places the identifier disappears.
  5. Decide explicitly what the server does when consent was refused. Your server cannot see the browser’s consent state unless you carry it there. Pass: the rule is written down and implemented, rather than left to whichever engineer touches the sender next.

Group 9 — Run the validation pass

  1. Work the QA matrix above, in order, and record the result of each row. Pass: every row opened, run and signed. A row marked “looks fine” is not a pass.
  2. Read the diagnostics panel last as well as first. Pass: Tools → Conversions, right-hand panel, is empty or every entry is explained after your test conversions have gone through.
  3. Wait 24 to 48 hours before judging any conversion count. Pass: you have not changed anything during the wait. The strongest temptation at this point is to fix something that was never broken.
  4. Reconcile against your other systems on aligned dates and time zones. Pass: you have recorded the baseline ratios described above, and named your system of record in writing.
  5. Document what you built. Pass: one page naming the pixel IDs, the events, the deduplication key, the hops that carry oppref, the consent sequence and the date of the last validation pass. The next engineer will not read the code, and if this document does not exist the whole install is re-derived from scratch in eight months.

What a checklist cannot give you

Three parts of this work resist a written procedure, and they are the parts worth paying for. Sequencing: the order above is not arbitrary, and a team that installs the pixel, wires consent and configures the event in parallel will spend longer untangling the interactions than the whole install should have taken. Judgement about which events matter: deciding that lead_created rather than registration_completed is the outcome you optimize toward is a business decision with a permanent cost attached, since a campaign objective locks at creation and a corrected event configuration does not backfill. Diagnosing intermittent loss: a conversion path that fails for one browser, one payment method or one locale produces a small persistent shortfall that no single test reproduces, and finding it is pattern recognition built from having seen the same six causes repeatedly. Hiring buys those three things and the signed verification at the end. It does not buy the list — the list is above, and you are welcome to it.

What you receive

The engagement ends with measurement that is installed, proved and documented — and with the evidence that it is proved, in writing. That last part is what separates this from a tagging job. Anyone can add a snippet. The deliverable is the proof that every event you care about actually reaches OpenAI, correctly attributed, exactly once.

The installed measurement layer. OpenAI Pixel deployed across every page that matters, the Conversions API where the business genuinely needs server-side coverage, conversion events configured to match exactly what you send, deduplication keyed on a stable identifier, and advanced matching verified rather than assumed.

A completed QA matrix. Every check, the instrument used, the pass condition, and the observed result. This is the artifact you keep. When someone asks in four months whether the tracking is trustworthy, this document answers it without anyone re-deriving the work.

A click-reference survival report. The full journey walked end to end — every redirect, consent gate, CDN rule and single-page-app transition — confirming oppref reaches your conversion event intact. This is the check that most installs skip and the one that most often explains a silent zero.

The configuration record. Pixel ID, the exact conversion event names as configured, the deduplication key and where it comes from, the CSP directives added, the consent wiring, and any partner integration with its sync mode and primary key. Short, boring, and the thing that saves a day of archaeology later.

A recorded walkthrough and a 30-day support window. Thirty to forty-five minutes showing the install and the verification, plus thirty days to come back if something behaves oddly once real traffic arrives — which is when the interesting failures surface.

What we will need from you

The ability to add a snippet to your page head and to edit your content-security-policy, read access to the ad account, and — only where server-side coverage is in scope — someone who can deploy a server-side integration. We do not ask for your CMS admin login, and the Conversions API key belongs on your server, never in our hands.

A large frosted glass plate ruled with fine parallel grooves lying on a white field, with a thick electric-blue glass disc pressed flush against it near one edge, throwing a concentrated pool of blue light beneath.
The install is the easy half. The signed-off QA matrix is what you are actually buying.

How the work runs

Eight to twelve business days for a standard single-property install, longer where server-side coverage or a measurement partner is in scope. The variable is rarely our work — it is how quickly a deploy window opens on your side.

  1. Scoping call. What you sell, what a conversion is, which systems hold the truth, and what already exists. We fix the price here, in writing, before anything starts.
  2. Discovery. We map the actual journey — every redirect, consent gate, subdomain and edge rule between an ad click and a completed conversion. Most surprises live here rather than in the code.
  3. Event design. Choosing which conversion event is configured and why, which is a business decision before a technical one. Documented and agreed before implementation.
  4. Implementation. Pixel, CSP directives, event calls, deduplication keys, advanced matching, and the Conversions API where in scope. Built against a dry run first — debug: true on the pixel and validate_only: true on the API.
  5. Validation. The QA matrix run in full, against real conversions rather than test fires, including both consent paths.
  6. Settle and confirm. We wait out the 24 to 48 hour attribution delay and confirm the numbers appear where they should before calling it done. Nothing is signed off on same-day data.
  7. Handover. Configuration record, completed QA matrix, walkthrough recording, and the 30-day window opens.

Price and scope

Conversion Tracking Implementation

From $3,999 one-off, fixed before we start

Pixel and event installation, CSP configuration, deduplication, advanced matching verification, click-reference survival testing, a completed QA matrix, configuration record, recorded walkthrough and a 30-day support window.

FactorEffect on price
One property, browser-side pixel, standard eventsAt the floor
Conversions API with a server-side deploymentAdds
A measurement partner integration — warehouse, cross-channel or a return pathAdds
Multiple properties, subdomains or regional sitesAdds per property — advanced matching is a per-pixel setting and drifts independently
Headless or single-page architectureAdds — click-reference survival is materially harder to guarantee
Offline or CRM-side conversionsAdds

No percentage of spend, no ongoing fee, and no requirement to buy anything else. If you want monitoring afterwards that is a separate conversation, and for most single-property installs we will tell you that you do not need it.

When this is the wrong purchase

Buy a tracking implementation when you know measurement is the problem, or when you are about to start spending and want the signal correct before the money moves. Several situations do not fit.

You do not know whether tracking is the problem

Then you are buying a guess. The audit establishes whether your issue is measurement, structure, hints, bidding or the offer itself, and its fee credits in full against managed service. Paying to fix the wrong layer is the most common way accounts waste a quarter.

You have no account yet

Measurement should be built as part of the account, not bolted on afterwards, because conversion-event corrections do not backfill and history lost at launch is lost permanently. Account setup and launch includes all of this work in the correct sequence.

You have a competent developer and a clear afternoon

Then follow the sequence published above. It is complete and it is the same sequence we run. The reasonable reasons to hire someone are that you do not want to spend the afternoon, that you want the QA matrix as a signed artifact, or that you have been round this loop before and would rather someone who has seen the failure modes did it. “It is secret knowledge” is not among them, which is why the knowledge is on this page.

Your spend does not justify it

Below roughly $1,500 a month in media the fee is disproportionate to what accurate measurement can save you. Install the pixel yourself, get the event name right, and revisit when the spend is large enough for precision to pay for itself.

You want perfect attribution

Nobody can sell you that here, and this is the point on which we would rather lose the sale than be misunderstood. A flawless install still leaves roughly 60% of attributable conversions arriving by paths no pixel observes. What you are buying is a trustworthy floor, not a complete picture. If your decision-making requires a complete picture, this channel is not currently able to give you one, and neither is any supplier who says otherwise.

The test we apply before quoting

If we cannot name a specific, plausible measurement fault whose correction would pay for the engagement inside a quarter, we say so and decline. On accounts spending meaningfully, that test is usually easy to pass — a single mismatched conversion event on a $10,000-a-month account costs more per month than the whole implementation.

Sources and further reading

Every technical claim on this page traces to one of the following. Where the available sources disagree — and on attribution windows in particular they currently do — we have left the number out rather than pick one, and said why.

One thing we deliberately do not state

You will find pages quoting specific ChatGPT Ads attribution windows — one-day, seven-day, twenty-eight-day, thirty-day. The published sources contradict each other, OpenAI’s help centre is not reliably reachable to automated verification, and we have not been able to confirm a single figure to the standard the rest of this page is held to. So we have left it out. The 24 to 48 hour reporting delay and the fixed one-day view-through window are both confirmed and appear throughout. If you need the click window for a specific decision, read it from your own Ads Manager rather than from anyone’s blog, including ours.

ChatGPT Ads conversion tracking FAQ

How much does ChatGPT Ads conversion tracking setup cost?

From $3,999 as a one-off, fixed in writing before the work starts. That covers a single property with browser-side pixel and standard events. Server-side Conversions API deployment, measurement partner integrations, additional properties or subdomains, headless architectures and offline conversions each add above the floor, and each is quoted before anything begins.

How long does it take to install the OpenAI pixel?

Eight to twelve business days for a standard single-property implementation, including the validation pass and the 24 to 48 hour wait for attributed conversions to appear before sign-off. The snippet itself takes minutes; the schedule is set by discovery, the deploy window on your side, and proving the install against real conversions rather than test fires.

Do I need a developer?

Someone needs to be able to add a snippet to the page head and edit your content-security-policy. For browser-side-only installs that is often a single deploy. If server-side Conversions API coverage is in scope then yes, a developer is required, because the API key must live on your server and never leave it.

Will this break my site?

The changes touch the page head, the content-security-policy and your consent wiring, so the risk is real rather than zero. We work against a dry run first — debug: true on the pixel and validate_only: true on the Conversions API — verify in a staging context where one exists, and hand over a documented rollback. We do not need your CMS admin login to do any of it.

Can I install the OpenAI pixel through Google Tag Manager?

You can, and many teams do successfully. The pattern we recommend is to hardcode the SDK loader and the init call in the page head and use the tag manager only for measure calls, because a container that loads late or reorders initialization and event calls produces intermittent loss that is extremely hard to diagnose later. There is no official OpenAI tag manager template — community templates exist — so this is practitioner consensus rather than a platform rule.

What is the difference between the pixel and the Conversions API? Do I need both?

The pixel runs in the browser and captures oppref automatically; the Conversions API runs on your server, is not affected by ad blockers, and is the only route for offline, CRM and warehouse-side conversions. Running both is the strongest configuration provided they are deduplicated on a shared identifier. Plenty of businesses genuinely only need the pixel, and we will say so rather than sell the larger scope.

How do I stop ChatGPT Ads double-counting conversions?

Use the same identifier as the pixel's event_id and the API's id, and for custom events make custom_event_name match on both sides too. An order or transaction ID is the right choice because it is stable and unique to the event. Timestamps, independently generated random values and session IDs all fail — they differ between the two paths, so nothing gets matched and every conversion is counted twice.

Why does my ChatGPT Ads conversion count not match GA4 or Shopify?

It never will exactly, and that is expected rather than a fault. Ad clicks measure ad interactions; analytics sessions depend on page load, redirects, consent settings, browser blocking, UTM handling, attribution windows and time zones. Reconcile on the same date range and the same time zone, check campaign and ad-level activity in the CSV export, and pick one system of record in writing before the first month closes rather than choosing whichever number flatters the report.

How long do ChatGPT Ads conversions take to show up?

Allow 24 to 48 hours for attributed conversions to be reflected in Ads Manager. Judging a launch or a bid change on day-one data is judging incomplete data. This delay is also the single most common reason a working install gets reported as broken — and, less often noticed, the excuse that hides a genuinely broken one.

Why do my conversions show zero even though the pixel is installed?

Most often the content-security-policy is missing connect-src https://bzr.openai.com, so the script loads and looks healthy but cannot transmit anything. The next most common causes are a conversion event whose name does not exactly match what is configured on the campaign, and oppref being stripped by a redirect, consent wall or CDN rule. All three are silent, and none of them surfaces an error in the interface.

What is oppref and why does it matter?

oppref is OpenAI's privacy-preserving click identifier. It is appended to your landing page URL on an ad click and captured by the pixel into a first-party cookie named __oppref, and it is what allows a conversion to be attributed back to an individual ad interaction. Stripping it anywhere along the journey — a marketing redirect, a link shortener, a consent gate that reloads without the query string, an edge rule that normalises URLs — breaks conversion tracking entirely.

How many conversion events can I track?

You can send multiple events, but the campaign optimizes toward one configured conversion event. There are 13 standard events across four data shapes, and custom is not eligible as an optimization goal for conversion campaigns — so if a custom event represents your real business outcome, fire a standard event alongside it and optimize toward that.

Is advanced matching GDPR compliant? Does it work with a consent banner?

Automatic Advanced Matching hashes supported form-entered customer information in the browser with SHA-256 before transmission, so raw values are not sent. Whether that processing is lawful for you is a decision for you and your counsel, not for an agency — we implement to your legal position and we do not provide legal advice. Practically, it must appear in your own privacy policy, and the pixel supports a consent sequence so nothing transmits before consent is granted. We verify the reject path actually suppresses transmission rather than just declining to set a cookie.

Is advanced matching on by default?

Yes. It is the default for newly created Web pixels, and OpenAI enabled it on existing Web pixels on 17 August 2026. It is opt-out, so taking no action means it is on, and the setting is per-pixel, which means a business with several properties must handle each one separately. Expect a step change in reported volume at the changeover and annotate the date, or you will misread it as a performance change.

What can I not measure, even with a perfect install?

There is no user-level data, no conversation content or prompts, no demographic breakdown, no search-term or per-prompt report, and no cross-session identity. Structurally, roughly 40% of attributable conversions land in the immediate post-click session and roughly 60% arrive later through paths that cannot be traced — per AdVenture Media's analysis. Someone can engage across a long conversation, never click, and convert later through branded search, and no pixel recovers that.

Do view-through conversions count toward my totals?

No. VTA (1d) is reported in its own column and is deliberately not part of the Conversions total. It does not affect CPA, post-click conversion rate, bidding or billing, and conversion-optimized bidding does not optimize toward it. The window is fixed at one day and is independent of whatever click window applies to your account. Treat it as supplemental evidence, never as performance.

Can my existing agency or developer do this?

Very possibly, and the complete install sequence is published on this page so they can. The specific traps worth knowing about are the CSP connect-src directive, exact event-name matching with no backfill on correction, deduplication key selection, and click-reference survival through redirects and consent gates. A generalist who has not met those failure modes tends to find them the expensive way.

Is this a one-time install or an ongoing cost?

One-time. There is no ongoing fee and no monitoring retainer bundled in. You get a 30-day support window after handover, which is when real traffic surfaces the interesting failures. For most single-property installs we will tell you that ongoing monitoring is unnecessary rather than sell it to you.

What do I get when the work is done?

The installed measurement layer, a completed QA matrix showing every check with its instrument and observed result, a click-reference survival report walking the full journey, a configuration record naming the pixel ID and exact event names and deduplication key, a recorded walkthrough, and 30 days of support. The QA matrix is the artifact that matters — it is what answers “can we trust this?” four months from now without anyone redoing the work.

What is the attribution window for ChatGPT Ads?

We deliberately do not publish a figure. Available third-party sources contradict each other on the click window, and we have not been able to confirm one to the standard the rest of this page is held to. What is confirmed: attributed conversions take 24 to 48 hours to appear in reporting, and the view-through window is fixed at one day. If you need the click window for a specific decision, read it from your own Ads Manager rather than from anyone's published page, ours included.

Want to know whether your tracking can be trusted?

Thirty minutes with Tarun. Bring your setup and he will tell you what is likely broken and roughly what it is costing you — and if the honest answer is that your developer can fix it in an afternoon with the sequence published on this page, he will say that instead.

Book a scoping call
Tarun Kapoor, founder of Context Hints, seated at a wooden desk with a soft city light behind him.
Tarun Kapoor
Founder & CEO, Context Hints

Twelve years of media buying across GroupM, WPP, Ogilvy & Mather, and Neil Patel Digital. Has personally owned media for Nestlé, Sage, Qualcomm, Aetna, Weight Watchers, Chubb and Novotel.