Skip to content
Home Guides Analytics
Analytics Expert

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.

FW
FW Delta
14 min 2–4 hours
The Problem

Browser-only Meta Pixel tracking loses conversions to ad blockers, ITP, and consent rejection, so ad optimisation runs on incomplete data.

The Fix

Run the Meta Conversions API alongside the Pixel with correct event_id deduplication and high Event Match Quality to recover lost signal.

Meta Conversions APIServer-Side GTMShopify

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:

LayerStrengthWeakness
Meta Pixel (browser)Rich browser signals (fbp, fbc, click ID), real-timeBlocked by ad blockers, ITP, consent rejection, network failures
Conversions API (server)Survives blockers and ITP, can send hashed first-party dataNo 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:

  1. Mismatched event_name. Purchase on the Pixel and purchase on the server will not deduplicate. Casing and spelling must be identical.
  2. A random event_id generated separately on each side. They will never match. Always derive it from a shared, deterministic source.
  3. 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:

PathWhat it isBest forTrade-off
Direct APIYour backend calls Meta’s Graph API itselfTeams with backend access and developersFull control, full maintenance burden
Server-Side GTM (sGTM)A server container receives events and forwards to Meta via a tagMarketing teams wanting one server hub for all platformsNeeds a hosted container; some complexity
CAPI GatewayA managed service that runs the server layer for youFast setup, limited dev resourcesVendor dependency; less data control
Native platform integrationThe Shopify/WooCommerce built-in CAPI connectorQuick start, simple storesLimited 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/paid webhook 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_id from the Shopify order ID on both the client Pixel and the server webhook handler so they deduplicate.
  • Capture fbp and fbc from the storefront and persist them with the order (a cart attribute or note attribute works) so the webhook handler can include them in user_data and 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:

  1. 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.
  2. 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_id or event_name do not match.
  3. 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.
  4. 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_id on both sides (derived from order ID)
  • Identical event_name casing on Pixel and server
  • Server event sent promptly (webhook or near-real-time), inside the dedup window
  • fbp and fbc cookies 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.