Skip to content
ceaksan

Sending Server-Side Conversions with the Conversions API: An OpenAI Ads Guide

The OpenAI Ads Conversions API sends conversion events server-to-server: a POST to the bzr.openai.com/v1/events endpoint with your Pixel ID and API key. This guide shows the request structure, the event fields and their required rules, the batch behavior, testing with validate_only, and dedup with the pixel.

Jun 11, 2026 3 min read
TL;DR

The OpenAI Ads Conversions API sends conversion events server-to-server. The Pixel ID and API key are obtained from the conversions tab in Ads Manager; events are POSTed to the bzr.openai.com/v1/events?pid=<PIXEL-ID> endpoint. Every event carries id, type, timestamp_ms, and data as required; the timestamp must be within the last 7 days and no more than 10 minutes ahead. One request accepts up to 1000 events, and a single failing event in the batch rejects the whole batch, so pre-validation with validate_only matters. If the same conversion is sent from the pixel, the server id is kept equal to the pixel event_id and the same Pixel ID is used to deduplicate.

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.

FieldRequirementDescription
idRequiredUnique event identifier; used with type for dedup
typeRequiredA standard event name or custom
timestamp_msRequiredMilliseconds; within the last 7 days, no more than 10 minutes ahead
dataRequiredEvent data appropriate to the type
custom_event_nameConditionalRequired when type is custom
source_urlConditionalRequired when action_source is web
action_sourceOptionalweb, mobile_app, offline, physical_store, phone_call, email, other
opprefOptionalOpenAI privacy-preserving identifier; not auto-captured, carried manually
userOptionalIdentity matching fields
opt_outOptionaltrue 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: true before 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

  1. 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
  2. Supported events (OpenAI Developers) — the standard event taxonomy and data shapes (contents/customer_action/plan_enrollment), the minor units rule, and that currency is required when amount is present.
Key Takeaways
  • 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.
Frequently Asked Questions (FAQ)
+ 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.