Missing OpenAI Ads Purchase Events: How to Find and Recover Data Loss
If your backend shows more completed orders than OpenAI Ads shows
order_createdevents for the same date range, you have a measurement gap — not necessarily a sales problem. Quantify it precisely first, then work through causes in order of likelihood: browser-side blocking and thank-you-page fragility explain most of the loss, and the durable fix is sendingorder_createdserver-side through the Conversions API with proper deduplication, triggered by your actual order-creation event instead of a page render.
Either your ads are underperforming and the numbers are honest, or your ads are working and your measurement is lying to you — you can't tell which from inside Ads Manager alone. Below is the process for finding out, the seven causes behind this specific gap, and how to build a setup where it stops happening.
Step 1: Measure the gap correctly before you touch anything
Get an honest number before changing any code. Pull two figures for the same date range and the same timezone:
- Backend confirmed orders — your source of truth, from your store, order management system, or payment processor.
order_createdevents in Ads Manager — for the identical window.
Get these details right, or the comparison produces a number that looks alarming but isn't real:
- Match timezones exactly. Local time vs. UTC can shift orders across the date boundary and manufacture a gap.
- Compare total orders, not just ad-attributed orders. The failure modes below (ad blockers, redirect fragility, script errors) affect all traffic hitting your thank-you page, not only visitors from an OpenAI Ads campaign. Isolating measurement from attribution keeps you solving one problem at a time.
- Let the data settle. Both server-side and pixel events can show up with some delay. A same-hour snapshot looks worse than settled next-day totals.
- Look at more than one date range. A single day can be thrown off by a traffic spike or a one-off deploy; week-over-week shows whether the gap is structural or occasional.
There's no universal "normal" gap percentage to benchmark against — it depends on your device mix, browser mix, checkout flow, and payment provider. What matters is whether your gap is stable and small, or large and persistent. A consistent, structural gap is the pattern this guide addresses.
The seven causes of missing order_created events
Work through these roughly in order of how often they matter.
1. Ad blockers and privacy browsers
Ad blockers, tracking-protection extensions, and privacy-hardened browsers can block requests to bzrcdn.openai.com (where the pixel SDK loads) or bzr.openai.com (where events send). The purchase is real and checkout completed normally — the browser simply never delivered the signal.
Detection: Test checkout with a common ad blocker enabled and watch the Network tab for a failed pixel load or a measure call that never leaves the browser.
Fix: Don't rely on the pixel alone for your primary conversion event. A server-side order_created via the Conversions API doesn't depend on the browser reaching OpenAI's domains at all.
2. The thank-you page never renders
If order_created only fires when a browser loads your confirmation page, anything that prevents that page from rendering means the event never fires — even though the order exists. Most common version: checkout redirects to an external payment provider, and the return redirect fails, times out, or the customer closes the tab after paying but before returning. The order is created and paid. The thank-you page is never seen.
Detection: Check your payment provider's dashboard for orders marked paid, and cross-reference against sessions that reached your confirmation page URL in analytics. A gap between "paid" and "reached confirmation" is this exact mechanism.
Fix: Trigger order_created from the order being created in your system — a webhook, a database write, an order-confirmed callback — not from a page rendering in a browser.
3. SPA checkouts that never trigger a route-based listener
If checkout is a single-page app updating the confirmation view via client-side routing, and your event call is wired to a page-load or route-change listener, state updates that don't fire that listener silently skip the tracking call — while showing a normal "thank you" screen.
Detection: Watch the Network tab during an SPA checkout completion, not just initial load. UI updates with no request to bzr.openai.com confirms this.
Fix: Fire measure explicitly from the checkout-completion logic — the code path that runs when the order API call succeeds — not from a generic page-view hook.
4. Consent declines
If the pixel is gated behind a consent banner, visitors who decline (or never interact before completing checkout) won't have the pixel loaded when they reach the confirmation page.
Detection: Check your consent platform's decline/no-interaction rate for checkout sessions. A meaningful rate means part of your gap is explained by design, not a bug.
Fix: Respecting a decline is correct behavior, not something to route around — but a consent-gated pixel-only setup will structurally show fewer orders than your backend. Server-side events face the same consent constraints when tied to the same state, so CAPI doesn't quietly bypass a visitor's choice.
5. Script errors and theme or template changes
A JavaScript error earlier on the confirmation page — an unrelated script, a theme update, a new checkout app — can halt execution before your order_created call runs.
Detection: Check the browser console on the confirmation page across a few real orders, and re-check after any checkout template, app, or theme change — that's when this breakage typically gets introduced.
Fix: Load your tracking call so it isn't dependent on unrelated scripts succeeding, and re-test after any checkout-adjacent deploy — exactly what the monitoring routine below is meant to catch.
6. Payment providers that confirm the order after the browser has left
Some payment methods don't confirm synchronously — the order might show "pending" on the confirmation page and only flip to "paid" via a webhook minutes or hours later (common with bank transfers, buy-now-pay-later, async fraud review). If order_created only fires at page-load time, you either fire too early or never fire at all.
Detection: Compare your provider's "confirmed" timestamp to the order-creation timestamp for these payment methods specifically. A meaningful delay plus page-load-time tracking is this mechanism.
Fix: Tie order_created to the moment the order is actually confirmed — the webhook your payment provider sends — not to a browser looking at a page. This is naturally a server-side event, since the original browser session may be long gone by confirmation time.
7. Double redirects that strip session or query parameters
Multi-hop redirect chains (checkout → payment gateway → your domain → confirmation, sometimes with extra hops for 3D Secure or fraud checks) can strip query parameters, session identifiers, or __oppref cookie context along the way. If tracking logic depends on something carried through that chain, a broken hop means the confirmation page loads but the event fires with incomplete data — or fails outright.
Detection: Manually trace checkout through every redirect hop in the network panel using the actual payment methods your checkout supports, not just a simplified card test.
Fix: Minimize reliance on data passed through the redirect chain. If your logic can look up what it needs (order ID, amount, currency, contents) directly from your backend at the point of firing, this failure mode mostly disappears.
The recovery architecture: server-side order_created as the source of truth
The pattern across most of these seven causes is the same: anything tied to a browser rendering a specific page, at a specific moment, with no errors, on a network that doesn't block OpenAI's domains, is a fragile foundation for your single most important conversion event. The durable fix is architectural:
- Trigger from your backend, not the browser. Hook
order_createdto the event in your own system that represents "this order is real and paid" — a webhook, a payment-confirmed callback, or equivalent. This removes causes 2, 3, 6, and 7 entirely, since none can interfere with a server-to-server call.
- Send it via the Conversions API. Per the Conversions API reference, this is a server-only
POSTtohttps://bzr.openai.com/v1/events?pid=PIXEL-ID, authenticated with a Bearer API key. Each event requiresid,type,timestamp_ms, and adataobject matching the event's shape. Fororder_created,datauses thecontentsshape:type: "contents", anamount(ISO 4217 minor units, an integer), acurrencycode, and an optionalcontentsarray of purchased items.
- Use the order ID as your event
id. This makes retries safe: a webhook retry sending the sameidagain doesn't create a duplicate — matchingid+typeis treated as one event.
- Deduplicate against any surviving pixel event. If pixel-side
order_createdstill fires for some orders, reuse the sameidas both the CAPI event'sidand the pixel event'sevent_id, under the same Pixel ID, and the two get deduplicated. For a custom event,custom_event_namemust match on both sides too — see the supported events reference.
- Pass
opprefexplicitly if you have it. The pixel captures the__opprefcookie automatically (roughly 30 days), but the Conversions API doesn't capture it for you — read it from the session and include it yourself.
A minimal payload shape for a single event in a CAPI request:
{
"id": "order_48213",
"type": "order_created",
"timestamp_ms": 1754812800000,
"action_source": "web",
"source_url": "https://shop.example.com/checkout/confirmation",
"data": {
"type": "contents",
"amount": 8998,
"currency": "USD",
"contents": [
{ "id": "SKU-1001", "quantity": 1 }
]
}
}
source_url is required whenever action_source is web. If you also fire the pixel-side event for the same order, give the pixel call an event_id equal to this id so the two get deduplicated instead of double-counted.
If you haven't set up the Conversions API yet, the Conversions API setup guide covers the full implementation from provisioning your API key onward.
What you can and can't backfill retroactively
Once the server-side implementation is live, the natural next question is whether you can go back and submit orders you already know you lost.
The real constraint: the Conversions API requires timestamp_ms to be within the last 7 days for each event.
- Orders from within the past 7 days that you can identify as missing can be submitted now, using their real order timestamp, and Ads Manager will accept them within that window.
- Orders older than 7 days cannot be submitted after the fact. There's no exception path — it's a hard boundary in the event structure, not a soft cap.
So the backfill opportunity is real but narrow. If you've just identified a gap going back three months, you can recover the most recent week of it and nothing before that. This is why monitoring matters more than the backfill — a one-time recovery doesn't stop the gap from reopening next month.
Monitoring so this doesn't silently regress
A tracking fix that isn't monitored tends to quietly break again — a theme update touches the confirmation page, a new checkout app gets installed, a provider changes its redirect behavior — and by the time anyone notices, weeks of orders are gone with no backfill option left.
A lightweight routine that catches this early:
- Weekly backend-vs-Ads-Manager check. The same comparison from Step 1, run on a schedule. A gap that was stable and small for months and suddenly widens is your earliest, most reliable signal.
- Spot-check with the Pixel Helper extension after any change to checkout, theme, or payment configuration — walk through a real or test purchase and confirm
order_createdfires with the data you expect. - Watch your payment provider's webhook delivery logs if CAPI depends on a webhook — a provider-side failure silently stops server-side events upstream of anything Ads Manager can tell you.
- Re-run the seven-cause checklist any time the gap widens, rather than assuming last time's cause. A checkout redesign is more likely to introduce cause 3 (SPA routing) than cause 1 (ad blockers).
Full detail on what correctly arriving events look like end-to-end is in Measure results with OpenAI Ads.
Where to go from here
If you're not sure how large your gap is, or which cause applies to your checkout, a tracking audit finds the actual leak within 48 hours, with the fee credited toward implementation if you move forward. If you already know you need the server-side fix, full-funnel ecommerce tracking builds the CAPI implementation, deduplication, and monitoring above so backend orders and Ads Manager numbers hold together — not just for one recovered week.
Want this done for you?
Fixed-scope setup, tested end-to-end and documented.
Explore the serviceTake the free audit