Guide

How to Track Add-to-Cart, Checkout, and Purchase Events for OpenAI Ads

Add-to-cart, checkout, and purchase events map directly onto three OpenAI Ads standard events: items_added, checkout_started, and order_created. Each one fires from an oaiq("measure", ...) call placed at the moment that action happens on your site, carrying the product and monetary data OpenAI Ads needs to optimize toward — and eventually report — real purchases, not just clicks.

This guide covers which events map to which funnel step, the parameters that matter most (especially amount, currency, and the contents array), example measure calls you can adapt, and the formatting mistake that breaks more implementations than anything else. If you haven't installed the base pixel yet, start with how to install the OpenAI Ads measurement pixel — everything below assumes the pixel is already loading on every page. And if you want the full picture of how pixel events, the Conversions API, and attribution fit together, the complete guide to ChatGPT Ads tracking is the pillar reference.

Supported purchase-flow events

OpenAI Ads defines a fixed set of standard events for the pixel. The ones relevant to an ecommerce purchase funnel are:

Event nameFunnel stepWhat it represents
page_viewedAny pageAutomatic on pixel load — you rarely need to fire this manually
contents_viewedProduct pageA visitor viewed one or more specific products
items_addedAdd to cartA visitor added one or more items to their cart
checkout_startedCheckout beginsA visitor started the checkout process
order_createdPurchase completeAn order was successfully placed (your primary conversion event)

Two related events worth knowing about even though they're outside a typical web purchase funnel: lead_created and registration_completed cover lead-gen and signup flows, and subscription_created/trial_started cover subscription businesses. If your funnel doesn't fit any standard event, the pixel also supports custom events via a custom data shape and a custom_event_name passed in the options argument.

One thing the pixel does not cover: app_installed and app_opened are not supported through the pixel at all. Those are Conversions API-only events, sent server-side with action_source: mobile_app. If you're tracking a mobile app funnel, that's a CAPI implementation, not a pixel one — see the Conversions API setup guide.

Every oaiq("measure", ...) call takes the same basic shape:

oaiq("measure", "event_name", eventData, options);
  • eventName — one of the standard event names above (or your custom name, handled via options).
  • eventData — an object whose shape must match what that event expects. For purchase-funnel events, this is almost always the contents data shape: an object built around a contents array plus top-level fields like amount and currency.
  • options — required for custom events (to pass custom_event_name); optional otherwise. This is also where you'd pass an event_id if you're deduplicating against a matching Conversions API call — more on that below.

The fields that matter most for items_added, checkout_started, and order_created:

  • amount — the monetary value of the action, required whenever an amount is present. It must be an integer in ISO 4217 minor units — cents for USD, pence for GBP, and so on. 129.99 is wrong; 12999 is correct.
  • currency — required whenever amount is present. Use the ISO 4217 currency code ("USD", "EUR", "GBP").
  • contents — an array of line-item objects. Only use the fields documented for the contents data shape — don't invent extra keys hoping they'll be picked up. Quantities inside contents are integers, same as amounts.

Stick to the documented fields inside contents[]. Adding undocumented properties doesn't extend the schema — it just adds noise the SDK ignores or, worse, data that silently fails validation. Full field-level detail lives in the measurement pixel reference.

Example event calls

These use clearly generic example values — swap in your actual product IDs, amounts, and currency before shipping.

contents_viewed — product page:

oaiq("measure", "contents_viewed", {
  currency: "USD",
  contents: [
    { id: "SKU-1001", quantity: 1 }
  ]
});

items_added — add to cart:

oaiq("measure", "items_added", {
  currency: "USD",
  amount: 4999,
  contents: [
    { id: "SKU-1001", quantity: 1 }
  ]
});

checkout_started — checkout begins:

oaiq("measure", "checkout_started", {
  currency: "USD",
  amount: 8998,
  contents: [
    { id: "SKU-1001", quantity: 1 },
    { id: "SKU-2002", quantity: 1 }
  ]
});

order_created — purchase complete:

oaiq("measure", "order_created", {
  currency: "USD",
  amount: 8998,
  contents: [
    { id: "SKU-1001", quantity: 1 },
    { id: "SKU-2002", quantity: 1 }
  ]
});

Custom event example, if your funnel has a step none of the standard events fit (say, a "size guide viewed" interaction you want to track separately):

oaiq("measure", "size_guide_viewed", {
  type: "custom"
}, {
  custom_event_name: "size_guide_viewed"
});

Notice the eventData.type matches the event's expected data shape in every case — contents for the purchase-flow events, custom for the custom one. Mismatching the shape to the event is one of the quieter ways an implementation looks like it's working (no console errors) while actually sending malformed data.

Amount and currency formatting — the most common error

If there's one mistake worth memorizing before you write any of these calls, it's this: amounts are integers in minor units, not decimal dollars.

// Wrong — decimal amount
oaiq("measure", "order_created", { amount: 129.99, currency: "USD", contents: [...] });

// Correct — 129.99 USD as an integer in cents
oaiq("measure", "order_created", { amount: 12999, currency: "USD", contents: [...] });

12999 means $129.99. This trips people up because it's the opposite of what most payment UIs display, and because a decimal amount doesn't throw an obvious error — it just sends a value that's off by a factor of 100, which quietly wrecks any revenue reporting built on top of it. The same rule applies to quantities inside contents[]: whole integers, not fractional or decimal values.

Before you consider an integration done, pull up a real order in your test environment and manually check that the amount you're sending equals the displayed price times 100 (for two-decimal currencies like USD or EUR) — not the raw decimal price.

Firing at the right funnel step

Where you place each measure call matters as much as what's inside it:

  • contents_viewed fires on the product detail page, ideally on page load or when the primary product image/data has rendered — not on every scroll or interaction.
  • items_added fires at the moment the add-to-cart action succeeds — after the server or cart state confirms the item was added, not on button click alone (a failed add-to-cart shouldn't register as a conversion signal).
  • checkout_started fires when the visitor lands on or begins the checkout page/flow — once, per checkout attempt, not on every field interaction within it.
  • order_created fires on the confirmation/thank-you page after the order is actually placed — this is your primary conversion event, so it needs to be reliable above all else. If your thank-you page can be reloaded or revisited (browser back button, bookmarked link), guard against firing this event more than once per order.

A pixel event fired at the wrong moment isn't just imprecise — it changes what the data means. An items_added fired on button click regardless of success overstates cart activity; an order_created that fires on every page refresh inflates your reported purchases against what actually shipped.

Testing checklist before go-live

Don't flip these events live without checking each one individually:

  1. Enable debug: true in your oaiq("init", ...) call (see the pixel install guide if you haven't set this up) and watch the console as you walk through the funnel manually — view a product, add to cart, start checkout, complete a test order.
  2. Check the Network tab for each step — you should see a request to bzr.openai.com return 202 each time an event fires, not just on page load.
  3. Use the Pixel Helper extension to confirm which events are firing on a given page without digging through raw network requests.
  4. Confirm amounts in the payload match what you'd expect in minor units — this is the moment to catch the 129.99 vs 12999 mistake before it reaches Ads Manager.
  5. Watch the Ads Manager event stream and confirm each event type arrives a few minutes after you trigger it, and that your primary conversion event (typically order_created) is linked to the campaign you're measuring.
  6. Test the duplicate-order scenario — reload or revisit the thank-you page and confirm order_created doesn't fire again.

Full verification detail, including what a correctly firing pixel looks like end-to-end, is in Measure results with OpenAI Ads.

Where the Conversions API comes in

The pixel only sees what happens in the browser. Ad blockers, browser privacy settings, server-side redirects, and events that happen after the browser session ends (a delayed order confirmation, a payment that settles later) are all cases where the pixel alone won't catch the conversion. That's what the Conversions API is for — sending the same event types server-side, with an event_id that matches the pixel's, so OpenAI Ads can deduplicate the two signals against the same Pixel ID instead of double-counting.

If you're running on Shopify, the pixel-plus-CAPI setup has some platform-specific shortcuts worth knowing — see pixel and CAPI setup for Shopify. Otherwise, once your pixel events are firing cleanly through the checklist above, the Conversions API setup guide is the next step to close the gaps the browser can't cover on its own.

Want this done for you?

Fixed-scope setup, tested end-to-end and documented.

Explore the serviceTake the free audit