Fix guide · Any website · advanced · about 60 min
OpenAI Conversions API deduplication: match the pixel event_id
For the findings “OpenAI server events have no event ID” and “OpenAI server events carry a different event ID”.
What this means
Your site sends its OpenAI events twice, on purpose: once from the browser through the OpenAI Pixel, and once more to your own server, which forwards them to OpenAI through the Conversions API. OpenAI suggests using both, to combine browser activity with conversions your backend confirms, as long as both copies can be recognized as one.
The server copy we saw does not carry the browser event’s ID:
- No event ID: the request to your server has the event name but no ID at all.
- A different event ID: the request carries an ID, but not the one the browser event had.
OpenAI treats two events as the same conversion only when the Pixel ID, the event name and the event ID all match: event_id in the pixel, id in the Conversions API. Without that, each conversion may be counted twice.
We report this as possible: we can see the request from the browser to your server, but never the request from your server to OpenAI, so check the server side yourself.
Why it costs you money:
- Purchases and leads count twice in ChatGPT Ads, so reported results and return on ad spend look better than they are.
- Conversion-optimized campaigns learn from inflated numbers and optimize toward the wrong results.
- When someone later “fixes” the double counting by switching one side off, you lose the reason you added the server copy.
How to confirm it yourself
- Open your page in a private (incognito) window, open developer tools (F12) and go to the Network tab. Accept cookies and trigger the conversion (for example, submit a test lead).
- Filter by
bzr.openai.com, open the pixel request and note the event’stypeandidin the payload. - Clear the filter and find the request your site sends to its own server at the same moment: a form submission, an
/api/...call, or a request to your server-side tag manager address. Open its payload. - Compare: does it contain the same ID as the pixel event? If there is no ID, or a different one, the two copies cannot be matched.
- Check your server code (or your server-side tag) for what it sends to
bzr.openai.com/v1/eventsas each event’sid.
A frequent surprise: the browser id is a random UUID that nobody chose. That means your code calls oaiq("measure", ...) without an event_id, and the pixel filled in a value of its own (current SDK behaviour; OpenAI’s docs only describe the event_id you set). The server cannot know it, so the copies will never match until the browser sends an ID of yours (see “OpenAI events have no event ID”).
How to fix
The rule: one action, one ID, created in one place and used by both copies.
-
Pick where the ID is created.
- For purchases, the order number is ideal: the confirmation page can use it in the browser, and your backend already has it.
- For leads, page views and clicks, create a random ID in the browser when the action happens (for example
crypto.randomUUID()), or create it on your server when it renders the page or processes the form, and hand it to the browser.
-
Pass it to the pixel in the fourth argument:
const eventId = crypto.randomUUID(); oaiq("measure", "lead_created", { type: "customer_action" }, { event_id: eventId }); -
Send the same value to your server with the request it already receives: a hidden form field, a property in your
/api/...call, or an event parameter in your server-side tag manager feed. -
Use it as
idin the Conversions API event, with the same eventtypeand the same Pixel ID (pid) as the browser event:{ "events": [{ "id": "4b1f0c6e-2f7d-4f6b-9a51-2d3c8e7f9a10", "type": "lead_created", "timestamp_ms": 1792771200000, "action_source": "web", "source_url": "https://example.com/contact", "data": { "type": "customer_action" } }] }For custom events, both copies also need the same
custom_event_name. -
Do not create a second ID on the server. A server that makes its own random ID, prefixes it (
srv-…), or uses a different value (for example the database row ID while the browser used the order number) produces a mismatch even though both copies “have an ID”. -
With server-side Google Tag Manager: generate the ID once with a variable such as Stape’s Unique Event ID, put it into the web tag’s server-side tracking settings, and map the same value to Event ID in the server tag (Stape’s OpenAI Ads Events API tag). Check both containers in Preview mode: the web tag and the server tag must show the same value for the same event.
-
Forward the click and browser references too. While you are in the server code: OpenAI asks you to pass the ad click ID as
opprefand the__obrefcookie value asuser.obref, unchanged, when available and allowed by the visitor’s consent. They improve attribution but do not replace the event ID. -
Publish and run a test conversion.
Common causes
- The browser calls
measurewithoutevent_id, so the pixel uses a random ID the server never sees. - The server-side code was added later and never received the ID from the browser.
- Browser and server each generate their own random ID.
- The server sends a different identifier (database ID, a prefixed value, the order number while the browser used a random value).
- The server event uses another event name (for example
purchaseor a custom name) than the browser’sorder_created. - In server-side GTM, the web tag and the server tag read different variables.
How to verify the fix
- For one test conversion, the pixel event’s
id, the ID in the request to your server and theidyour server sends to OpenAI are identical, and so are the event names. - With access to the Advertiser API, OpenAI’s recent-events endpoint (
GET /v1/conversions/events?pid=<PIXEL-ID>) shows a sample of the last roughly 15 minutes, withpixel_sdkfor browser events andserver_to_serverfor server events. - Re-run the check on IsPixelWorking. The finding should be gone.
Sources
- OpenAI Developers, Conversions API (event
id; deduplication on Pixel ID, event name andid; first event wins;opprefanduser.obref): https://developers.openai.com/ads/conversions-api - OpenAI Developers, Measurement Pixel (
event_idoption, deduplicating browser and server events): https://developers.openai.com/ads/measurement-pixel - OpenAI Developers, Conversion Tracking (use the same Pixel ID, event name and event ID through both integrations; Monitoring Events): https://developers.openai.com/ads/conversion-tracking
- Stape, OpenAI Ads Events API tag (Unique Event ID variable, Event ID mapping between web and server containers): https://stape.io/helpdesk/documentation/openai-ads-events-api-tag
More OpenAI guides
- OpenAI Pixel fires before cookie consent: how to fix it
- OpenAI Pixel not firing after cookie consent: how to fix it
- Repeated OpenAI event IDs drop ChatGPT Ads conversions
- Duplicate OpenAI Pixel events: how to find and remove them
- OpenAI Pixel event_id missing: fix ChatGPT Ads deduplication
- ChatGPT Ads click ID (oppref) lost after an ad click
- OpenAI Pixel tracks a button on desktop but not on mobile
- Two OpenAI Pixel IDs on one site: when it's fine, how to fix
- OpenAI Ads Pixel setup: install the ChatGPT Measurement Pixel
- How to set up the OpenAI Conversions API for ChatGPT Ads