Meta Conversions API with AgentFirstStack
AgentFirstStack sends each conversion to Meta twice, on purpose: from the browser through the Meta pixel with an eventID, and from the server through the Conversions API with the same event_id. Meta matches the two by event name and ID and counts the conversion once. An agent sets it up with your pixel ID and a Conversions API access token, tests it with your Test Events code, and reads Meta's answer for every delivery in the event log.
The prompt
Paste this into your coding agent. It does everything except create the access token, which only you can do in Events Manager.
Set up the Meta pixel and Conversions API for this project with AgentFirstStack Tags, deduplicated by event_id. It's a free, open-source tag manager with server-side conversion APIs, built for agents, and you can do the setup yourself: there is no signup form. 1. Read https://agentfirststack.com/llms.txt and https://tags.agentfirststack.com/llms.txt. 2. If this project's .env already has AGENTFIRSTSTACK_API_KEY, use it and read AGENTFIRSTSTACK.md. Otherwise get a key: curl -X POST https://agentfirststack.com/v1/signup, and save api_key in .env as AGENTFIRSTSTACK_API_KEY and in the production secrets. 3. Create a tags container for the production domain, put its snippet in the head of every page, and serve it through a first-party proxy on our own domain (the configs from GET /v1/containers/:id/install?proxy=1, with the proxy key kept server-side), so ad blockers don't drop the conversion hits. Deploy, and run scan. If the site already loads a Meta pixel (in the code or through Google Tag Manager), tell me where, and remove the hard-coded copy in the same change that publishes the container, so events don't count twice. 4. Find the conversions that matter here (purchases, sign-ups, leads) and run setup with platforms.meta: the pixel (dataset) ID, the Conversions API access token and, if I have one, a test_event_code. Map the visitor's email or phone where the page has it. If you have Meta tools, use them to find the pixel ID; otherwise ask me for exactly what setup lists as missing and tell me where to find each. 5. Validate the draft and fix every error, test the meta destination once and show me Meta's answer, open a preview on the conversion pages and read its log, then publish. 6. If the backend knows about conversions the browser doesn't (paid orders), send them to POST /v1/containers/:id/events with the visitor_id, and the page's afst('get','event_id') as event_id when the page also tracked it. 7. Write the container id and the env var name (never the key or token) in AGENTFIRSTSTACK.md, and create a claim link for me with POST https://agentfirststack.com/v1/claim-link. Tell me what you set up, what the test returned, what's waiting on me, and give me the claim link.
How deduplication works
Meta's rule: "We determine if events are identical based on their ID and name." The pixel's eventID has to match the Conversions API's event_id, and the pixel's event the Conversions API's event_name; events are "only deduplicated if they are received within 48 hours", and Meta "generally prefer[s] the event that is received first" (Deduplicate pixel and server events, as of 6 Nov 2026).
Here is how AgentFirstStack meets it:
- When a conversion's trigger fires (a dataLayer event, a thank-you page, a form submit, a click), the runtime creates one
event_id. - The pixel fires with it:
fbq('trackSingle', pixelId, 'Purchase', data, { eventID }), so only your pixel gets it even if the page has others. - The same event goes to
/collect, and the service sends it to the Conversions API with the sameevent_nameandevent_id. - For a conversion your backend reports, pass the page's
afst('get','event_id')along with the checkout or sign-up request and send it asevent_id. Without it, Meta sees two different events.
Standard event names come from the conversion's kind: purchase becomes Purchase, lead Lead, sign_up CompleteRegistration, contact Contact, subscribe Subscribe. Other names go out as custom events.
What the owner provides
- Pixel (dataset) ID: Events Manager > Data sources > the pixel > Settings. It's 15 or 16 digits.
- Access token: Events Manager > the pixel > Settings > Conversions API > Generate access token.
- Test event code (optional, starts with
TEST): Events Manager > Data sources > the pixel > Test events.
The pixel ID alone is enough for the browser side; add the token when you have it and run setup again, which merges. The token is sealed at rest and never returned.
Set it up by API
T=https://tags.agentfirststack.com
H="Authorization: Bearer $AGENTFIRSTSTACK_API_KEY"
J="content-type: application/json"
curl -s -X POST $T/v1/containers -H "$H" -H "$J" -d '{"domain":"example.com"}'
curl -s "$T/v1/containers/$C/install?proxy=1" -H "$H" # first-party proxy configs (keep the key server-side)
# add the proxy and the proxied <script> to <head>, deploy, then:
curl -s -X POST $T/v1/containers/$C/scan -H "$H"
curl -s -X POST $T/v1/containers/$C/setup -H "$H" -H "$J" -d '{
"platforms": {
"meta": { "pixel_id": "1234567890123456", "access_token": "EAAB...", "test_event_code": "TEST12345" }
},
"conversions": [
{ "name": "purchase", "when": { "data_layer_event": "purchase" },
"value": { "data_layer": "ecommerce.value" }, "currency": "USD",
"transaction_id": { "data_layer": "ecommerce.transaction_id" },
"email": { "data_layer": "customer.email" } },
{ "name": "lead", "when": { "form_selector": "form#contact" },
"email": { "selector": "input[type=email]" } }
]
}'
curl -s -X POST $T/v1/containers/$C/draft/validate -H "$H"
curl -s -X POST $T/v1/containers/$C/destinations/$D/test -H "$H" # Meta's answer, with the test code
curl -s -X POST $T/v1/containers/$C/publish -H "$H" -H "$J" -d '{"note":"meta pixel + capi"}'
The validator treats a Meta Purchase without a value and currency as an error, so it never goes live that way. Over MCP the same calls are tags_setup_conversion_tracking, tags_test_destination and tags_publish.
What Meta receives
The server copy goes to POST https://graph.facebook.com/v26.0/{pixel_id}/events with:
event_name,event_time,event_id,event_source_url, andaction_source: "website"(orsystem_generatedfor a backend event without a page URL and user agent).user_data: SHA-256 hashes of email, phone, first and last name, city, state, postal code, country andexternal_id(your user id), plus the visitor's IP address and user agent,fbcandfbp.custom_data: value, currency,order_idand, for shops,contents,content_idsandnum_items.
The runtime builds _fbc from the fbclid in the landing URL, and creates _fbp in Meta's format when a Meta tag is configured and the cookie is missing. Cookies are written only when consent allows. The pixel uses advanced matching by default, with the same hashed values.
Personal data is normalised the way Meta asks and hashed before it leaves the browser; the service never stores it in plain text. The event log keeps no raw IP or user agent: the IP is cut to its network, and the user agent travels only in the queued delivery until it's sent.
Test events, and what Meta does with them
With a test_event_code in the destination, the destination test and all test traffic (preview links, tabs opened with ?afsa_debug=1, backend events sent with "test": true) go to the Conversions API with that code, and show up in Events Manager > Data sources > the pixel > Test events. Without a code, nothing test-related is sent to Meta: test traffic is skipped and the destination test answers 400 test_event_code_required. In the browser, the pixel never fires in test traffic.
Meta's docs say test events still count for something: "Events sent with test_event_code are not dropped." They flow into Events Manager and are used for targeting and ads measurement (Using the Conversions API, as of 6 Nov 2026). So test sparingly: one destination test and one setup test usually tell you what you need. Real events never carry the code.
Ad blockers
The Conversions API copy is sent from AgentFirstStack's servers, so it reaches Meta even when a browser blocks Meta's own script. But the browser first has to reach AgentFirstStack: the default snippet loads from tags.agentfirststack.com and posts to it, and ad blockers can block that host. Serve the container through a path on your own domain with the proxy configs from GET /v1/containers/:id/install?proxy=1 (a Cloudflare Worker, Next.js, Vercel, Netlify or nginx; each sends the container's x-afs-proxy-key). There's no custom-domain (CNAME) option yet. Conversions your backend reports with POST /v1/containers/:id/events never pass through the browser, so they can't be blocked.
Verify it
- Destination test:
POST /v1/containers/:id/destinations/:did/testreturns{ result: { ok, http_status, response }, test_mode }, whereresponseis Meta's answer. An expired token or a wrong pixel ID shows up here. - Events Manager: the test event appears under Test events for your pixel.
- Preview:
POST /v1/containers/:id/previewruns the draft on the live site; its log shows the Meta tag astest_skipped(it would have fired) and the conversion with the platforms it went to in test mode. - After real traffic:
GET /v1/containers/:id/events?since=1hlists each conversion withfired_in_browser, what identity it carried (click ids,fbp,fbc, hashed email as true or false) and, per destination, the status, attempts, Meta's HTTP status and any error.
Consent and retries
- The Meta destination needs the visitor's
ad_user_dataconsent; without it the delivery is logged asskippedwith the reason.setupdefaults toeea_opt_in: visitors in the EEA, the UK and Switzerland start with consent denied until your banner grants it. - Meta's transient errors (network failures, 5xx and its transient error codes) are retried with backoff, up to 6 attempts over about 16 minutes. Other errors are final and shown in the event log.
- The service needs something to match on: hashed email or phone,
fbcorfbp, an external id, or the IP and user agent. With none, the delivery is skipped and the log says why.
FAQ
How does Meta avoid counting the pixel and the Conversions API event twice?
Meta treats two events as the same when their name and ID match: the pixel's event and eventID against the Conversions API's event_name and event_id, received within 48 hours. AgentFirstStack creates one event_id per conversion and uses it on both sides.
Do I still need the Meta pixel?
Meta recommends it: "we recommend that advertisers implement the Conversions API alongside their Meta Pixel" (Meta's dedup guide). The pixel sees what happens on the page; the server copy reaches Meta even when Meta's script is blocked, as long as the hit reaches AgentFirstStack (see ad blockers), and your backend can add conversions only it knows.
Do test events affect my ads?
They can. Meta's docs say events sent with test_event_code are not dropped: they flow into Events Manager and are used for targeting and ads measurement. AgentFirstStack only sends test traffic to Meta when you've set a test_event_code, and never fires the browser pixel in test traffic, so keep test runs few.
Does Meta receive my customers' emails?
Only as SHA-256 hashes. Emails, phones, names and addresses are normalised and hashed before they're sent; in the browser that happens before the data leaves the page. The service never stores plaintext personal data.
Do I need server-side GTM or a gateway to use the Conversions API?
No. The Conversions API is an HTTP API. AgentFirstStack calls it with your pixel ID and access token, so there is no tagging server or gateway to run. If you already use server-side GTM, see the Stape comparison for when to keep it.
Related
- Google Ads conversion trackingBrowser tag and server upload, tested before it counts.
- Server-side conversion trackingMeta, Google Ads, TikTok and GA4, counted once.
- Stape alternativeServer-side GTM hosting, or conversion APIs built in.
- Google Tag Manager MCP comparedEdit GTM, or let the agent run the tag manager.
- AnalyticsAdd analytics with an AI agentOne prompt, every step, and what's left for you.
- All servicesOne MCP server for every serviceAnalytics, tags, mail and replay tools on one key.