
Quick Answer: OpenAI Ads sends conversions through 11 standard event types like order_created and checkout_started, organized into 4 data shapes. Deduplication works by reusing the same event_id across the pixel and Conversions API so OpenAI merges the two into one conversion instead of counting it twice.
Key Takeaways
- OpenAI supports 11 standard conversion event names across 4 data shapes: contents, customer_action, plan_enrollment, and custom.
- Deduplication between the pixel and the Conversions API comes down to one thing: reusing the same event_id, and for custom events the same custom_event_name, on both sides.
- OpenAI's dedup match key is Pixel ID plus event name plus event_id. There's no separate toggle to turn deduplication on, it happens automatically once the IDs line up.
- A 401 error citing a missing
ads.third_party_events.writescope means the API key wasn't created with conversion-write permission, not that the integration itself is broken. - Skipping deduplication doesn't throw an error, it just quietly inflates reported conversions until someone checks the numbers against actual Shopify orders.
What conversion events does OpenAI Ads actually support?
OpenAI's Conversions API and pixel share the same taxonomy: 11 standard event names, split across 4 data shapes. Ten map to a specific shopper action, and the eleventh, custom, covers anything outside that list.
Here's the complete reference, more detail than OpenAI's own docs page shows in one place:
Complete OpenAI Ads Event Reference
| Event name | Data shape | Fires when | Typical Shopify trigger | Notes |
|---|---|---|---|---|
order_created | contents | A purchase completes | Order confirmation page | Include amount + currency to report revenue |
checkout_started | contents | A user begins checkout | Checkout page load | amount + currency recommended for funnel value |
items_added | contents | An item is added to a cart, bundle, or selection | Add to cart click | amount + currency recommended |
contents_viewed | contents | A user views a specific product or content item | Product detail page | Use contents[] to identify the specific item |
page_viewed | contents | A user lands on or views an important page | Any page load worth counting | No fields required beyond type |
lead_created | customer_action | A user submits a lead form or requests contact | Contact/lead form submit | No fields required beyond type |
registration_completed | customer_action | A user finishes account or event registration | Account creation confirmation | No fields required beyond type |
appointment_scheduled | customer_action | A user books a meeting, demo, or consultation | Booking confirmation | Add amount + currency if the booking is priced |
subscription_created | plan_enrollment | A paid subscription starts | Subscription checkout confirmation | plan_id recommended, plus amount + currency |
trial_started | plan_enrollment | A free trial starts | Trial signup confirmation | plan_id recommended |
custom | custom | Anything outside the standard taxonomy | Store-specific action | Set custom_event_name in eventOptions, not eventProps |
One detail that trips people up: custom_event_name and event_id both belong in eventOptions, not in the eventProps object where everything else lives.
Which data shape does each event use, and what fields does it need?
Every event's data object needs a type field matching one of four shapes: contents, customer_action, plan_enrollment, or custom. Get the shape wrong and the event won't parse the way you expect.
The contents shape covers anything commerce-flow related: order_created, checkout_started, items_added, contents_viewed, page_viewed. It optionally carries an event-level amount and currency, plus a contents array where each item can include id, name, content_type, quantity, amount, and currency. Monetary values go in as integers in the currency's lowest denomination, so $129.99 is sent as 12999 with currency: "USD", not as a decimal.
The customer_action shape covers non-transactional actions: lead_created, registration_completed, appointment_scheduled. It's lighter weight, just an optional amount and currency if the action carries a price tag.
The plan_enrollment shape covers subscription_created and trial_started, adding an optional plan_id so you can tell which plan or trial tier converted.
The custom shape is for anything else. Keep custom_event_name between 1 and 64 characters, lowercase letters, numbers, underscores, or dashes only, and don't reuse one of the 10 standard event names.
If you're a standard ecommerce brand, contents is the shape you'll use for nearly everything which is something Aimerce sends as a standard event.
How does OpenAI's Conversions API differ from the pixel technically?
The pixel runs in the browser and handles a few things automatically that a server-side integration has to do by hand. That difference is exactly why most teams end up running both.
The pixel captures oppref, OpenAI's privacy-preserving click identifier, straight from the landing page URL and stores it in a first-party __oppref cookie so later page views can reuse it. It also stamps the current page as source_url automatically. A server-side event sent through the Conversions API doesn't get any of that for free. You have to capture oppref yourself and pass it along when it's available, or attribution quality drops. In practice, that means your backend needs to store the landing page's query parameters somewhere retrievable, so oppref is still there by the time an order webhook fires, potentially well after the original visit. Aimerce handles that persistence automatically as part of its webhook and pixel setup.
The Conversions API also has its own operational limits worth knowing. Events are sent in batches of up to 1,000, and if one event in a batch is malformed, the entire batch fails, not just the bad event. Every event's timestamp_ms has to fall within the last 7 days and no more than 10 minutes in the future, so stale or clock-skewed events get silently rejected rather than backfilled.
How do you actually deduplicate pixel and CAPI events?
Reuse the same event_id on both the pixel call and the server-side event, sent through the same Pixel ID. That's the entire mechanism, there's no separate deduplication setting to configure.
If a purchase fires order_created from the browser pixel with event_id: "order_12345", the server-side event for that same order needs id: "order_12345" in the Conversions API payload, using the identical value. OpenAI's matching logic keys on three things together: Pixel ID, event name, and event_id. All three have to line up for the system to recognize it's the same conversion and merge the two signals into one instead of counting twice. For custom events, custom_event_name takes the place of the standard event name in that match, so it has to be identical on both sides too.
If you omit event_id entirely, the pixel SDK generates one for you automatically, which is fine for browser-only tracking. The moment you're also sending that same conversion server-side, you need to generate the ID yourself and reuse it in both places, because the SDK's auto-generated ID isn't something your server can retrieve after the fact.
What happens if you don't deduplicate?
Every conversion the pixel catches and the server also reports gets counted twice, which inflates your reported ROAS on a channel that isn't actually performing as well as the dashboard suggests.
You won't see any error messages, but your conversion numbers will be totally inflated. You’ll usually catch it when your OpenAI Ads dashboard numbers don't line up with your actual Shopify orders. By then, you’ve probably already wasted money scaling your ads based on junk data.
Should you run the pixel alone, the Conversions API alone, or both?
Both, in almost every case. Each one recovers a different category of loss, and running just one leaves a predictable gap.
Pixel vs. Conversions API: What Each One Actually Covers
| Dimension | Pixel Only | Conversions API Only | Both, Deduplicated |
|---|---|---|---|
| Blocked by ad blockers | Yes, common failure point | No, server-to-server | No |
| Affected by Safari/ITP or cookie deletion | Yes | No | No |
Captures oppref automatically | Yes, via first-party cookie | No, must be captured and passed manually | Yes, via pixel |
| Requires manual event_id management | No, SDK generates one | Yes, for dedup to work later | Yes |
| Works if a customer's browser blocks JavaScript entirely | No | Yes, since it doesn't depend on the browser | Yes, via CAPI |
| Risk of double-counted conversions | None on its own | None on its own | Only if event_id/Pixel ID aren't matched |
| Overall coverage | Partial | Partial | Most complete |
How to choose your event mapping approach
If you're a standard ecommerce brand: the contents shape covers nearly everything you need, order_created, checkout_started, items_added, contents_viewed, and page_viewed. Start there before worrying about the other shapes.
If you run lead-gen or a contact-request flow alongside your store: lead_created is the standard event to use, and it's confirmed as something you can send as a standard event rather than a custom one.
If you run subscriptions or trials: the plan_enrollment shape exists for that, but confirm with your engineering team which of these are currently wired up before assuming they're live.
If you're only running the pixel today: plan a deliberate move to add server-side events rather than treating it as optional. The gap it's covering, ad blockers, cookie deletion, JavaScript-disabled browsers, doesn't shrink over time, it grows.
If you're already sending both: the only thing left to verify is that event_id and Pixel ID match exactly between the two paths. That's the single point of failure for the entire dedup mechanism.
Common mistakes to avoid
- Assuming the pixel alone is enough. It will look like it's working right up until ad blockers, cookie deletion, or a JavaScript-disabled browser quietly erase a chunk of your data.
- Generating a fresh, random ID for the server event instead of reusing the pixel's event_id. This breaks deduplication silently. No error, just double-counted conversions.
- Sending pixel and server events under different Pixel IDs. The dedup match requires the same Pixel ID on both sides, not just the same event_id.
- Forgetting to capture and forward oppref in a server-only setup. The pixel does this automatically; a server-side integration has to do it manually or lose attribution accuracy.
- Assuming every standard event applies to your business. If you're a standard ecommerce brand, the customer_action and plan_enrollment events mostly won't apply to you, don't spend setup time mapping events you'll never fire.
FAQ
How many conversion events does OpenAI Ads support? Eleven standard event names across four data shapes: contents, customer_action, plan_enrollment, and custom for anything outside the standard taxonomy.
What is oppref and why does it matter for OpenAI Ads tracking? oppref is OpenAI's privacy-preserving click identifier. The pixel captures and stores it automatically in a first-party cookie. A server-only integration has to capture and forward it manually, or attribution accuracy drops.
How do you deduplicate OpenAI Ads pixel and Conversions API events? Reuse the same event_id (or, for custom events, the same custom_event_name) on both the pixel call and the server-side event, sent under the same Pixel ID. OpenAI matches on Pixel ID, event name, and event_id together to merge the two into a single conversion.
What's the difference between the pixel and the Conversions API for OpenAI Ads? The pixel runs in the browser and captures things automatically, like the oppref click identifier, but it's vulnerable to ad blockers and cookie restrictions. The Conversions API runs server-to-server and isn't affected by either, but requires you to capture and forward identifiers like oppref manually. Running both, deduplicated, covers the gap either one leaves alone.
What happens if an event's data shape doesn't match its type?
The event won't parse the way you expect. Every event's type field has to match one of four shapes, contents, customer_action, plan_enrollment, or custom, and sending fields that belong to the wrong shape is a common source of silently malformed events.
Why did my OpenAI Ads conversion event fail with a scope error?
A 401 error citing a missing ads.third_party_events.write scope usually means the API key was generated without conversion-write permission. Create a new key from the Conversions tool in Ads Manager with the correct scope.
Do I need both the pixel and the Conversions API, or just one? Both, for the most complete picture. The pixel alone misses conversions blocked by ad blockers or deleted cookies, and the Conversions API alone misses the automatic oppref capture the pixel handles for you. Running both, properly deduplicated, covers the gap either one leaves on its own.
Can I send custom events to OpenAI Ads? Yes. Set the event's type to custom on both the pixel and the Conversions API, and provide a custom_event_name between 1 and 64 characters, using lowercase letters, numbers, underscores, or dashes, that doesn't duplicate a standard event name.
Does deduplication happen automatically, or do I have to configure it? It's automatic once a shared event_id is present on both delivery paths. There's no separate deduplication setting. OpenAI's system merges any pixel and server event that match on Pixel ID, event name, and event_id.
Sources
[1] OpenAI, "Conversions API" [2] OpenAI, "JavaScript Pixel" [3] OpenAI, "Supported events” [4] Aimerce, "OpenAI Ads Manager: The Complete Walkthrough for DTC Marketers in 2026" June 15,
Related reading

Try Aimerce Pixel Risk-Free
for 30 Days
Most teams see results within 2 weeks.
Money-back guarantee.
It pays for itself, or you don't pay anything.
30-Day Aimerce Pixel Free Trial