Meta Conversions API Setup Guide: CAPI, Deduplication, and EMQ Done Right
Set up the Meta Conversions API the right way: CAPI vs Pixel, event_id deduplication, raising Event Match Quality, sGTM vs direct, Shopify, and GDPR.
Browser-only Meta Pixel tracking loses conversions to ad blockers, ITP, and consent rejection, so ad optimisation runs on incomplete data.
Run the Meta Conversions API alongside the Pixel with correct event_id deduplication and high Event Match Quality to recover lost signal.
The Meta Conversions API (CAPI) is a server-to-server interface that sends conversion events directly from your backend to Meta, instead of relying only on the browser-based Meta Pixel. Where the Pixel fires from the user’s device and is blocked by ad blockers, Safari Intelligent Tracking Prevention (ITP), and consent rejection, CAPI sends the same events from your server, where those restrictions do not apply. The result is more complete conversion data, which Meta’s algorithm uses to optimise ad delivery and attribution.
This guide explains how CAPI and the Pixel work together, why deduplication via a shared event_id is the single most important detail to get right, how to raise Event Match Quality (EMQ), the trade-offs between server-side GTM and a direct API integration, the Shopify specifics, and the GDPR considerations that apply in the EU. It is vendor-neutral: the goal is a setup that works regardless of which hosting or tool you choose.
What the Meta Conversions API actually is
The Pixel is a JavaScript snippet that runs in the visitor’s browser. When someone views a product or completes a purchase, the Pixel sends an event (ViewContent, Purchase, and so on) to Meta from the client. This is fragile by design: the browser is a hostile environment for tracking.
CAPI moves that same event to the server. Your backend, a server-side tag container, or a gateway sends a structured HTTP request to Meta’s Graph API endpoint with the event name, a timestamp, hashed customer data, and event parameters. Because the request originates from your infrastructure, it is not affected by browser extensions, cookie limits, or script blocking.
CAPI is not a replacement for the Pixel. The two are designed to run in parallel. The Pixel still captures the rich, real-time browser context (the fbp and fbc cookies, the user agent, the click ID from the ad). The server event adds reliability and can carry first-party identifiers the browser does not expose cleanly. Meta then stitches the two streams together.
CAPI vs Pixel: why you need both in parallel
A common mistake is treating CAPI as an either/or decision. It is not. The recommended architecture is Pixel plus CAPI for every important event, with deduplication so a single purchase is only counted once.
Each layer covers the other’s blind spots:
| Layer | Strength | Weakness |
|---|---|---|
| Meta Pixel (browser) | Rich browser signals (fbp, fbc, click ID), real-time | Blocked by ad blockers, ITP, consent rejection, network failures |
| Conversions API (server) | Survives blockers and ITP, can send hashed first-party data | No native browser context unless you forward it, depends on your data quality |
Running both is how you recover the conversions a Pixel-only setup silently drops. Pixel-only tracking can miss a large share of real conversions, and reported figures for the data recovered by adding a deduplicated CAPI typically land in the range of roughly 20 to 40 percent more conversions captured, depending on traffic mix, consent rates, and how clean the implementation is. Treat that as a setup-dependent range, not a guarantee.
For the underlying signal-loss problem this solves, our server-side tracking service covers the full browser-to-server architecture, not just Meta.
Event Match Quality (EMQ): the lever for lower cost per conversion
Event Match Quality (EMQ) is Meta’s score, from 1 to 10, for how well it can match the events you send to real Meta accounts. The more (and the more accurate) customer identifiers you attach to each event, the higher the match rate, and the better Meta can attribute conversions and optimise delivery.
The scale breaks down roughly like this:
- 8 to 10: Great. A perfect 10 is extremely rare. In practice 8 to 9 is the realistic ceiling, reached by sending eight or more hashed customer identifiers per event through CAPI.
- 5 to 7: Good. Functional, but leaving match potential on the table.
- Below 5: Needs work. Attribution and optimisation will suffer.
This is not a vanity metric. Per Meta’s own research, advertisers who improved their EMQ saw roughly 15 to 25 percent better cost per action. Higher match quality directly feeds the optimisation engine.
How to raise EMQ
Send more identifiers, correctly hashed. The customer parameters Meta accepts (sent as user_data) include:
- Email (
em) - Phone number (
ph) - First name (
fn) and last name (ln) - City (
ct), state (st), ZIP (zp), country (country) - External ID (
external_id), e.g. your customer ID - Click ID (
fbc) and browser ID (fbp) - IP address (
client_ip_address) and user agent (client_user_agent)
All personally identifiable fields must be SHA-256 hashed before they leave your server. Before hashing, normalise the value: lowercase it, trim whitespace, strip formatting from phone numbers (E.164, digits only). The fbc, fbp, IP, and user agent are sent unhashed.
import crypto from "crypto";
function hash(value) {
if (!value) return undefined;
const normalized = value.trim().toLowerCase();
return crypto.createHash("sha256").update(normalized).digest("hex");
}
const userData = {
em: hash(customer.email),
ph: hash(customer.phone?.replace(/\D/g, "")), // digits only
fn: hash(customer.firstName),
ln: hash(customer.lastName),
ct: hash(customer.city),
zp: hash(customer.zip),
country: hash(customer.countryCode), // e.g. "de"
external_id: hash(String(customer.id)),
fbc: cookies.fbc, // not hashed
fbp: cookies.fbp, // not hashed
client_ip_address: request.ip,
client_user_agent: request.headers["user-agent"],
};
The single biggest EMQ win for most shops is forwarding the fbc (click ID) and fbp (browser ID) cookies from the Pixel to the server event, plus a hashed email. Capture fbp and fbc client-side and pass them into your server payload.
Deduplication: the detail that breaks setups when wrong
When you run the Pixel and CAPI together, the same purchase is reported twice: once from the browser, once from the server. Without deduplication, Meta counts it twice, inflating conversions and corrupting optimisation. Deduplication is mandatory, not optional.
Meta deduplicates two events when they share the same event_name and the same event_id, received within roughly a five-minute window. When it finds a match, it keeps one event (typically crediting the browser event) and discards the duplicate server event. The customer data from both is merged.
The rule is therefore simple and unforgiving: the Pixel event and the CAPI event for the same action must carry the same event_id.
Generate the event_id once, then use it for both. A reliable pattern is to derive it from a stable transaction identifier (the order ID) so the browser and server independently compute the same value.
// Browser (Pixel)
const eventId = "purchase_" + orderId; // deterministic, same on both sides
fbq("track", "Purchase", {
value: 49.90,
currency: "EUR",
content_ids: ["SKU-123"],
}, { eventID: eventId }); // note the casing: eventID for the Pixel
// Server (CAPI) - same event_id, same event_name
await fetch(
`https://graph.facebook.com/v21.0/${PIXEL_ID}/events?access_token=${ACCESS_TOKEN}`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
data: [{
event_name: "Purchase", // must match the Pixel exactly
event_time: Math.floor(Date.now() / 1000),
event_id: "purchase_" + orderId, // must match the Pixel's eventID
action_source: "website",
event_source_url: orderUrl,
user_data: userData,
custom_data: {
value: 49.90,
currency: "EUR",
content_ids: ["SKU-123"],
},
}],
}),
}
);
Three things people get wrong here:
- Mismatched
event_name.Purchaseon the Pixel andpurchaseon the server will not deduplicate. Casing and spelling must be identical. - A random
event_idgenerated separately on each side. They will never match. Always derive it from a shared, deterministic source. - Sending the server event too late. If the server event arrives well outside the matching window, dedup may fail. Send it promptly, ideally near-real-time on the order confirmation or via an order webhook.
Implementation paths compared: direct API vs server-side GTM vs gateway
There is no single correct way to send CAPI events. The right choice depends on your stack, your team, and how much control you want over the data. The four common paths:
| Path | What it is | Best for | Trade-off |
|---|---|---|---|
| Direct API | Your backend calls Meta’s Graph API itself | Teams with backend access and developers | Full control, full maintenance burden |
| Server-Side GTM (sGTM) | A server container receives events and forwards to Meta via a tag | Marketing teams wanting one server hub for all platforms | Needs a hosted container; some complexity |
| CAPI Gateway | A managed service that runs the server layer for you | Fast setup, limited dev resources | Vendor dependency; less data control |
| Native platform integration | The Shopify/WooCommerce built-in CAPI connector | Quick start, simple stores | Limited deduplication and EMQ control |
Server-side GTM is the most common choice for shops that already use GTM, because one server container can feed Meta, GA4, TikTok, and others from a single, consented data stream. You forward the Pixel data and the fbp/fbc cookies into the container, then a Meta CAPI tag handles hashing and the Graph API call. Our server-side GTM work sits in this layer.
Direct API gives the cleanest control and no third-party in the data path, which matters for GDPR and for data minimisation. It costs developer time and ongoing maintenance.
Gateways and native integrations are the fastest to switch on but the hardest to get to high EMQ and reliable deduplication, because you have less control over which identifiers are sent and how event_id is generated. They are a reasonable starting point, not usually the end state for a serious advertiser.
Shopify specifics
Shopify has a native Meta channel that sends some CAPI events automatically. It is a fine baseline, but it is limited: you have little control over which identifiers are sent (so EMQ stays mediocre), and deduplication with any custom Pixel you also run can be inconsistent.
For a high-quality Shopify setup:
- On Shopify Plus, use Web Pixels (the Customer Events API) rather than editing
checkout.liquid, which is being deprecated for checkout. Web Pixels run in a sandbox and are the supported way to capture events. - For the purchase event, the
orders/paidwebhook is the most reliable server-side trigger. It fires after payment confirmation, carries the full order, and is not affected by the customer closing the tab on the thank-you page. - Derive
event_idfrom the Shopify order ID on both the client Pixel and the server webhook handler so they deduplicate. - Capture
fbpandfbcfrom the storefront and persist them with the order (a cart attribute or note attribute works) so the webhook handler can include them inuser_dataand lift EMQ.
// Shopify orders/paid webhook handler (server)
export async function handleOrderPaid(order) {
const eventId = "purchase_" + order.id;
const fbp = order.note_attributes?.find(a => a.name === "fbp")?.value;
const fbc = order.note_attributes?.find(a => a.name === "fbc")?.value;
await sendCapiEvent({
event_name: "Purchase",
event_id: eventId,
user_data: {
em: hash(order.email),
ph: hash(order.phone?.replace(/\D/g, "")),
ct: hash(order.billing_address?.city),
zp: hash(order.billing_address?.zip),
country: hash(order.billing_address?.country_code),
fbp,
fbc,
},
custom_data: {
value: Number(order.total_price),
currency: order.currency,
content_ids: order.line_items.map(i => String(i.product_id)),
},
});
}
WooCommerce and other platforms follow the same logic: fire the server event from an order-completed hook, share the event_id with the Pixel, and forward as many hashed identifiers as you legally can.
Testing in Events Manager
Do not assume it works. Validate it. Meta’s Events Manager gives you the tools:
- Test Events tab. Open Events Manager, go to your dataset, open the Test Events tab, and copy the test event code (
test_event_code). Add it temporarily to your server payload. Then trigger a real action. Events appear in the Test Events feed within seconds, showing exactly what Meta received. - Confirm deduplication. A correctly deduplicated event shows in Events Manager as received from both the browser and the server, marked as deduplicated. If you see two separate counted events, your
event_idorevent_namedo not match. - Check the Event Match Quality score. In the dataset overview, each event lists its EMQ. Use this to see which identifiers are missing and iterate.
- Watch the Diagnostics tab. Meta flags missing parameters, hashing problems, and dropped events here. Clear these before you trust the data.
{
"data": [{
"event_name": "Purchase",
"event_time": 1719500000,
"event_id": "purchase_1029",
"action_source": "website",
"user_data": { "em": "<sha256>", "fbp": "fb.1...", "fbc": "fb.1..." },
"custom_data": { "value": 49.90, "currency": "EUR" }
}],
"test_event_code": "TEST12345"
}
Remove the test_event_code before going to production. Test events do not count toward optimisation.
GDPR considerations for the EU
CAPI does not exempt you from data protection law. Sending personal data to Meta from your server is still processing of personal data, and in the EU it requires a lawful basis and, in almost all cases, consent. This is a technical explanation, not legal advice; confirm your specific setup with your data protection officer or counsel.
Key points for a GDPR-compliant setup:
- Consent is still required. Server-side does not mean consent-free. If the user rejects marketing cookies, you should not send their identifying data to Meta. Couple CAPI to your consent management platform.
- Consent Mode v2. Meta participates in Google’s Consent Mode v2 signalling, and your consent state should gate the CAPI event just as it gates the Pixel. Send the event only when marketing consent is granted, or send it without identifiers when it is not, according to your consent design.
- Hash personal data. Email, phone, and name are sent SHA-256 hashed. Hashing reduces exposure but the data is still personal data under the GDPR, so it does not remove the consent requirement.
- Data minimisation. Send only the identifiers you actually need for matching. Do not forward fields with no tracking purpose.
- Data processing agreement. Meta acts as a processor (or joint controller, depending on configuration) for this data. You need the appropriate agreement in place and a transparent privacy policy that names the processing and the transfer.
- Document it. Record the lawful basis, the consent flow, and the data flow in your processing records.
The advantage of a clean, server-side architecture is precisely that it gives you a single, auditable point where consent is enforced and identifiers are controlled, instead of tracking scattered across browser scripts. For the consent and lawful-basis layer specifically, see our tracking and consent work.
Recovering lost signal: what to expect
Adding a deduplicated, high-EMQ CAPI is the most effective way to undo the conversion blindness that iOS 14.5 and ITP created. Meta’s own figures illustrate the scale: it reported that iOS web-conversion underreporting fell from roughly 15 percent in September 2021 to about 8 percent in February 2022, a reduction of around half, which Meta attributed to wider Conversions API and Aggregated Event Measurement adoption. Some baseline underreporting is expected to remain; CAPI shrinks the gap rather than closing it entirely.
Set realistic expectations: you are recovering signal and improving optimisation, not buying perfect tracking. The combination of more complete data and higher match quality is what moves cost per action.
Implementation checklist
- Pixel and CAPI both fire for
Purchase(and other key events) - A shared, deterministic
event_idon both sides (derived from order ID) - Identical
event_namecasing on Pixel and server - Server event sent promptly (webhook or near-real-time), inside the dedup window
-
fbpandfbccookies forwarded to the server event - At least email plus several other hashed identifiers in
user_data - All PII SHA-256 hashed and normalised before sending
- Consent gating wired to your CMP (Consent Mode v2)
- Validated in Events Manager Test Events, deduplication confirmed
- EMQ checked and iterated toward 8+
- Diagnostics tab clear
- Data processing agreement and privacy policy updated
Frequently asked questions
What is the Meta Conversions API and how does it differ from the Pixel?
The Pixel sends conversion events from the visitor’s browser; CAPI sends the same events from your server. CAPI survives ad blockers, Safari ITP, and consent-driven script blocking that stop the browser Pixel. They are designed to run together, not as alternatives.
Do I still need the Meta Pixel if I set up CAPI?
Yes. The Pixel captures real-time browser context (the fbp and fbc cookies, click IDs) that improves matching. The recommended setup is Pixel plus CAPI for every important event, deduplicated by a shared event_id.
Is the Meta Conversions API GDPR-compliant?
CAPI can be operated in a GDPR-compliant way, but it is not automatically compliant. Sending personal data to Meta still requires a lawful basis (in practice, consent), a data processing agreement, and a transparent privacy policy. This is a technical explanation, not legal advice.
What is Event Match Quality and what is a good score?
EMQ is Meta’s 1-to-10 score for how well it can match your events to real accounts. 8 to 10 is “Great” (a perfect 10 is extremely rare), 5 to 7 is “Good”. You raise it by sending more accurate, correctly hashed customer identifiers per event.
How does deduplication between Pixel and CAPI work?
Meta deduplicates events that share the same event_name and event_id within roughly a five-minute window, keeping one and discarding the duplicate. Both the Pixel and the server event for the same action must send an identical event_id and event_name.
What is the difference between server-side GTM, a CAPI gateway, and a direct API integration?
Direct API means your backend calls Meta’s Graph API itself (most control, most maintenance). Server-side GTM routes events through a server container that can feed multiple platforms. A gateway is a managed service that runs the server layer for you (fastest, least control). All three can work; control over identifiers and event_id is the differentiator.
How do I set up the Conversions API for Shopify?
Use Web Pixels for client events, trigger the server Purchase event from the orders/paid webhook, derive event_id from the Shopify order ID on both sides, and forward fbp/fbc plus hashed customer data to lift EMQ.
How long does it take to set up the Meta Conversions API?
A focused implementation for the core events takes a few hours of work; reaching high EMQ and validating deduplication across all events and edge cases typically takes longer with iteration.
Getting CAPI right is mostly about the unglamorous details: a deterministic event_id, identical event names, the right hashed identifiers, and consent enforced in one place. If you would rather have it implemented and validated for you, see our Meta Conversions API service or the broader server-side tracking service. The best place to start is a free initial consultation: in a free strategy call we map what your current setup is missing and agree the fastest path to a clean, deduplicated CAPI.