---
name: implement-conversions-api
description: >-
  Sends server-side conversion events to Meta so browser signal loss from ATT opt-outs, ad blockers, and
  Safari ITP stops deflating the pixel's event volume. Covers the three integration paths - direct Graph
  API, Conversions API Gateway (CAPIG), and partner integrations (Shopify, GTM server-side) - built on
  mads_send_conversion_event, which posts to the pixel's /events edge mirroring the browser pixel. Every
  server event carries the same event_id and event_name as its browser twin so Meta's shared-key dedup
  collapses the pair into one counted conversion, plus hashed user_data match keys (em, ph, external_id, and
  more), the correct action_source, and a test_event_code for validating events before attribution. Reach
  for it when Purchase/Lead counts trail backend truth, iOS underperforms Android, or a Sales/Leads campaign
  is being stood up. It ends at CAPI live and deduplicating cleanly - the pixel itself is
  set-up-pixel-and-standard-events; more match keys is improve-event-match-quality.
---
# Implement the Conversions API (CAPI)

## Purpose
Browser-only measurement leaks: ATT opt-outs hide iOS activity, ad blockers strip pixel calls,
and Safari ITP/ETP cap cookie life, all before Meta ever sees the event. The Conversions API
sends the same events from your server, in parallel with the pixel, so the auction and reporting
keep seeing conversions the browser alone would have lost. CAPI is not a replacement pixel; it is
a second path that must produce events identical enough to the browser's for Meta's dedup to
merge them into one counted conversion, not two.

## When to run
- A Sales/Leads campaign (`select-campaign-objective`) is being stood up and only browser signal exists.
- Events Manager's Purchase/Lead counts visibly trail known backend order/lead counts.
- iOS or Safari traffic converts at a suspiciously lower rate than Android/Chrome in reporting.
- A partner platform (e.g., Shopify) offers native CAPI and it isn't switched on yet.

## When NOT to run
- No pixel or standard events exist yet → `set-up-pixel-and-standard-events` first; there is
  nothing for CAPI to mirror.
- CAPI is already live and deduplicating, but the score in Events Manager is still low →
  `improve-event-match-quality`, not a new integration.
- The domain isn't verified for AEM → `configure-aggregated-event-measurement` (a parallel track
  that consumes these events once they exist).
- The question is whether the credit is real, not whether the event arrives →
  `run-incrementality-and-lift-tests`.

## Prerequisites
- A working pixel with standard events firing browser-side, located via `mads_list_pixels`.
- A server, CAPIG deployment, or partner integration capable of sending the event.
- Server-side (or build-time) SHA-256 hashing, plus a normalization step ahead of it.
- A `test_event_code` from Events Manager > Test Events for validation before going live.
- A decided `action_source` per event type (website, app, phone_call, physical_store, chat,
  system_generated, other).

## Procedure
1. **Confirm the pixel.** `mads_list_pixels` to get the `pixel_id` feeding the domain. IF no
   standard events fire yet THEN stop and run `set-up-pixel-and-standard-events` first.
2. **Pick the integration path.** Direct (most control, most maintenance), CAPIG (Meta-hosted
   gateway, less code, the sane default without dedicated engineering), or partner (Shopify/GTM
   server container, least code, only if that platform is already in the stack). HUMAN STEP:
   provision CAPIG or enable the partner's native CAPI toggle.
3. **Mirror the event, don't reinvent it.** For every browser standard event (PageView,
   ViewContent, AddToCart, InitiateCheckout, AddPaymentInfo, Purchase, Lead, CompleteRegistration,
   Subscribe), send the server twin with the identical `event_name` and, critically, the identical
   `event_id` generated once per real-world event and shared by both calls.
4. **Normalize, then hash `user_data`.** Lowercase and trim email/name, convert phone to E.164,
   THEN SHA-256 each before it leaves your server, never transmit raw PII. Attach `fbp`, `fbc`,
   `client_ip_address`, and `client_user_agent` unhashed alongside the hashed keys.
5. **Set `action_source` accurately.** `website` for on-site events mirroring the pixel, `app` for
   app events, `phone_call`/`physical_store`/`chat`/`system_generated`/`other` for events with no
   browser counterpart.
6. **Send via `mads_send_conversion_event` with `test_event_code` attached.** HUMAN STEP: confirm
   each event lands in Events Manager > Test Events with the expected parameters (value, currency,
   content_ids, content_type) and no error rows.
7. **Verify dedup, not double-count.** HUMAN STEP: in Events Manager, confirm a real conversion
   shows as ONE deduplicated event credited to both pixel and server, not two. Two separate counts
   means `event_id`/`event_name` mismatched between the calls, fix step 3.
8. **Clear the test code and go live.** Confirm production events keep flowing and deduplicating
   for 24-48h; report the before/after event volume as the first proof CAPI was worth the effort.

## Decision rules
- IF an event has no browser twin (in-store POS, phone sale) THEN send it CAPI-only with the true
  `action_source`, dedup doesn't apply, it's a net-new event.
- IF Purchase counts still trail backend after go-live THEN check `event_id` generation timing, it must be created before either call fires; a late-generated id is the #1 dedup failure cause.
- IF dedup shows roughly double the expected count THEN `event_id` or `event_name` differs between
  the pixel and server calls, align them.
- IF there's no engineering capacity for a direct build THEN default to CAPIG over custom server
  code, same shared-`event_id` dedup, far less maintenance.
- IF a partner platform's native CAPI exists (Shopify) THEN prefer it over a parallel direct build, one source of truth for `event_id` generation avoids drift.
- **Done means:** CAPI events post successfully with the test code cleared, hashed match keys
  present, and Events Manager shows the pixel+server pair deduplicating to one conversion per
  real event.

## Common failure modes
- Raw PII sent instead of hashed, reject at the boundary; this is a compliance issue, not a
  shortcut.
- `event_id` generated per HTTP request instead of per real-world event, refreshes then count as
  new conversions, or fail to dedup with the pixel fire.
- A 200 response is not proof of success, Meta's Graph API can return HTTP 200 with an error
  object in the body; check the payload for an `error` field, not just the status code, while
  validating with `test_event_code`.
- `action_source` set to `website` for genuinely offline events, corrupting channel attribution.
- Leaving `test_event_code` live in production, those events don't count toward optimization.
- Assuming CAPI live means EMQ is solved; CAPI without match keys still scores low.

## Related skills
- Prerequisite: `set-up-pixel-and-standard-events` (the browser twin CAPI mirrors).
- Run after: `improve-event-match-quality` (once dedup is stable, add match keys to raise EMQ).
- Feeds: `configure-attribution-settings`, `configure-aggregated-event-measurement` (both consume
  the combined pixel+CAPI event stream).
- Related: `select-campaign-objective` (Sales/Leads objectives require this signal).
