Meta Pixel + Conversions API: shipping both without double-counting
The pixel-plus-CAPI setup is the right answer for almost every advertiser in 2026. Done wrong, you'll inflate every conversion by 60%. Here's the deduplication contract that makes it work.
If you only run the Meta Pixel, browsers and ad blockers swallow somewhere between 15% and 40% of your events. Match quality drops, optimization signal degrades, the algorithm flies half-blind.
If you only run Conversions API, you lose the parameters the browser knows about (click ID, browser hash, every UTM that didn't make it server-side) and you eat all the engineering of building a server pipeline.
The right answer in 2026 is both — pixel for everything the browser sees, CAPI for the same events sent server-side, deduplicated by event ID so Meta counts each conversion exactly once.
The trap is that "deduplicated by event ID" sounds simple and is constantly done wrong. This post is the contract that gets it right.
What deduplication actually requires
Meta deduplicates server (CAPI) events against browser (Pixel) events when all three of these are true:
- Same
event_name—Purchase,Lead,AddToCart, etc. Case-sensitive. - Same
event_id— your generated ID, identical on both sides. - Within the deduplication window — 48 hours by default.
Get any one wrong and Meta counts both events. You'll see your conversion volume spike 50–80% the day you ship CAPI and look like a hero for two weeks until someone notices the ROAS doesn't match revenue.
The four common ways teams break it
1. Two different event ID schemes
The pixel fires event_id: "ord_abc123". The server sends event_id: "abc123". Meta sees two events.
Fix: pick one ID format and use it on both sides. The event ID should be the same string, generated once, passed to both. Order ID, transaction ID, or a UUID — all fine. They just have to match exactly.
2. CAPI sent before the pixel resolves
The pixel relies on the browser to fire the event. The server might send the CAPI event before the browser even loaded. Meta receives the CAPI event with event_id: ord_abc123 and waits 48 hours. The pixel never fires (ad blocker). Both events arrive, dedup works.
But: if the CAPI event fires more than 48 hours before the pixel — say, your server sends the conversion when the order ships, but the pixel fires at checkout — the dedup window has already closed by the time the pixel arrives. Both count.
Fix: fire CAPI as close to the user-facing event as possible, not at fulfillment time. The pixel and the CAPI call should be triggered by the same source event, ideally within seconds of each other.
3. Different event names
The pixel fires Purchase. The server sends purchase. Meta sees two events (case-sensitive match required).
Or: the pixel fires CompleteRegistration and the server sends Lead. They mean the same thing in your head. They don't to Meta.
Fix: standardize the event name. Reference: Meta's standard event list. For custom events, pick one name per event type.
4. CAPI sent only when the pixel fails
Some teams build a "fallback" CAPI: only send server-side if the pixel didn't fire. This sounds clever but breaks two things. First, you can't reliably know if the pixel fired (the server has no visibility into the browser). Second, you lose the match-quality lift CAPI gives you when both fire — Meta's matching algorithm uses signals from both events.
Fix: always send both. Always dedup by event ID. That's the whole contract.
The minimum-viable Purchase event, both sides
This is the shape of an event that dedupes correctly. Send the same event_id on both, the same event_name, the same event_time. Use server-side hashing for PII parameters per Meta's spec.
Browser (Pixel):
fbq('track', 'Purchase', {
value: 142.50,
currency: 'USD',
content_ids: ['SKU-1234'],
contents: [{ id: 'SKU-1234', quantity: 1, item_price: 142.50 }],
}, {
eventID: 'ord_abc123' // matches the CAPI side
});
Server (CAPI):
{
"event_name": "Purchase",
"event_time": 1714521600,
"event_id": "ord_abc123",
"action_source": "website",
"event_source_url": "https://shop.example.com/order/abc123/confirmation",
"user_data": {
"em": ["sha256_hashed_email"],
"ph": ["sha256_hashed_phone"],
"client_ip_address": "203.0.113.42",
"client_user_agent": "Mozilla/5.0 ...",
"fbc": "fb.1.1714000000000.AbCdEf...",
"fbp": "fb.1.1714000000000.123456789"
},
"custom_data": {
"value": 142.50,
"currency": "USD",
"content_ids": ["SKU-1234"],
"contents": [{"id": "SKU-1234", "quantity": 1, "item_price": 142.50}]
}
}
Three things worth calling out:
fbcandfbpare the click ID and browser ID cookies. Read them on the server from the request and forward them inuser_data. They're the single biggest match-quality lift you can give CAPI.event_source_urlmust be the exact URL the user is on when the event fires. Not the API endpoint. Not the homepage.event_timein seconds (Unix). Match between pixel and CAPI within the dedup window.
Verifying it works
Three places to look, in order:
1. Test Events tab in Meta Events Manager. Send a few test purchases with a known test_event_code in CAPI and a manual checkout for the pixel. Both should appear with a matching event ID and a "Deduplicated" tag.
2. Diagnostics tab on the pixel. Look for "Event Deduplication" warnings. If Meta sees mismatched fields between your pixel and CAPI events for the same event ID, it'll surface them here.
3. Match quality score on the dataset. Should rise after CAPI ships. If it falls, your user_data payload is sparse — add fbc/fbp and hashed email at minimum.
What to track in your tracking map
Each Meta dataset (formerly "pixel") deserves four pieces of structured metadata in your agency's documentation:
- Pixel ID + dataset ID
- Active events (Purchase, Lead, AddToCart, ...) with whether each is sent via Pixel, CAPI, or both
- Match quality score + last-checked date
- Deduplication health — green if Events Manager shows >95% dedup rate on overlapping events, yellow if 80-95%, red below
The whole point of having a tracking infrastructure map is that this stuff is on a dashboard, not in someone's head. The Pixel + CAPI setup is one of the easiest places to silently drift from "configured" to "broken" — match quality degrades when iOS bumps a privacy default, when the server adds a CDN that strips IP, when someone changes the order-confirmation page URL. Without a structured per-pixel record, the only signal is the ROAS report eight weeks later.
The takeaway
Pixel + CAPI is the right architecture in 2026. Done correctly, conversions stop disappearing into ad-block voids and your match quality climbs.
Done incorrectly, you'll silently inflate every Meta number for months.
The contract is short:
- Same
event_idon both sides - Same
event_name(case-sensitive) - Within 48 hours
- Always send both, never use CAPI as a fallback
Get the contract right, verify in the Test Events tab, then track the dedup rate as part of every client's tracking-map health check. The rest is just discipline.