Server-side tracking, explained without the buzzwords
What the Conversions API and Events API actually are, how event_id deduplication lets the pixel and server run side by side, and the myths that refuse to die.
“Server-side tracking” has accumulated enough marketing fog that it's worth restating what it actually is: instead of (or in addition to) the user's browser telling the ad platform about a conversion, your server tells the platform directly, over a plain HTTPS API. That's the whole idea. Everything else is implementation detail. Important detail, but still detail.
What the browser pixel actually does
A pixel is a JavaScript snippet that runs on your page. When something happens (a page view, an add to cart, a purchase), it fires an HTTP request from the visitor's browser to the platform, carrying the event name, cookies like _fbp/_fbc, and whatever page context it can gather. Its strengths: trivial to install, and it sees browser-native signals firsthand. Its weakness is structural: it executes on hardware you don't control, inside software actively working against it. Ad blockers stop it from loading, ITP kills its cookies, iOS policies limit what it can persist, and a closed tab mid-checkout means the purchase event simply never fires.
What CAPI and the Events API actually are
Meta's Conversions API (CAPI) and TikTok's Events APIare not magic infrastructure. They are HTTP endpoints that accept JSON. Google's equivalents (enhanced conversions, offline conversion imports) differ in schema but not in spirit. A minimal Meta CAPI call looks like this:
POST https://graph.facebook.com/v24.0/{pixel_id}/events
{
"data": [{
"event_name": "Purchase",
"event_time": 1782950400,
"event_id": "order_58213",
"action_source": "website",
"event_source_url": "https://shop.example/checkout/thank-you",
"user_data": {
"em": ["a8f5f167f4..."], // sha256(normalized email)
"ph": ["c0e8179438..."], // sha256(normalized phone)
"external_id": ["9b74c9897b..."],
"fbc": "fb.1.1719364800.AbCdEfGh",
"fbp": "fb.1.1719364800.1019141",
"client_ip_address": "203.0.113.7",
"client_user_agent": "Mozilla/5.0 ..."
},
"custom_data": { "currency": "EUR", "value": 249.00 }
}]
}Same event vocabulary as the pixel, same user_data matching fields, just sent from an environment where nothing gets blocked, nothing expires after seven days, and delivery can be retried on failure. Because it originates server-side, it can also carry things the browser lost long ago: the click ID captured on the landing page three weeks before the purchase, the customer's email from the order record, a stable external_id.
Deduplication: how pixel and server coexist
The recommended setup is redundant: browser pixel and server events both report the same conversions, and the platform keeps exactly one copy. The mechanism is event_id. When Meta receives two events with the same event_name and the same event_id within the dedup window, it discards the later one and keeps the union of useful signals.
// Browser (pixel)
fbq('track', 'Purchase',
{ value: 249.00, currency: 'EUR' },
{ eventID: 'order_58213' } // <- same ID
);
// Server (CAPI) - same event
{ "event_name": "Purchase", "event_id": "order_58213", ... }The only rule that matters: the ID must be deterministic, derived from something both sides know, like the order ID. Random UUIDs generated independently in each environment cannot dedupe, and that is the single most common implementation bug we see. If the browser event gets blocked, only the server copy arrives, so there is nothing to dedupe and nothing is lost. If both arrive, the platform sees one purchase. Either way the count is right.
action_source: small field, real consequences
Every CAPI event must declare where the conversion actually happened: website, app, phone_call, physical_store, chat, email, system_generated, other. It isn't decorative. Platforms use it for attribution logic and policy enforcement, and misdeclaring it (marking offline events as website, say) is the kind of thing that gets event sources flagged. If you send website conversions server-side, they are still action_source: "website". The API describes where the user converted, not where the HTTP request came from.
Myths worth killing
“Running pixel + server double-counts”
Not if event_idis implemented correctly. Dedup exists precisely for this. If you see inflated numbers after adding CAPI, the cause is mismatched or missing event IDs, not a flaw in the approach. Check Events Manager's dedup diagnostics before blaming the architecture.
“Server-side replaces the pixel”
Keep the pixel. Browser events contribute signals that are cheapest to collect in the browser (fresh fbp/fbc values, page context) and give the platform a second observation to merge. Server-only setups are legitimate (and sometimes required), but the default recommendation from the platforms themselves is redundant setup with deduplication, because matched pairs perform best.
“It's a way around consent”
No. Moving the HTTP request from browser to server changes the transport, not the law. Consent requirements apply to the processing, so a proper implementation propagates consent state to the server and drops or limits events for users who declined. Anyone selling server-side tracking as a GDPR loophole is selling you a liability.
“Turn on CAPI and attribution fixes itself”
The API is a delivery channel; the value is in the payload. A server event with no click ID, no hashed identifiers and a random event ID is the same weak signal, delivered more reliably. The wins come from what server-side enables: click IDs captured first-party and re-attached at conversion time, order emails hashed into em, a persistent external_id. That is what moves match quality. We cover it in depth in our EMQ guide.
What a good implementation looks like
- Click IDs and session data captured on landing, stored first-party, tied to a durable visitor ID.
- Conversions matched server-side to that visitor, even days later and cross-device via login or checkout email.
- Events sent to each platform with full
user_data, normalized and SHA-256 hashed, with deterministicevent_ids shared with the pixel. - Retries on platform errors, and monitoring that shows delivered vs. failed events per platform, so you find out before the algorithm does.
None of this is exotic. It is plumbing: the kind that quietly decides whether the auction algorithms spend your budget on reality or on the fraction of it a browser managed to report. If you want the “why” behind the missing fraction, start with What is signal loss?