Skip to content
ceaksan

Setting Up the OpenAI Ads JavaScript Pixel: A Step-by-Step Guide

Setting up the OpenAI Ads pixel takes three steps: add the snippet to the head of every page, pass your Pixel ID with oaiq('init'), and call oaiq('measure') when a conversion happens. This guide shows the install snippet, the init and measure calls, standard and custom event examples, and the testing steps.

Jun 9, 2026 4 min read
TL;DR

Setting up the OpenAI Ads pixel takes three steps: add the oaiq snippet to the head of every conversion page, as high up as possible; initialize the pixel with oaiq('init', { pixelId }); and call oaiq('measure', '<event>', <data>, <options>) when a conversion happens. Standard events use names like page_viewed, contents_viewed, items_added, order_created, lead_created, subscription_created, trial_started and the data shapes appropriate to their type. User matching fields go into init, not into individual measure calls; event_id goes into options for dedup with the server side.

Setting up the OpenAI Ads pixel takes three steps:

  1. Add the snippet to the page
  2. Define the Pixel ID with oaiq("init")
  3. 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.

EventData type (type)When
page_viewedcontentsAn important page is viewed
contents_viewedcontentsA product, content, or item is viewed
items_addedcontentsAn item is added to cart or selection
order_createdcontentsA purchase is completed
lead_createdcustomer_actionA lead form is submitted
registration_completedcustomer_actionRegistration is completed
appointment_scheduledcustomer_actionA meeting or demo is scheduled
subscription_createdplan_enrollmentA paid subscription starts
trial_startedplan_enrollmentA 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 oppref identifier from the landing page URL.
  • Stores this identifier in a first-party __oppref cookie and reuses it on later page views.
  • Adds the current page origin as source_url.
  • Timestamps each event and batches closely grouped measure calls.

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: true on during testing and watch the console logs.
  • Send amount and quantity as 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

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