Privacy-First Measurement: Server-Side Tagging, Consent Mode and Conversion APIsConversion APIs: Meta, Google, TikTok and LinkedIn · Lesson 7 of 14

Meta Conversions API: payloads, matching and deduplication

Article · 8 min · 9 min lecture

Video lecture

Meta Conversions API: payloads, matching and deduplication

14 chapters · about 9 min · full transcript

Coming soon

Chapter 1 of 14

Meta Conversions API

  • Why CAPI
  • Implementation routes
  • Event anatomy
  • Deduplication
  • Match quality

The narrated lecture is in production

Every chapter is scripted and ready. Browse the chapters and read the full transcript now — the video will appear here when it’s published.

Chapters

Why the Conversions API

The Meta Conversions API (CAPI) sends web, app and offline events from your server directly to Meta. Meta recommends using it alongside the Meta Pixel, with deduplication, so that events blocked or lost in the browser still reach Meta and events carry stronger matching data. Better event coverage and matching generally improve optimization and measurement.

Implementation routes:

  • Partner integrations (Shopify, WooCommerce, other platforms) — quickest; check which events and parameters they send.
  • Conversions API Gateway — a Meta-provided, self-hosted solution for web events.
  • Server-side GTM with Meta's Conversions API tag template.
  • Direct integration via the Graph API from your backend.

Anatomy of an event

{
  "data": [{
    "event_name": "Purchase",
    "event_time": 1758787200,
    "event_id": "order_10042",
    "action_source": "website",
    "event_source_url": "https://shop.example.ae/checkout/thank-you",
    "user_data": {
      "em": ["<sha256 of normalized email>"],
      "ph": ["<sha256 of normalized phone>"],
      "external_id": ["<sha256 of your customer id>"],
      "client_ip_address": "<from request>",
      "client_user_agent": "<from request>",
      "fbp": "fb.1.1758700000000.123456789",
      "fbc": "fb.1.1758700000000.IwAR..."
    },
    "custom_data": {"currency": "AED", "value": 425.50, "order_id": "10042"}
  }]
}
  • event_time is a Unix timestamp in seconds; send events promptly (Meta limits how old website events can be — check the current window).
  • action_source describes where the conversion happened (website, app, physical_store, system_generated, etc.).
  • user_data hashed fields (em, ph, fn, ln, ct, st, zp, country, external_id) must be normalized and SHA-256 hashed; client_ip_address, client_user_agent, fbp and fbc are sent unhashed.
  • fbp is the _fbp browser cookie; fbc is the _fbc cookie or built from the fbclid URL parameter (fb.1.<timestamp>.<fbclid>).

Deduplication

When both the Pixel and CAPI send the same event, Meta deduplicates if event_name matches and event_id (server) equals eventID (browser). Generate one ID per real conversion (the order ID is ideal) and pass it to both:

// Browser (Pixel), only after advertising consent
fbq('track', 'Purchase', {value: 425.50, currency: 'AED'}, {eventID: 'order_10042'});

Hands-on: direct server integration (Python)

import os, time, hashlib, requests

PIXEL_ID = os.environ["META_PIXEL_ID"]
TOKEN = os.environ["META_CAPI_TOKEN"]
API_VERSION = os.environ.get("META_GRAPH_VERSION", "v23.0")  # set to a currently supported version
TEST_CODE = os.environ.get("META_TEST_EVENT_CODE")  # only while testing

h = lambda v: hashlib.sha256(v.strip().lower().encode()).hexdigest()

def send_purchase(order, req, consent_ads: bool):
    if not consent_ads:
        return None  # respect the user's choice for advertising
    event = {
        "event_name": "Purchase",
        "event_time": int(time.time()),
        "event_id": f"order_{order['id']}",
        "action_source": "website",
        "event_source_url": order["thank_you_url"],
        "user_data": {
            "em": [h(order["email"])] if order.get("email") else [],
            "ph": [h(order["phone_e164"].lstrip('+'))] if order.get("phone_e164") else [],
            "external_id": [h(str(order["customer_id"]))],
            "client_ip_address": req["ip"],
            "client_user_agent": req["user_agent"],
            "fbp": req["cookies"].get("_fbp"),
            "fbc": req["cookies"].get("_fbc"),
        },
        "custom_data": {"currency": order["currency"], "value": order["value"], "order_id": str(order["id"])},
    }
    event["user_data"] = {k: v for k, v in event["user_data"].items() if v}
    body = {"data": [event]}
    if TEST_CODE:
        body["test_event_code"] = TEST_CODE
    r = requests.post(f"https://graph.facebook.com/{API_VERSION}/{PIXEL_ID}/events",
                      params={"access_token": TOKEN}, json=body, timeout=10)
    if r.status_code >= 400:
        raise RuntimeError(f"CAPI error {r.status_code}: {r.text}")
    return r.json()

Check the Graph API changelog for the current version and the Conversions API parameter reference for normalization rules (e.g., phone digits with country code, no leading zeros).

Offline and CRM events

CAPI is not only for websites. With action_source set to physical_store, phone_call, system_generated or similar, you can send store purchases, phone orders and CRM stages (for example a lead that became a qualified customer). Match them on hashed email, phone and external_id, and for leads captured by Meta instant forms, include the Meta lead ID where supported so the outcome links back to the original lead. This lets lead-gen campaigns optimize toward qualified outcomes instead of raw form submissions.

Event Match Quality and diagnostics

In Events Manager, Event Match Quality (EMQ) scores how well your events' customer information can be matched. Improve it by adding hashed email/phone, external_id, fbc/fbp, IP and user agent where lawful. The Test Events tab shows events in real time (use the test code), and Diagnostics flags issues such as missing deduplication keys, invalid parameters or low coverage.

Worked example: a Jeddah abaya brand

Initially: Pixel only, blocked for a meaningful share of Safari and in-app browser traffic, EMQ "poor". After adding CAPI from the order system (with order IDs for dedup, hashed email and phone captured at checkout, fbc/fbp forwarded), EMQ improved and reported purchases moved closer to backend orders. The brand verified dedup by comparing Events Manager's "deduplicated" counts with orders.

Pitfalls

  • Different IDs in browser and server events (double counting).
  • Hashing IP address or user agent (they must not be hashed).
  • Sending events for users who refused advertising consent.
  • Sending sensitive data (health conditions, financial details) in custom_data or URLs — Meta's terms prohibit it.

How to measure success

Deduplication working (no inflated counts), EMQ trending up, reported purchases within an expected band of backend orders, and zero policy warnings about sensitive data.

Key takeaways

  • Run Meta CAPI alongside the Pixel with deduplication for better coverage and matching.
  • Dedup requires the same event_name and matching event_id/eventID — use the order ID.
  • Hash identifiers (em, ph, external_id) with SHA-256; send IP, user agent, fbp and fbc unhashed.
  • Use Test Events, Diagnostics and Event Match Quality to verify and improve.
  • Respect advertising consent and never send sensitive data.

Check your understanding

Quick questions to lock in the lesson. They don’t count towards your certificate.

  1. What must match for Meta to deduplicate a Pixel and CAPI purchase?
  2. Which user_data fields should NOT be hashed?
  3. How can you see CAPI events in real time while testing?

Put it into practice

Map your purchase flow: where the order ID is created, how it reaches the Pixel eventID and the CAPI event_id, which identifiers you can lawfully send, and how consent is checked.

Enrol for free to save your progress

Reading is always free. Enrol to keep your place, take the final assessment and earn a verifiable certificate.