The OAIQ pixel and the Conversions API measure the same conversions from two places that fail differently. The pixel runs in the browser, where it alone can capture the oppref click identifier, and where blockers, consent and closed tabs remove events. The Conversions API runs from your server, where nothing is blocked and hashed customer data improves matching. Send both with one event ID; OpenAI keeps one copy.
Two vantage points on the same sale
A conversion is one fact: a customer paid for an order. The two layers are two witnesses of that fact, and each witness sees something the other cannot.
| OAIQ pixel (browser) | Conversions API (server) | |
|---|---|---|
| Where the code runs | Visitor’s browser (on Shopify, inside a web pixel sandbox) | Your server or a connector, on a webhook |
Captures oppref | Yes, from the landing URL, stored in the __oppref cookie | Only if the browser relays it |
| Knows the page URL | Yes | Only what the webhook or the browser relay provides |
| Depends on a script executing | Yes | No |
| Depends on the tab staying open | Yes | No |
| Subject to blockers | Yes | No |
| Carries hashed customer identifiers | Not in the image request | Yes, in the user object |
| Can be retried after failure | No, the moment is gone | Yes, with the same event ID |
| Can be validated before going live | No | Yes, with validate_only |
Neither column is complete on its own. That is the whole argument, and the rest of this article is the detail behind each row.
What only the pixel can see
The click identifier
When a visitor clicks a ChatGPT ad, the landing URL carries an oppref parameter. The Measurement Pixel reference states that the pixel “captures oppref from the landing page URL, which is a privacy-preserving identifier” and “stores oppref in a first-party __oppref cookie so later page views can reuse it”. The Conversions API reference describes the same value as “An opaque, OpenAI-provided attribution identifier”.
Your server never sees that URL. The webhook that tells you an order was paid contains the order, not the landing page the customer arrived on three days earlier. So the browser is the only place where oppref can be captured, and the cookie is what carries it from the landing page to the checkout. Convrail’s pixel keeps it for 7 days and includes it in the compact copy of each event it posts to Convrail, so the server copy of the order can carry the same identifier.
The browser reference
The Conversions API user object accepts obref, described as “Opaque browser reference from the Pixel’s __obref cookie”. Again a value that exists only because a pixel ran in that browser.
The page context
source_url is “Required for web events when action_source is web”. The pixel has it for free. The server has to be told.
Where browser events get lost
None of these mechanisms produce an error you would notice. The event simply never arrives.
- The script does not load. Content blockers maintain lists of tracking domains and script URLs. A blocked script never runs, so no request is ever attempted. On Shopify, web pixels run inside a sandbox provided by the platform, which changes how the script is delivered but not the fact that a request to OpenAI’s endpoint still has to leave the browser.
- Consent is denied or arrives late. The pixel exposes
oaiq("consent", false)andoaiq("consent", true), and the reference notes it “initializes consent totrueby default unless you set it tofalse”. A store that requires opt-in consent will, correctly, never fire the pixel for visitors who decline. A consent banner that resolves after the thank-you page has already rendered can miss the order event even for visitors who accept. On Shopify, web pixels “honor the consent signals chosen by the customer” (from the web pixels overview), so the platform enforces this for you. - The tab closes first. The order event fires on the thank-you page. A customer who closes the tab, navigates away, or whose browser suspends the background request before it completes leaves no trace.
- The page never renders. Payment methods that redirect back to the store can end on a page that the pixel does not cover, or on a confirmation delivered by email rather than by a page load.
- Network conditions. Mobile connections drop. A request that times out is not retried by a pixel; the moment is gone.
We have not measured what share of orders each mechanism removes on a typical store, and we do not repeat other people’s figures here. The point is structural: every mechanism above is invisible from your side, and the server layer is immune to all of them.
What only the server can guarantee
Reliability
An orders/paid webhook arrives whether or not the customer’s browser cooperated. Your server builds the order_created event, batches it and sends it. If OpenAI returns 429 or a 5xx, you retry with backoff and the same event ID, and deduplication makes the retry harmless. If the batch fails permanently, you still hold it: Convrail keeps exhausted batches in a dead-letter queue with the HTTP status and error text, and opens a health alert instead of dropping the events.
The Conversions API also gives you room to recover. The timestamp_ms “must be within the last 7 days and no more than 10 minutes in the future”, so an outage of a few hours on your side is survivable; you replay when it ends.
Hashed matching
The user object is where the server earns its place. It accepts lists of SHA-256 hashes: emails_sha256, phone_numbers_sha256, external_ids_sha256, first_names_sha256, last_names_sha256, plus raw ip_address, user_agent, countries, regions, cities, postal_codes. These are how OpenAI matches a conversion to a ChatGPT user when the browser context is missing or ambiguous. The pixel’s image request carries none of that, and the image tag reference is explicit: “Don’t put personal data, secrets, session IDs, customer identifiers, or order identifiers in any query parameter”.
The hashing has rules (trim and lowercase emails; digits-only phone numbers of 8-15 digits after stripping the leading +, leading zeros, spaces, parentheses, periods and hyphens), and it has legal weight: a hash is pseudonymous data, not anonymous data. Both are covered in Server-side tracking without leaking personal data.
Test mode
validate_only: true makes OpenAI validate a batch “without saving them”. You can prove that your payload shape is correct before a single real conversion is at stake. There is no equivalent for the pixel.
How deduplication works
Sending both copies would double-count every order if OpenAI could not recognize them as one. The rule, from the Conversions API reference:
If you send the same conversion from the pixel and the Conversions API, reuse the same value as the API
idand pixelevent_id. Deduplication uses your Pixel ID,event_name, andid.
And: “OpenAI uses the first event it receives for a matching key and ignores later duplicates”.
Three consequences worth spelling out.
- The key has three parts. Same ID under a different Pixel ID is not a duplicate. Same ID with a different event type is not a duplicate either: an
order_createdand acheckout_startedsharing an ID are two events. Forcustomevents thecustom_event_nameis part of the identity as well. - The ID must be reproducible without coordination. The browser fires at the thank-you page; the server fires when the webhook lands, possibly seconds later, possibly after a retry an hour later. Neither can ask the other what ID it used. A value derived from the order,
order_<orderId>, is the only kind that works. Convrail uses exactly that on both layers. - First wins, so the browser copy usually wins. The thank-you page typically fires before the webhook is processed. This is fine: the browser copy has the click context. When the browser copy is lost, the server copy is the only one and it is kept. If you need the server copy’s richer
userdata to be the one OpenAI stores, you cannot force that ordering; both copies should therefore be as complete as their layer allows.
A retry is a duplicate by design. Resending a failed batch with the same IDs is safe precisely because of this rule; generating fresh IDs on retry would turn every recovered outage into inflated conversions.
The image tag: a fallback, not a third layer
OpenAI also documents a no-JavaScript option: a 1x1 image pointing at https://bzr.openai.com/v1/sdk/events?pid=<PIXEL-ID>&event=<name>&event_id=<id>&data[type]=contents&data[amount]=<minor units>&data[currency]=<ISO>, with an optional oppref parameter. It is meant, in the reference’s words, “to record page loads without executing JavaScript”.
Its limits, all from the image tag reference:
- “Each request sends one event. The image tag doesn’t batch events.”
- “A static image tag loads with the page. It can’t measure clicks, form submissions, or other interactions that happen later.”
- “The image tag doesn’t support a
userobject.” No hashed matching. - “The image tag doesn’t capture
opprefautomatically. Pass it only when your page-rendering system already has the value.”
That last point is the one that bites. An image tag on a thank-you page only carries oppref if your server rendered it into the URL, which means your server already had to receive and store it from the landing page. At that point you have built most of a server-side integration and should send the order through the Conversions API instead. The image tag remains useful where scripts cannot run at all (an email confirmation, an AMP-style page, a locked-down template), and it deduplicates the same way: “use the image tag’s event_id value for the API call’s id field” under the same Pixel ID.
Which events to send from where
| Event | Browser (pixel) | Server (Conversions API) | Reason |
|---|---|---|---|
page_viewed | Yes | No | Only the browser knows a page was viewed; no server counterpart exists |
contents_viewed (product, collection) | Yes | No | Same; low individual value, high volume |
items_added | Yes | Optional | Cart mutations exist server-side on some platforms, but the browser is the natural source |
checkout_started | Yes | Optional | The browser fires it at the right moment; a server copy adds little |
order_created | Yes | Yes, same event ID | The conversion that matters: browser for click context, server for reliability and hashed matching |
subscription_created, trial_started, registration_completed | If a page exists | Yes | Often completed outside a page view or confirmed asynchronously; the server knows for sure |
| Refunds and cancellations | No | Not supported as event types | The Conversions API has no refund event type in its documented list; handle margins in your own reporting |
The pattern: everything upper-funnel from the browser only, the money event from both, anything confirmed asynchronously from the server. The first five rows are the mapping Convrail applies on Shopify and WooCommerce, and the setup walkthrough is in How to track ChatGPT Ads conversions on Shopify.
Common mistakes
- Treating the server layer as a backup you turn on later. Deduplication only works if the IDs were designed for it from day one. Retrofitting deterministic IDs onto a pixel that has been sending random ones means a period where the two layers double-count.
- Sending the upper funnel from the server too. Ten
page_viewedcopies per session from your backend add volume without information and eat into the 1,000-event batch limit for nothing. - Forgetting the Pixel ID is part of the key. A staging pixel and a production pixel receiving the same order IDs are two separate streams; nothing deduplicates across them.
- Relaying
opprefthrough the URL of the thank-you page. Query parameters end up in logs, analytics and referrers. Relay it through a first-party request to your own endpoint, as Convrail’s pixel does. - Assuming the image tag is “the pixel without JavaScript”. It is a single, page-load-only, user-less event. It cannot replace the script for interactions or for the order.
- Judging the pixel by its Ads Manager numbers. If the pixel is your only layer, the reported count is the pixel’s count; you have no independent baseline to notice what it misses. The server layer is that baseline. Convrail’s health monitor uses it: zero browser events in 24 hours while server events keep arriving is flagged as a broken pixel.
What to do next
Set up both layers with one shared event ID from the start, and let the Convrail tracking module handle the relay, the hashing and the retries so that the two witnesses always describe the same sale.
Sources
Frequently asked questions
Is the OAIQ pixel enough to track ChatGPT Ads conversions?
It is enough to start, not enough to trust. The pixel depends on a script running and a request leaving the visitor's browser, and both fail silently; the Conversions API sends the same order from your server where nothing is blocked.
Can I use only the Conversions API and skip the pixel?
You can, but you lose the click context. The oppref attribution identifier is captured from the landing URL by the pixel; without a browser layer your server has nothing to attach to the order.
How does OpenAI deduplicate a pixel event and a Conversions API event?
On the combination of Pixel ID, event name and event ID. The first copy received is kept and later duplicates are ignored, so both copies must carry the same ID under the same Pixel ID.
Is the image tag a replacement for the JavaScript pixel?
No. It sends one event per request, loads only with the page, carries no user object and cannot capture oppref automatically. It is a fallback for environments where scripts cannot run.