Meta Conversions API in n8n: server-side tracking without depending on the pixel
Published 10 August 2026 · 6 min read
An ad blocker, a strict cookie policy, or the simple expiry of a third-party cookie is enough to make a conversion invisible to the Meta Pixel installed on a site. The problem isn't a minor one: Garrett Johnson, Scott Shriver, and Shaoyin Du showed, in a study published in Marketing Science (2020), that an ad shown to a user who opted out of behavioral tracking sells for 52% less on average at auction — a loss in value directly tied to the degraded measurement signal. Meta's Conversions API (CAPI) addresses this by sending the conversion event directly from your server, without depending on what the visitor's browser lets through. This guide walks through building that pipeline in n8n, from the order webhook to the call to the Graph API.
Why the Conversions API changes the game
The Meta Pixel runs in the visitor's browser: an ad blocker, private browsing mode, or a restrictive third-party cookie policy (Safari's ITP, for instance) can prevent the event from ever leaving before it reaches Meta's servers. The CAPI sidesteps that dependency by sending the same type of event — a purchase, a signup, an add-to-cart — directly from your backend or your n8n instance, a channel no client-side blocker can intercept.
That reliability has a measurable effect on the quality of ad attribution itself. In an analysis covering fifteen advertising experiments run at Facebook and over 500 million observations, Brett Gordon, Florian Zettelmeyer, Neha Bhargava, and Dan Chapsky showed, in A Comparison of Approaches to Advertising Measurement (Marketing Science, 2019), that non-experimental measurement methods struggle to recover a campaign's true causal effect once the input signal is incomplete or biased. A more complete, more reliable conversion stream — what CAPI provides on top of a Pixel alone — doesn't fix everything, but it directly shrinks the share of missing signal that those measurement biases feed on.
How the Conversions API works
Concretely, the CAPI is an HTTP endpoint: a POST to https://graph.facebook.com/v26.0/{pixel-id}/events (check the current version in Meta's documentation at implementation time — Graph API versions are deprecated regularly), with a system access token as a parameter. The request body contains a data[] array where each event specifies:
event_name—Purchase,Lead,CompleteRegistration, or a custom name;event_time— the Unix timestamp of the actual action, not of the API call;event_id— a unique identifier shared with the Pixel for deduplication (more on this below);action_source—websitefor the vast majority of e-commerce use cases;user_data— the person's identifiers, hashed (see Step 2);custom_data—value,currency,content_ids, depending on the event.
Deduplication via event_id
If the Pixel already captures part of your conversions client-side, sending the same conversion a second time through the CAPI would double-count it — unless you share an identical event_id between the two sends. Meta then matches the two events and keeps only one for reporting and campaign optimization. In an n8n workflow, the simplest approach is to generate that identifier once (a UUID, via the Crypto node's Generate operation — covered in our Crypto node guide) and pass it both to the front-end Pixel script and to the server-side call.
Building the workflow in n8n
Step 1 — Trigger on the actual conversion event
The workflow starts on the business event that constitutes the conversion: a confirmed-order webhook (Shopify, WooCommerce — see our Shopify connection guide and our article on automating e-commerce orders), or a confirmed-payment webhook from Stripe as described in our article on Stripe webhooks and payment follow-ups. The key is to trigger the send on the actual confirmation of the action (payment captured, not just order created), so you don't pollute your ad statistics with conversions that never actually completed.
Step 2 — Normalize and hash user data
The user_data block accepts the customer's email (em) and phone (ph), but only in SHA-256-hashed form, after normalization: email in lowercase with no whitespace, phone in E.164 format without the leading +. A Code node placed right after the trigger prepares these fields:
const crypto = require('crypto');
const hash = (value) =>
crypto.createHash('sha256').update(value.trim().toLowerCase()).digest('hex');
const email = hash($json.customer_email);
const phone = hash($json.customer_phone.replace(/[^0-9]/g, ''));
return [{ json: { ...$json, em: email, ph: phone } }];
The Crypto node's Hash operation (SHA256, HEX output) does the exact same computation without code, if you'd rather stick to standard nodes — both approaches are equivalent, and the choice mostly comes down to how many fields you're processing in one pass. By contrast, the client IP address, the user agent, and the fbc/fbp cookies (Meta's click and browser identifiers, captured client-side) must travel in plain text: hashing them would make them useless for the identity matching Meta performs on its end.
Step 3 — Build the payload and call the API
A Set node assembles the final JSON in the format the CAPI expects, then an HTTP Request node (or n8n's native Facebook Graph API node, which natively handles the credential and API version) sends the request:
{
"data": [{
"event_name": "Purchase",
"event_time": 1754812800,
"event_id": "{{ $json.event_id }}",
"action_source": "website",
"event_source_url": "{{ $json.checkout_url }}",
"user_data": {
"em": ["{{ $json.em }}"],
"ph": ["{{ $json.ph }}"],
"client_ip_address": "{{ $json.client_ip }}",
"client_user_agent": "{{ $json.user_agent }}",
"fbc": "{{ $json.fbc }}",
"fbp": "{{ $json.fbp }}"
},
"custom_data": {
"currency": "EUR",
"value": "{{ $json.order_total }}",
"content_ids": {{ $json.product_ids }}
}
}]
}
The system access token belongs in a dedicated n8n credential, never hardcoded into the node — the same discipline covered in our guide on securing credentials and API keys. As with any external API call, an order webhook that fires twice for the same transaction (a network redelivery, a double click) needs to be filtered upstream: the same principle as webhook idempotency, applied here to the send toward Meta rather than to the business processing itself.
Step 4 — Verify with the Test Events tool
Before publishing the workflow, temporarily add a test_event_code field to the payload — visible under the Test Events tab of Meta's Events Manager, specific to your ad account. The event then shows up within seconds in Meta's interface, with the full detail of the fields received and any warnings (a malformed email, a missing event_id). Once validated, remove that field: leaving it in production would keep the event out of your real statistics.
Best practices and common pitfalls
- Never hash the IP address, user agent,
fbc, orfbp— these fields must stay in plain text, unlike personal identifiers. - Generate the
event_idonce per conversion and pass the exact same value to both the front-end Pixel and the server-side call, or every conversion gets double-counted in Meta's reports. - Trigger on the actual confirmation, not on order creation, which could still be canceled or left unpaid.
- Keep test and production environments clearly separated: a
test_event_codeleft in production silently drops real conversions from your statistics. - The same general pattern — server-side send, hashed identifiers, deduplication via a shared identifier — applies almost identically to Google Ads Enhanced Conversions and TikTok's Events API: once the first pipeline is built in n8n, the next ones reuse the same node structure with a different endpoint and payload format.
In summary
Building a Conversions API pipeline in n8n comes down to four pieces: a trigger on the actual conversion (a confirmed order or payment webhook), normalization and SHA-256 hashing of personal identifiers, a structured HTTP call to the Graph API with an event_id shared with the Pixel, and verification through the Test Events tool before going live. None of this requires a paid third-party service: a Webhook node, a Crypto or Code node, and an HTTP Request node are enough. If your n8n instance already handles your e-commerce orders or payment webhooks, adding this CAPI send only takes one more node in an existing workflow rather than a new integration built from scratch.
FAQ
Frequently asked questions
Should you disable the Meta Pixel once the Conversions API is in place?
No, the two are designed to coexist. The Pixel captures whatever the browser still lets through (ad blockers and ITP permitting), the CAPI captures the same conversion server-side, where nothing can block it. Sending the same `event_id` from both sides lets Meta automatically deduplicate and count the event only once.
Which fields need to be hashed before sending them to the Conversions API?
The personal identifiers in the `user_data` block — email (`em`), phone (`ph`), first name, last name, city, zip code — get SHA-256 hashed after normalization (lowercase, no whitespace, phone in E.164 format without the +). The client IP address, user agent, and the `fbc`/`fbp` cookies must be sent in plain text: hashing them would make them useless to Meta.
Can you use the generic HTTP Request node instead of the Facebook Graph API node?
Yes, both work. n8n's native Facebook Graph API node is convenient because it natively handles the credential and API version, but a plain HTTP Request node doing a POST to `graph.facebook.com/vXX.X/{pixel-id}/events` with the token as an `access_token` parameter produces an identical result — useful if you'd rather keep a single node type across your workflows.
How do you test an event before sending it in production?
The `test_event_code` field, visible under the Test Events tab of Meta's Events Manager, gets added to the payload for the duration of testing: the event shows up immediately in the interface without being counted in real statistics. Remove that field (or leave it empty) before publishing the workflow.
Bundle FlowKit Complet
€269