OpenAI Ads Conversions API: Complete Setup Guide
The OpenAI Ads Conversions API (CAPI) sends conversion events directly from your server to OpenAI, bypassing the browser entirely. Set it up by generating a Pixel ID and API key in Ads Manager, sending events as a POST request to
https://bzr.openai.com/v1/events?pid=<PIXEL-ID>, and validating each event type against the required data shape before you rely on it. Done correctly, CAPI recovers the conversions your JavaScript pixel misses to ad blockers, browser privacy limits, and users who close the tab early.
Why CAPI exists: what the browser pixel misses
The OpenAI Ads measurement pixel is a JavaScript SDK that fires events from the browser as visitors interact with your site. It works well, but it depends on the browser cooperating — the script has to load, execute, and finish before the user navigates away. Ad blockers, Safari's Intelligent Tracking Prevention, corporate network filters, and simple impatience (closing a confirmation page half a second too soon) all interrupt that chain.
None of that is a pixel defect. It's a structural limit of client-side JavaScript. The Conversions API sidesteps it by sending the same event from your backend — where the purchase, lead, or signup is already confirmed in your own database — directly to OpenAI's servers. No browser involved, no script to block, no tab to close early.
For the full architecture — how the pixel and CAPI fit together as one measurement system rather than competing options — see our complete guide to ChatGPT Ads tracking. This guide focuses specifically on getting CAPI installed and sending valid events.
Who needs CAPI
You need server-side CAPI, not just the browser pixel, if any of the following apply:
- You sell online and want purchase data (
order_created) to reflect what actually shipped, not just what the browser reported. - Your funnel includes steps that finish off-site or after a redirect — payment processors, third-party checkout, phone-based sales — where a browser-side script never gets the chance to fire.
- You've compared your ad platform's conversion count against your own backend order count and found a gap.
- You run any mobile app events at all:
app_installedandapp_openedare CAPI-only. There is no browser-side equivalent for either, since app installs don't happen inside a web pixel's context. - You want deduplicated, high-confidence data feeding campaign optimization instead of a partial signal.
If you're not sure which category you fall into, a tracking audit will tell you exactly where your current setup is losing events, in under 48 hours.
Prerequisites before you start
CAPI is a server-side integration, so before writing any code you need:
- Backend access. Someone who can add a server-side request to your order confirmation flow, lead form handler, or wherever your conversion events originate. This is not a script tag you paste into your homepage.
- A Pixel ID. Created in the conversions tab of OpenAI Ads Manager. The same Pixel ID is used by both the browser pixel and CAPI — it's what ties both signal sources back to the same ad account.
- A Conversions API key. Also provisioned from the conversions tab in Ads Manager, alongside the Pixel ID. This key grants write access to your event stream, so treat it like any other production secret — see the security note below before you go further.
- A list of the events you want to send, mapped to supported standard events like
order_created,lead_created, orregistration_completedwhere possible, with custom events for anything that doesn't fit a standard shape.
Step 1: Generate and secure your credentials
Log into OpenAI Ads Manager and open the conversions tab. From there you can create a Pixel ID (if you don't already have one from a browser-pixel install) and generate a Conversions API key.
Two things matter immediately once you have that key:
- It never belongs in browser-side code. The key authorizes write access to your event stream. If it ships in a JavaScript bundle, anyone who opens dev tools can read and reuse it. CAPI calls must originate from your server, full stop.
- Store it like a production secret — an environment variable or secrets manager, not a config file committed to source control, not a value hardcoded in a script that might get shared or forked.
We cover this in more depth, including how to check whether your own key is already exposed, in secure OpenAI Ads CAPI implementation. If you're setting this up for the first time, read that alongside this guide rather than after.
Step 2: Map your events to the payload schema
The full field reference lives in OpenAI's Conversions API documentation; here's the shape you'll actually build against.
Every CAPI request is a POST to:
https://bzr.openai.com/v1/events?pid=<PIXEL-ID>
with these headers:
Authorization: Bearer <API-KEY>
Content-Type: application/json
The request body always has the same top-level shape: a validate_only flag and an events array.
{
"validate_only": true,
"events": [
{
"id": "order-48213",
"type": "order_created",
"timestamp_ms": 1752600000000,
"action_source": "web",
"source_url": "https://example.com/checkout/confirmation",
"data": {
"value": 12999,
"currency": "USD",
"order_id": "48213"
}
}
]
}
A few fields deserve explanation, since getting them wrong is the most common reason events get rejected or silently miscounted:
| Field | Required? | Notes |
|---|---|---|
id | Yes | Must be unique per event. Used together with type for deduplication against the browser pixel. |
type | Yes | A standard event name (order_created, lead_created, etc.) or custom. |
timestamp_ms | Yes | Must fall within the last 7 days and no more than 10 minutes in the future. This is not built for backfilling old historical data. |
data | Yes | Must match the shape required for that specific event type — an order_created event needs an amount and currency, for example. |
custom_event_name | Only when type is custom | Also used in dedup matching for custom events specifically. |
action_source | Yes | One of web, mobile_app, offline, physical_store, phone_call, email, other. |
source_url | Required when action_source is web | The page the conversion happened on. |
oppref | No | CAPI does not auto-capture this. If you need it, read the __oppref cookie value browser-side and pass it through yourself. |
user | No | Event-scoped user data that improves match quality. |
opt_out | No | Defaults to false. |
Amounts always follow ISO 4217 minor units as integers — 12999 for $129.99, never 129.99 as a decimal. Pair every amount field with a currency code.
You can batch up to 1,000 events in a single request, but one malformed event fails the entire batch — there's no partial success. That's exactly why the validate_only flag exists: set it to true while you're building the integration, and OpenAI validates the payload shape without actually recording the event. Flip it to false only once you've confirmed your payloads pass validation cleanly.
Here's that same request as a curl call, run from a server or terminal with backend access — never from a browser console, since that would expose the key:
curl -X POST "https://bzr.openai.com/v1/events?pid=YOUR_PIXEL_ID" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"validate_only": true, "events": [{"id": "order-48213", "type": "order_created", "timestamp_ms": 1752600000000, "action_source": "web", "source_url": "https://example.com/checkout/confirmation", "data": {"value": 12999, "currency": "USD", "order_id": "48213"}}]}'
Replace YOUR_PIXEL_ID and YOUR_API_KEY with the values from your conversions tab, and swap in real values for id, timestamp_ms, and data matching your own order.
Step 3: Send a test event and confirm it in Ads Manager
Once your validate_only: true request returns without errors, the payload shape is correct — but you haven't sent a real event yet. Two things left to confirm:
- Flip
validate_onlytofalseon a single test event and send it. Use anidyou can easily recognize, liketest-capi-001, so it's unmistakable in the event stream. - Check the event stream in Ads Manager. Your test event should appear against the correct Pixel ID within a short delay. If it doesn't show up at all, work through the error patterns in Step 4 before assuming the integration is broken — most non-appearances trace back to one of a small number of causes.
Don't skip this and go straight to wiring CAPI into your live checkout flow. A payload that validates cleanly can still fail to appear in Ads Manager if the Pixel ID is wrong, the key isn't authorized for that pixel, or the timestamp falls outside the accepted window.
Step 4: Go live and monitor
Once your test event confirms correctly, wire the same request into your production flow — the order confirmation handler, the lead form submission endpoint, wherever the conversion is finalized server-side. From here, monitoring matters more than the initial setup did, because a CAPI integration that silently stops sending events is easy to miss until conversion counts look off weeks later.
The error patterns worth knowing:
| Symptom | Likely cause | Fix |
|---|---|---|
401 response | Invalid or expired API key | Regenerate the key in the conversions tab and update wherever it's stored |
403 response | Key not authorized for that Pixel ID | Confirm the key and Pixel ID pair were generated together and match the account you're sending to |
| Event rejected for timestamp | timestamp_ms outside the accepted window (older than 7 days, or more than 10 minutes ahead) | Use the actual event time, not a cached or delayed value; check server clock drift |
| Entire batch fails | One malformed event in a multi-event batch | Validate events individually with validate_only: true before batching, and log per-event validation failures rather than only the batch-level response |
| Event sent but not appearing in Ads Manager | Wrong Pixel ID in the query string, or a data shape mismatch for that event type | Re-check the pid parameter and compare your data object against the required shape for that specific type |
Set up basic alerting on non-2xx responses from your CAPI calls so a broken integration surfaces in hours, not in a monthly reporting review.
Security: never call CAPI from browser code
This is worth repeating on its own, because it's the single most common mistake teams make when adding CAPI after already having a browser pixel installed: the Conversions API is server-side only. The key in the Authorization header grants write access to your event stream. If that request runs in a browser — even once, even in a "temporary" test — the key is visible to anyone who opens developer tools.
The correct pattern is always: browser event happens → your backend receives or already knows about it → your backend calls CAPI. The browser never talks to bzr.openai.com directly for CAPI purposes; that's the pixel's job, using the public Pixel ID rather than a secret key.
We go through how to check whether your own site is already doing this wrong, how to fix it, and what key-storage practices actually hold up in production, in secure OpenAI Ads CAPI implementation. If you've already shipped a CAPI integration and haven't specifically verified it isn't browser-side, that guide is a five-minute read worth doing today.
Deduplication: making CAPI and the pixel work together
If you're running CAPI alongside the browser pixel — which is the setup OpenAI's own guidance recommends, per the measure results article — you need both signals to combine into one count, not two.
Deduplication works like this: send the same value as the pixel's event_id and CAPI's id field, for the same Pixel ID, and OpenAI treats them as a single event rather than double-counting. For custom events specifically, the custom_event_name also has to match exactly between the two payloads — a mismatched name breaks dedup even if the ID matches.
Get this wrong — a server-generated ID that doesn't match what the browser sent, for instance — and your conversion counts will inflate rather than improve, which is a confusing failure mode because it looks like "more data" when it's actually broken data. Our comparison of pixel vs. CAPI vs. a done-for-you setup covers the tradeoffs in more depth if you're deciding how much of this to build versus hand off.
Where this typically breaks
The CAPI schema itself isn't complicated. Real problems in practice usually trace back to one of: a key that ended up in client-side code, a batch that fails entirely because of one bad event buried in it, timestamps that drift out of the accepted window because of a queued job, or an event_id that isn't actually shared between browser and server. Each is fixable in isolation, but hard to catch by reading your own code — it usually looks correct until you check the actual event stream.
If you'd rather have this built and verified by someone who does it daily, our team handles the full CAPI setup — credential provisioning, event mapping, validate_only testing, and Ads Manager confirmation — so you're sending clean, deduplicated data from day one instead of debugging it after launch.
Want this done for you?
Fixed-scope setup, tested end-to-end and documented.
Explore the serviceTake the free audit