pixellint

Blog · OpenAI and Meta

OpenAI CAPI vs Meta CAPI wants order_created and timestamp_ms, not Purchase.

OpenAI CAPI vs Meta CAPI is a Graph Purchase pointed at bzr.openai.com. Meta posts data[] with event_name, event_time as ten-digit Unix seconds, action_source website, and user_data. The pixel id is in the Graph path. Purchase needs value and currency. OpenAI Ads posts events[] to bzr.openai.com. Each event has id, type, and timestamp_ms. The pixel id rides on the query as pid. Auth is a Conversions API key for that host, not the Advertiser API key used on api.ads.openai.com for setup.

type is order_created, lead_created, registration_completed, subscription_created, trial_started, checkout_started, contents_viewed, items_added, page_viewed, appointment_scheduled, or custom. app_installed and app_opened are CAPI-only and need action_source mobile_app. They are not on the JavaScript pixel. Purchase is not in the list. A shared enum named Purchase is a standard event on Meta and a miss on OpenAI.

timestamp_ms is Unix milliseconds. It has to be within the last seven days and no more than ten minutes in the future. Date.now() is already that unit. Math.floor(Date.now() / 1000) is the Meta helper. Those ten digits as timestamp_ms are not this decade. Meta reads thirteen digits as seconds and rejects them as far future. The same integer cannot be sent to both fields.

oppref is not fbclid, and the pixel will not capture it for the API

OpenAI appends oppref to the landing URL after an ad click in ChatGPT. The JavaScript pixel can store it in a first-party cookie. The Conversions API does not capture oppref for you. If the thank-you host is not the landing host, the cookie is gone unless you copied the value through the redirect. Server events without oppref have nothing to join for click-through.

The browser cookie for that reference is __obref, sent as user.obref, not hashed. fbclid and _fbc do not fill it. Do not hash oppref because a Meta helper hashed em. user.emails_sha256 is the hashed email list, after lowercase and trim. user.ip_address stays plaintext. Meta's client_ip_address is the same rule under another name.

View-through on web is a fixed one-day window after an eligible impression, and Ads Manager keeps it as its own campaign metric. Conversions stays the click-through total. CPA, post-click conversion rate, bidding, billing, and conversion optimization stay click-through. A report that adds the view-through column into Conversions will not match the API, which never saw oppref on those rows.

website is not web, and 42.00 is not 4200

action_source for an OpenAI web event is web. Meta uses website. source_url is required when action_source is web, and it has to be HTTPS with a scheme and host. That field is not event_source_url. Copying the Meta key leaves the page URL unread.

amount is an integer in the currency's minor unit. 4200 is 42.00 USD. currency is a three-letter ISO 4217 code. Meta custom_data.value is the major-unit number you already show on a receipt. Sending 42.00 as an OpenAI amount under-counts by a hundred if the API reads a number, or fails the type if it requires an integer. Do not share one value field without a unit conversion at the edge.

Batches can hold up to 1,000 events. If one event fails, the full batch fails. A mixed batch of Meta-shaped rows and one valid OpenAI event is a full miss. Meta's batch fails the same way when one event_time is outside seven days. Keep each vendor's array pure. id should be the id the pixel already used. A new UUID on the server is a second conversion, the same dedup miss as Meta event_id.

The Graph body on the wrong host

validate_only checks the OpenAI body without saving it. A 200 there is not an Ads Manager conversion.

meta:   data[].event_name Purchase, event_time 1759010400, action_source website
openai: events[].type order_created, timestamp_ms 1759010400000, action_source web, oppref from the landing URL

validate_only on the request validates events without saving them. A 200 from that path is not a row in Ads Manager. A 200 from the live path is still not a join. The join is oppref, or another match key OpenAI can use, inside the window the product bills on. Meta test_event_code is the same class of split: the test tab moves and production does not.

Pixellint is not affiliated with OpenAI or Meta. The existing ChatGPT ads post is the product announcement. This page is the copy: which Graph fields die on bzr.openai.com. Lint the events[] you actually POST. Do not lint the Meta fixture and change the host in the client.

Two keys, two hosts, and a pixel that is not Graph

Setup calls for the advertiser account go to api.ads.openai.com with the Advertiser API key. Conversion posts go to bzr.openai.com with the Conversions API key. A worker that reuses the setup key on the conversion host, or the Graph access token on either, fails auth before the body is read. The pixel id is the pid query parameter, not a path segment the way Meta places the pixel id.

The JavaScript pixel and the API are not the same surface. The pixel can capture oppref into __obref. The API expects you to send user.obref yourself. app_installed and app_opened never appear on that pixel. If the only test you run is a browser tag, those two types stay unproven. Send them from the server with action_source mobile_app, or do not claim the app lifecycle is wired.

Meta dedup is event_name plus event_id plus the pixel id, and the first arrival keeps the value for about 48 hours. OpenAI wants the same id on the pixel and on events[].id. A GTM variable that mints a UUID per read gives the pixel and the server two ids. One order then exists twice, on whichever vendor accepted its own shape.

Keep the seven-day window on the original instant

Both vendors refuse a clock older than about seven days, in different units. Do not refresh timestamp_ms or event_time to now so a late webhook passes. That attributes the order to the upload. Drop the copy that is outside the window. Retries of a timely event reuse the original milliseconds on OpenAI and the original seconds on Meta.

Ten minutes into the future is the OpenAI cap on timestamp_ms. A client clock set ahead can clear Meta's looser future skew and fail OpenAI. Do not stamp the event with the worker's local time if the order time is what you stored. Convert that stored instant at the edge.

Pixellint can check the OpenAI envelope and the Meta envelope as two fixtures. It cannot see Ads Manager, and it cannot see whether oppref survived the redirect. Capture the landing query in your session store before you judge match rate.

What does not transfer

Sources

Contract pages

The dated argument is above. These pages are the field lists.

Read OpenAI CAPI against Meta Docs