Setting up the OpenAI Ads pixel takes three steps:
- Add the snippet to the page
- Define the Pixel ID with
oaiq("init") - Call
oaiq("measure")when a conversion happens1
This guide shows each step with code examples. The pixel’s place in the measurement architecture and how it reconciles with the server side are the subject of a separate post; the focus here is getting the browser side working.
Step One: Add the Snippet to the Page
The snippet is added to the <head> of every page where conversions are captured, as high up as possible. The goal is to prevent early conversions from escaping before the pixel loads1. You get the Pixel ID from the conversions tab in Ads Manager. Of course, the Ads Manager is only available in the countries where access is offered. It is worth following OpenAI’s announcements.
<script>
(function (w, d, s, u) {
if (w.oaiq) return;
var q = function () {
q.q.push(arguments);
};
q.q = [];
w.oaiq = q;
var js = d.createElement(s);
js.async = true;
js.src = u;
var f = d.getElementsByTagName(s)[0];
f.parentNode.insertBefore(js, f);
})(window, document, "script", "https://bzrcdn.openai.com/sdk/oaiq.min.js");
oaiq("init", { pixelId: "<PIXEL-ID>" });
</script>
While testing, adding debug: true to the init object logs SDK activity to the console and makes verifying the setup easier1.
Step Two: Add User Data with init
User matching fields are request-scoped: they go into the init call, not into individual measure calls. All fields are optional; send only what you have. Email and external ID are sent hashed, not raw, with SHA-256; raw email, raw external ID, and phone numbers are never sent1.
oaiq("init", {
pixelId: "<PIXEL-ID>",
user: {
email_sha256: "b4c9a289...",
external_id_sha256: "73d83a07...",
country: "US",
city: "San Francisco",
zip_code: "94107",
},
});
If user data arrives later, for example after login, init is called again with the full user object; pixelId can be omitted after the first init1. The hashing method, the oppref identifier, and the GDPR/KVKK framing belong to the privacy side and will be covered in a separate post.
Step Three: Send Conversions with measure
When a conversion happens, measure is called. The argument order is: the command ("measure"), the event name, the event data, and an optional options object1. The data object’s type field matches the event shape: content and order events use contents, lead and registration events use customer_action, and subscription and trial events use plan_enrollment2.
| Event | Data type (type) | When |
|---|---|---|
page_viewed | contents | An important page is viewed |
contents_viewed | contents | A product, content, or item is viewed |
items_added | contents | An item is added to cart or selection |
order_created | contents | A purchase is completed |
lead_created | customer_action | A lead form is submitted |
registration_completed | customer_action | Registration is completed |
appointment_scheduled | customer_action | A meeting or demo is scheduled |
subscription_created | plan_enrollment | A paid subscription starts |
trial_started | plan_enrollment | A free trial starts |
// Page and content views
oaiq("measure", "page_viewed", {
type: "contents",
contents: [{ id: "pricing", name: "Pricing page", content_type: "page" }],
});
oaiq("measure", "contents_viewed", {
type: "contents",
contents: [
{ id: "sku_123", name: "Starter bundle", content_type: "product" },
],
});
// Commerce events (contents shape)
oaiq("measure", "items_added", {
type: "contents",
amount: 2599,
currency: "USD",
contents: [
{
id: "sku_123",
name: "Starter bundle",
content_type: "product",
quantity: 1,
amount: 2599,
currency: "USD",
},
],
});
oaiq("measure", "order_created", {
type: "contents",
amount: 2599,
currency: "USD",
contents: [
{
id: "sku_123",
name: "Starter bundle",
content_type: "product",
quantity: 1,
},
],
});
// Lead and registration (customer_action shape)
oaiq("measure", "lead_created", { type: "customer_action" });
// Subscription and trial (plan_enrollment shape)
oaiq("measure", "subscription_created", {
type: "plan_enrollment",
plan_id: "pro_monthly",
amount: 2000,
currency: "USD",
});
oaiq("measure", "trial_started", {
type: "plan_enrollment",
plan_id: "pro_trial",
});
Monetary value is always an integer in ISO 4217 minor units; if amount is present, currency is required. For example, $25.99 is sent as 25992.
Custom Event
For an action not in the taxonomy, custom is used and custom_event_name is added to the options object1.
oaiq(
"measure",
"custom",
{ type: "custom" },
{ custom_event_name: "quote_requested" },
);
Custom name rules: 1-64 characters; letters, numbers, underscores, dashes; starts and ends with a letter or number; cannot reuse a standard event name; lowercase is recommended for consistency1.
Bridging to the Server Side with event_id
If the same conversion will be sent from both the pixel and the server Conversions API, to prevent double-counting you pass options.event_id on the pixel side and keep this value equal to the id on the server; the same Pixel ID is used on both1.
oaiq(
"measure",
"order_created",
{ type: "contents", amount: 2599, currency: "USD" },
{ event_id: "order_12345" },
);
Here event_id only sets up the match; how deduplication works and how the server side is set up are the subject of separate posts.
What the SDK Handles Automatically
After setup, the pixel handles a few things on its own1:
- Captures the
opprefidentifier from the landing page URL. - Stores this identifier in a first-party
__opprefcookie and reuses it on later page views. - Adds the current page origin as
source_url. - Timestamps each event and batches closely grouped
measurecalls.
Thanks to these automatic behaviors, there is no need to write extra code for post-click identity tracking and batched sending.
Testing and Common Mistakes
A few things to watch while verifying the setup1:
- Keep
debug: trueon during testing and watch the console logs. - Send
amountandquantityas integers, not text. - Put only documented fields inside
contents[]. - Always use the pixel in the browser; do not call the server Conversions API from page code, call it from the server.
Is the pixel enough on its own?
The browser pixel is the first step of the setup, but not the whole of measurement on its own. Ad blockers, consent refusal, and cookie restrictions thin out client-side data. Sending critical conversions also from the server Conversions API closes that gap.
Next Steps
Once the browser side works, the next steps are sending critical conversions from the server too and reconciling the two layers with dedup. Pixel setup is only the first layer of the measurement architecture; the server side and deduplication are the parts that move measurement from an incomplete sample to a count.
Footnotes
-
JavaScript Pixel (OpenAI Developers) — the oaiq snippet and the bzrcdn.openai.com source, init with pixelId and request-scoped user fields, the measure argument order, standard and custom event examples, custom_event_name rules, browser/server dedup via event_id, automatic oppref/
__oppref/source_url/batching, “Always use the pixel on the browser. Do not call the server conversions API directly from page code.” ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11 -
Supported events (OpenAI Developers) — the standard event taxonomy, data shapes (contents/customer_action/plan_enrollment), the minor units rule (“
12999for $129.99”), and thatcurrencyis required whenamountis present. ↩ ↩2
- 01 Pixel setup is three steps: add the snippet to the head, pass the Pixel ID with oaiq('init'), call oaiq('measure') on conversion.
- 02 User matching fields (such as email_sha256) go into init, not into individual measure calls; all are optional and hashed.
- 03 The measure data's type field matches the event shape: contents, customer_action, plan_enrollment. If amount is present, currency is required, and values are integers.
- 04 For dedup with the server-side Conversions API, set options.event_id on the pixel and keep it equal to the server id; the pixel always runs in the browser, the API is called from the server.
+ Where is the OpenAI Ads pixel added?
The snippet is added to the <head> of every page where conversions are captured, as high up as possible. The pixel should load at the very top of the page so that early conversions are not lost. The Pixel ID is obtained from the conversions tab in Ads Manager.
+ Where do user matching fields go?
User data is request-scoped and goes into the oaiq('init') call, not into individual oaiq('measure') calls. All fields are optional; send only what you have. If user data arrives later (for example after login), init is called again, and pixelId can be omitted after the first init.
+ What is the difference between a standard event and a custom event?
Standard events are the ready-made names in the taxonomy (page_viewed, order_created, etc.) and use a data shape appropriate to their type. A custom event is sent with the name 'custom' for an action not in the taxonomy and requires custom_event_name in the options object; the name is 1-64 characters, lowercase, letters/numbers/underscores/dashes, and cannot reuse a standard event name.
+ Should the pixel and the server Conversions API be used together?
Yes, but each called from the right place. The pixel is always used in the browser; the server Conversions API is called from the server, not from page code. If the same conversion is sent from both sides, options.event_id on the pixel is kept equal to the id on the server and the same Pixel ID is used, so dedup works.