The OpenAI Ads Conversions API sends conversion events from the server, not from the browser. It delivers conversions the pixel cannot fire, that ad blockers cut off, or that happen offline directly to OpenAI1. This guide gets the server side working: the request structure, the event fields, the batch behavior, testing with validate_only, and dedup with the pixel.
The pixel’s browser-side setup and why the two layers are used together are the subject of separate posts; the focus here is the server side.
API Key
The Pixel ID and the Conversions API key are obtained from the conversions tab in Ads Manager1. The API key stays on the server; it is never leaked to client code or the browser. The Conversions API runs only on the server side.
Request Structure
Events are POSTed to the endpoint that carries the Pixel ID as the pid query parameter. Authentication is done with the Authorization: Bearer header. The request body carries two fields: validate_only and events1.
curl -X POST "https://bzr.openai.com/v1/events?pid=<PIXEL-ID>" \
-H "Authorization: Bearer <API-KEY>" \
-H "Content-Type: application/json" \
--data '{
"validate_only": false,
"events": []
}'
When validate_only: true is sent, events are validated without being saved; for real sending, false is used1.
Event Fields
Every event carries a few required, a few conditional, and a few optional fields. The data object uses the shape appropriate to its type: contents, customer_action, or plan_enrollment2.
| Field | Requirement | Description |
|---|---|---|
id | Required | Unique event identifier; used with type for dedup |
type | Required | A standard event name or custom |
timestamp_ms | Required | Milliseconds; within the last 7 days, no more than 10 minutes ahead |
data | Required | Event data appropriate to the type |
custom_event_name | Conditional | Required when type is custom |
source_url | Conditional | Required when action_source is web |
action_source | Optional | web, mobile_app, offline, physical_store, phone_call, email, other |
oppref | Optional | OpenAI privacy-preserving identifier; not auto-captured, carried manually |
user | Optional | Identity matching fields |
opt_out | Optional | true opts the event out of personalization; default false |
The timestamp_ms window is a silent trap: events older than the last 7 days or more than 10 minutes in the future are not accepted. Watch this limit when sending offline and CRM conversions late.
Full Example Event
The full event body of an order looks like this1:
{
"id": "order_12345",
"type": "order_created",
"timestamp_ms": 1773892800000,
"oppref": "oppref_abc",
"source_url": "https://shop.example.com/checkout/confirmation",
"action_source": "web",
"user": {
"email_sha256": "b4c9a289323b21a01c3e940f150eb9b8c542587f1abfd8f0e1cc1ffc5e475514",
"external_id_sha256": "73d83a078369bb4f0971b317aa7797a91cf5c0df1b62161c2e47d75c33ab5b6e",
"country": "US",
"city": "San Francisco",
"zip_code": "94107",
"ip_address": "203.0.113.1",
"user_agent": "Mozilla/5.0"
},
"data": {
"type": "contents",
"amount": 2599,
"currency": "USD",
"contents": [
{
"id": "sku_123",
"name": "Starter bundle",
"content_type": "product",
"quantity": 1
}
]
}
}
Identity Matching
All fields in the user object are optional; send only what you have. Email and external ID are hashed with SHA-256 and sent as lowercase, 64-character hexadecimal strings. Geographic data, IP, and user-agent can be sent raw; raw email, raw external ID, phone numbers, and phone hashes are never sent1. The hashing method and the GDPR/KVKK framing belong to the privacy side and will be covered in a separate post.
Batch and Failure Behavior
One request carries up to 1000 events. The critical rule: a single failing event in the batch causes the whole batch to be rejected1. This directly shapes the server-side design:
- Pre-validate with
validate_only: truebefore sending. - Build a validation layer that isolates failing events and a retry queue.
- Split large sends into chunks of 1000.
Losing the whole batch over a single event is a way conversions go missing unnoticed, which is why pre-validation is not optional.
Dedup with the Pixel
If the same conversion is sent from both the pixel and the Conversions API, the server id is kept equal to the pixel event_id and the same Pixel ID is used on both sides. For custom events, the match is made with the same custom_event_name1. How deduplication works and the common mistakes are the subject of a separate post; here, what matters is which field sets up the match on the server side.
Pixel or Conversions API?
The two are not rivals but complements. The pixel captures the live interaction in the browser; the Conversions API carries offline and server-originated conversions, unaffected by ad blockers. OpenAI describes the Conversions API as a more reliable source than the pixel and recommends using both together.
Next Steps
Once the server side works, the next step is reconciling the two layers without overlap, that is, setting up dedup correctly. The details of the event_id and id match, generating a stable identifier, and the common mistakes are the subject of the next post.
Footnotes
-
Conversions API (OpenAI Developers) — the server-side endpoint (
bzr.openai.com/v1/events?pid=), the request body (validate_only,events), event fields and requirements (id/type/timestamp_ms/data required; timestamp “within the last 7 days and no more than 10 minutes ahead”), action_source values, the 1000-event batch (“If one event in the batch fails, the full batch fails.”), the user object and hashing rules (lowercase 64-char hex; never raw email/external id/phone), the full example event, pixel/server dedup (id = event_id + same Pixel ID, custom_event_name). ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 -
Supported events (OpenAI Developers) — the standard event taxonomy and data shapes (contents/customer_action/plan_enrollment), the minor units rule, and that
currencyis required whenamountis present. ↩
- 01 The Conversions API runs server-to-server: a POST to bzr.openai.com/v1/events?pid=<PIXEL-ID> with a Bearer API key.
- 02 Every event requires id, type, timestamp_ms, and data; the timestamp must be within the last 7 days and no more than 10 minutes ahead.
- 03 If action_source is web, source_url is required; if type is custom, custom_event_name is required.
- 04 One request takes up to 1000 events; a single failing event rejects the whole batch, so pre-validate with validate_only and build a retry queue.
- 05 Dedup with the pixel: the server id equals the pixel event_id, the same Pixel ID on both sides; for custom events, the same custom_event_name.
+ How do you send a request to the OpenAI Ads Conversions API?
Events are POSTed to the bzr.openai.com/v1/events?pid=<PIXEL-ID> endpoint. The request carries an Authorization: Bearer <API-KEY> header and Content-Type: application/json; the body contains validate_only and events. The Pixel ID and API key are obtained from the conversions tab in Ads Manager.
+ Which fields are required in an event?
id (a unique event identifier), type (a standard event name or custom), timestamp_ms, and data are required. timestamp_ms must be within the last 7 days and no more than 10 minutes ahead. If action_source is web, source_url is required; if type is custom, custom_event_name is required.
+ How many events can be sent in one request?
One request accepts up to 1000 events. A single failing event in the batch causes the whole batch to be rejected, so before sending you should validate with validate_only: true and build a retry queue that isolates failing events.
+ What happens if the server and the pixel send the same conversion?
To prevent double-counting, the server id is kept equal to the pixel event_id and the same Pixel ID is used on both sides. For custom events, the match is made with the same custom_event_name. This way OpenAI counts the two records as one conversion.