Guide

How to Deduplicate OpenAI Ads Pixel and CAPI Events with Event IDs

If you run both the OpenAI Ads pixel and the Conversions API for the same conversion — which is the point of running both — you must send the same identifier from each side: the pixel's event_id and the CAPI id field have to match, using the same Pixel ID and event name. When they match, OpenAI recognizes the two events as one conversion and counts it once. When they don't, you get two conversions for every purchase or lead the pixel already caught, and your reported numbers climb above what actually happened.

This is one of the few tracking mistakes that doesn't look like a mistake at first. Missing pixel events show up as a suspiciously low number — something feels obviously wrong. Missing deduplication shows up as a higher number, which is the direction everyone wants to see, so it tends to go unquestioned right up until someone reconciles ad platform conversions against real backend orders and finds a gap that shouldn't exist.

Symptoms: what double-counting actually looks like

The tell isn't a single dramatic number — it's a pattern, and it's usually only visible once you compare OpenAI Ads Manager against a source of truth you control.

  • Reported conversions exceed backend orders or leads. If Ads Manager shows more order_created or lead_created events for a given date range than your order database or CRM shows for the same range, that gap is a strong signal of duplication, not new demand appearing from nowhere.
  • ROAS looks better than the math should allow. Return on ad spend is calculated from reported conversion value. If every purchase is counted twice, the value side of that ratio inflates, and campaigns can look like they're outperforming when they're reporting the same revenue twice.
  • The gap tracks with when CAPI went live. If the discrepancy appeared or widened around the same time a Conversions API implementation was added alongside an existing pixel, that timing is close to diagnostic on its own — it's the exact moment a hybrid setup starts needing deduplication it may not have.
  • Custom event counts look inflated specifically, not standard events. If standard events like order_created look fine but a custom event you defined looks doubled, that usually points to a custom_event_name mismatch rather than a general id/event_id problem — more on that below.

None of these symptoms are proof by themselves — a real traffic spike can also raise conversions — but if you see any of them without an obvious explanation, deduplication is the first thing worth ruling out before you trust the number for a budget decision.

How OpenAI actually matches events

Deduplication in OpenAI Ads works on identity, not proximity or timing. OpenAI treats the pixel's event_id and the CAPI id as the same identifier — when a pixel event and a CAPI event share the same Pixel ID, the same event name (type), and the same event_id/id value, OpenAI recognizes them as one conversion and keeps a single record.

For a standard event like order_created, that means three things have to line up across both sends:

  1. The same Pixel ID — the pixel's init call and the CAPI request's pid query parameter must reference the identical Pixel ID.
  2. The same event name — order_created sent from the pixel and order_created sent via CAPI, not a variant spelling or a different standard event.
  3. The same event_id / id value — generated once, per real-world event, and reused identically on both the pixel call and the CAPI call.

For custom events — anything outside the standard set (page_viewed, contents_viewed, items_added, checkout_started, order_created, lead_created, registration_completed, subscription_created, trial_started) — there's a fourth requirement: the custom_event_name must also match between the pixel send and the CAPI send. This is the detail that gets missed most often, because it's easy to get the id matching right while forgetting that the custom event's name field needs the same exact-match treatment.

Miss any one of these four checks, and OpenAI has no way to know the two events describe the same real-world action. It records both, and your dashboard shows two conversions where one purchase happened.

Implementation: generating and sending a stable, shared ID

The core requirement is simple to state and easy to get subtly wrong in practice: one ID, generated once, used on both sides.

Step 1 — Generate the ID once, from something that already uniquely identifies the event

The safest source for this ID is something your system already treats as unique — an order ID, a lead submission ID, a transaction ID from your payment processor. Don't generate a fresh random value independently on the client and the server; generate it (or derive it) in exactly one place, then pass that same value to both the pixel call and the CAPI call.

A common, reliable pattern is to build the ID from the order or lead record itself:

// Generated once, server-side, when the order is created —
// then passed down to the confirmation page for the pixel call
// and used directly for the CAPI call.
const eventId = `order-${order.id}`;

Using the order ID directly (optionally with a short prefix like order- to make the event type visually obvious in logs) avoids an entire category of bugs: you never have to ask "did the pixel and the server generate the same UUID," because there's no separate generation step to get out of sync. The ID already exists — you're just reusing it.

Step 2 — Pass the ID to the pixel via the measure options

The pixel call is oaiq("measure", eventName, eventData, options) — the options argument is where event_id goes:

oaiq("measure", "order_created", {
  currency: "USD",
  value: 12999
}, {
  event_id: `order-${order.id}`
});

The event_id here has to be the exact same string your backend will also send via CAPI for this same order — same characters, same casing, same prefix or lack of one.

Step 3 — Send the matching id via CAPI

On the server side, the Conversions API call uses id (not event_id — the field name differs by endpoint, but OpenAI treats them as the same identifier for matching purposes):

curl -X POST "https://bzr.openai.com/v1/events?pid=PIXEL-ID" \
  -H "Authorization: Bearer YOUR-API-KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "data": [{
      "id": "order-48213",
      "type": "order_created",
      "timestamp_ms": 1721000000000,
      "action_source": "web",
      "source_url": "https://example.com/checkout/confirmation",
      "data": { "currency": "USD", "value": 12999 }
    }]
  }'

Note two required fields that are easy to drop when focused only on dedup: source_url is required whenever action_source is web, and timestamp_ms has to fall within 7 days in the past or 10 minutes in the future relative to when you send the request. Both are separate requirements from deduplication itself, but a request that fails validation for either reason won't get recorded at all — which can look like a dedup problem when it's actually a rejected request.

For a custom event, the same pattern applies with the added custom_event_name match:

// Pixel side
oaiq("measure", "custom", {
  custom_event_name: "demo_requested",
  value: 0
}, {
  event_id: `demo-${lead.id}`
});
# CAPI side — custom_event_name must match exactly
curl -X POST "https://bzr.openai.com/v1/events?pid=PIXEL-ID" \
  -H "Authorization: Bearer YOUR-API-KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "data": [{
      "id": "demo-9021",
      "type": "custom",
      "timestamp_ms": 1721000500000,
      "action_source": "web",
      "source_url": "https://example.com/demo-confirmed",
      "data": { "custom_event_name": "demo_requested", "value": 0 }
    }]
  }'

Common mistakes that break deduplication

Most dedup failures trace back to one of these five patterns.

  • Generating the ID independently on each side. The most common root cause. If the frontend generates a random UUID for the pixel call and the backend generates a separate random UUID for the CAPI call, they will never match — there was never a shared source of truth. Fix this by deriving the ID once from an object that already exists (the order, the lead, the transaction) and reusing that same string on both calls.
  • Truncating or reformatting the ID between systems. An ID that's order-48213 on the frontend but gets normalized, trimmed, or has its prefix stripped by backend validation before reaching CAPI no longer matches character-for-character. Dedup is an exact string match, not a fuzzy one — one dropped character or case change breaks it.
  • Forgetting custom_event_name on custom events. Custom events need a fourth matching field beyond the standard three. Teams that build dedup logic against standard events first sometimes ship custom events without realizing the requirement is stricter.
  • Mismatched Pixel IDs across pixel and CAPI. If your pixel's init call references one Pixel ID and your CAPI pid parameter references a different one — common after account migrations or copy-paste errors — events land under two different IDs and can't be matched, regardless of whether event_id/id are identical.
  • Sending the CAPI event outside the timestamp window. A queued or retried event with a timestamp_ms outside the 7-day-past / 10-minute-future window gets rejected rather than deduplicated. That's not technically a dedup failure, but it produces a similar-looking symptom, and it's worth ruling out separately.

Testing deduplication before launch

Don't find out whether dedup works from a week of live campaign data. Confirm it in a test environment first.

  1. Trigger one real conversion event (a test order, a test lead form submission) connected to your actual Pixel ID.
  2. Confirm the pixel call fires with the expected event_id in the measure options — check your browser's Network tab for the POST to bzr.openai.com and inspect the payload.
  3. Confirm the CAPI call fires with the same id value, by logging the outgoing request server-side.
  4. Check that the two event_id/id values are byte-for-byte identical — same string, same case, no extra whitespace. Do this as an explicit comparison, not a visual glance; the failure mode here is subtle by nature.
  5. Wait for the event to appear in OpenAI Ads Manager and confirm one conversion, not two. Processing isn't always instant, so give it time before concluding dedup failed — a delay isn't the same as a duplicate.
  6. Repeat for at least one custom event, if you use them, and verify custom_event_name matches on both sides — the check most likely to get skipped if you only tested standard events first.

If step 4 or step 6 turns up a mismatch, fix it before the setup goes live on real spend. A dedup bug caught in testing costs a few minutes; the same bug caught three weeks into a campaign costs three weeks of ROAS numbers you now have to explain and can't fully trust retroactively.

When to get this checked instead of debugging it yourself

Deduplication is a small piece of configuration with an outsized ability to quietly corrupt every conversion number downstream of it. If you're not confident your event_id and id values are generated from one shared source, or you've never explicitly tested the byte-for-byte match described above, that's worth resolving before you trust a reporting dashboard for a real budget decision — not after a discrepancy shows up.

A tracking audit ($450, 48-hour turnaround, credited toward any setup you move forward with) checks exactly this: whether your pixel and CAPI events are actually deduplicating, alongside the rest of your measurement setup. If you're setting up pixel and CAPI from scratch and want dedup built correctly from day one instead of debugged after the fact, that's what a complete Pixel + CAPI setup is for — event IDs matched, tested against real events, and documented so the logic doesn't depend on one person remembering how it works.


Deduplication is one line of logic — reuse one ID on both sides — but it's the line that determines whether your hybrid pixel + CAPI setup gives you more accurate data or just a bigger, wronger number. Get the shared ID right, test it explicitly before launch, and it stops being something you have to think about at all.

Want this done for you?

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

Explore the serviceTake the free audit