[{"data":1,"prerenderedAt":1026},["ShallowReactive",2],{"article-alternates":3,"cat-en-marketing":4},null,[5],{"_path":6,"_dir":7,"_draft":8,"_partial":8,"_locale":9,"title":10,"description":11,"publishedAt":12,"modifiedAt":12,"category":7,"i18nKey":13,"tags":14,"readingTime":20,"author":21,"body":22,"_type":1020,"_id":1021,"_source":1022,"_file":1023,"_stem":1024,"_extension":1025},"\u002Fen\u002Fmarketing\u002Fserver-side-conversions-setting-up-meta-capi-correctly","marketing",false,"","Server-Side Conversions: Setting Up Meta CAPI the Right Way","Complete guide to sGTM + Conversion API architecture, event match quality optimization, deduplication strategies, and first-party data pipelines for post-iOS 17 attribution.","2026-05-07","marketing-001-2026-05",[15,16,17,18,19],"conversion-api","server-side-gtm","attribution","meta-ads","first-party-data",8,"Roibase",{"type":23,"children":24,"toc":1011},"root",[25,33,40,86,137,143,148,153,238,284,290,331,492,505,656,691,697,723,728,775,787,799,805,810,828,834,855,874,880,920,938,956,966,992,1005],{"type":26,"tag":27,"props":28,"children":29},"element","p",{},[30],{"type":31,"value":32},"text","Since iOS 14.5, browser-based pixel measurement power has dropped 40-60%. According to Meta's 2025 Q4 data, advertisers not using CAPI average an Event Match Quality score below 3.8\u002F10. This means the algorithm lacks sufficient signals to optimize. The cookie-less era's first phase saw browser-side trackers lose ground. The second phase—where server-side architecture either works properly or fails entirely—is happening now. Setting up Meta Conversion API through sGTM is no longer optional. It's infrastructure-level required for performance marketing.",{"type":26,"tag":34,"props":35,"children":37},"h2",{"id":36},"why-the-pixel-to-capi-gap-matters",[38],{"type":31,"value":39},"Why the pixel-to-CAPI gap matters",{"type":26,"tag":27,"props":41,"children":42},{},[43,45,52,54,60,62,68,70,76,78,84],{"type":31,"value":44},"Meta Pixel runs in the browser. It depends on user consent, can't filter bot traffic, and gets hit by network latency. CAPI sends HTTP POST directly from your server to Meta. Two differences matter: timing and data quality. Pixel fires a ",{"type":26,"tag":46,"props":47,"children":49},"code",{"className":48},[],[50],{"type":31,"value":51},"PageView",{"type":31,"value":53}," event when the user loads a page; CAPI can send the same event from your backend after checkout completes. This time gap is where deduplication lives—Meta needs to merge the same event from two sources. Second difference: you control user identifiers in CAPI. Hash ",{"type":26,"tag":46,"props":55,"children":57},{"className":56},[],[58],{"type":31,"value":59},"em",{"type":31,"value":61}," (email), ",{"type":26,"tag":46,"props":63,"children":65},{"className":64},[],[66],{"type":31,"value":67},"ph",{"type":31,"value":69}," (phone), ",{"type":26,"tag":46,"props":71,"children":73},{"className":72},[],[74],{"type":31,"value":75},"fbc",{"type":31,"value":77}," (Facebook click ID), and ",{"type":26,"tag":46,"props":79,"children":81},{"className":80},[],[82],{"type":31,"value":83},"fbp",{"type":31,"value":85}," (browser ID) correctly, or Event Match Quality drops. Low EMQ means the algorithm can't fully understand which user triggered which event. This dulls bid optimization. Meta's 2024 whitepaper showed CAPI + Pixel together deliver 13% average ROAS lift (n=4200 advertisers, 60-day window). But this only happens when deduplication is set up correctly.",{"type":26,"tag":27,"props":87,"children":88},{},[89,91,97,99,105,107,113,115,121,122,128,130,135],{"type":31,"value":90},"Shutting off the pixel and going CAPI-only is also wrong. Browser pixel captures mid-funnel events like ",{"type":26,"tag":46,"props":92,"children":94},{"className":93},[],[95],{"type":31,"value":96},"ViewContent",{"type":31,"value":98}," and ",{"type":26,"tag":46,"props":100,"children":102},{"className":101},[],[103],{"type":31,"value":104},"AddToCart",{"type":31,"value":106}," in real time; CAPI typically handles only ",{"type":26,"tag":46,"props":108,"children":110},{"className":109},[],[111],{"type":31,"value":112},"Purchase",{"type":31,"value":114},". You need the middle path: keep the pixel lightweight and send critical conversions through CAPI as duplicates. Deduplication parameters prevent double-counting. Meta's system looks at the ",{"type":26,"tag":46,"props":116,"children":118},{"className":117},[],[119],{"type":31,"value":120},"event_id",{"type":31,"value":98},{"type":26,"tag":46,"props":123,"children":125},{"className":124},[],[126],{"type":31,"value":127},"event_time",{"type":31,"value":129}," combo to avoid counting the same action twice. But if you don't pass these parameters identically in both pixel and CAPI, dedup fails. Most implementations break here: frontend generates ",{"type":26,"tag":46,"props":131,"children":133},{"className":132},[],[134],{"type":31,"value":120},{"type":31,"value":136}," as a UUID, backend sends a different ID. Result: two separate events, inflated ROAS reports.",{"type":26,"tag":34,"props":138,"children":140},{"id":139},"building-the-sgtm-infrastructure",[141],{"type":31,"value":142},"Building the sGTM infrastructure",{"type":26,"tag":27,"props":144,"children":145},{},[146],{"type":31,"value":147},"You can set up CAPI without server-side Google Tag Manager—post directly from your backend to Meta. But that approach breaks at scale. Add multiple destinations (Google Ads Enhanced Conversions, TikTok Events API, Snapchat CAPI) and you'll write separate endpoints for each. sGTM provides an abstraction layer: a single server container handles all tagging needs. Host it on Google Cloud Run or App Engine. It catches HTTP requests from your client-side GTM container, fires server-side tags, then sends parallel POSTs to Meta, Google, and TikTok.",{"type":26,"tag":27,"props":149,"children":150},{},[151],{"type":31,"value":152},"Setup flow:",{"type":26,"tag":154,"props":155,"children":156},"ol",{},[157,177,203,213],{"type":26,"tag":158,"props":159,"children":160},"li",{},[161,167,169,175],{"type":26,"tag":162,"props":163,"children":164},"strong",{},[165],{"type":31,"value":166},"Create a Cloud Run instance:",{"type":31,"value":168}," ",{"type":26,"tag":46,"props":170,"children":172},{"className":171},[],[173],{"type":31,"value":174},"gcloud run deploy gtm-server --image=gcr.io\u002Fcloud-tagging-10302018\u002Fgtm-cloud-image:stable --platform=managed --region=europe-west1",{"type":31,"value":176},". This deploys Google's official sGTM image.",{"type":26,"tag":158,"props":178,"children":179},{},[180,185,187,193,195,201],{"type":26,"tag":162,"props":181,"children":182},{},[183],{"type":31,"value":184},"Get your Tagging Server URL:",{"type":31,"value":186}," After deployment, you'll have something like ",{"type":26,"tag":46,"props":188,"children":190},{"className":189},[],[191],{"type":31,"value":192},"https:\u002F\u002Fgtm-server-xxxxx-ew.a.run.app",{"type":31,"value":194},". Add this to your client-side GTM as the ",{"type":26,"tag":46,"props":196,"children":198},{"className":197},[],[199],{"type":31,"value":200},"serverContainerUrl",{"type":31,"value":202},".",{"type":26,"tag":158,"props":204,"children":205},{},[206,211],{"type":26,"tag":162,"props":207,"children":208},{},[209],{"type":31,"value":210},"Update your GA4 tag in client-side GTM:",{"type":31,"value":212}," Normally GA4 sends data straight to Google. Set the transport URL to your sGTM endpoint, and GA4 data flows through your server first. This also lets you handle IP anonymization and user-agent normalization server-side.",{"type":26,"tag":158,"props":214,"children":215},{},[216,221,223,229,230,236],{"type":26,"tag":162,"props":217,"children":218},{},[219],{"type":31,"value":220},"Add a Meta CAPI tag in sGTM:",{"type":31,"value":222}," Use the \"Meta Conversions API\" template. Enter your ",{"type":26,"tag":46,"props":224,"children":226},{"className":225},[],[227],{"type":31,"value":228},"Pixel ID",{"type":31,"value":98},{"type":26,"tag":46,"props":231,"children":233},{"className":232},[],[234],{"type":31,"value":235},"Access Token",{"type":31,"value":237},". Get the token from Events Manager > Settings > Conversions API. Test with a test event to confirm the connection.",{"type":26,"tag":27,"props":239,"children":240},{},[241,243,249,251,257,259,264,266,271,273,282],{"type":31,"value":242},"sGTM's advantage: you can fire both GA4 and CAPI from a single request. A single ",{"type":26,"tag":46,"props":244,"children":246},{"className":245},[],[247],{"type":31,"value":248},"dataLayer.push",{"type":31,"value":250}," on the client side triggers two server-side tags. No need to write separate API calls in your backend. But watch one thing: GA4's ",{"type":26,"tag":46,"props":252,"children":254},{"className":253},[],[255],{"type":31,"value":256},"client_id",{"type":31,"value":258}," isn't Meta's ",{"type":26,"tag":46,"props":260,"children":262},{"className":261},[],[263],{"type":31,"value":83},{"type":31,"value":265},". Create a transformation variable in sGTM to map the ",{"type":26,"tag":46,"props":267,"children":269},{"className":268},[],[270],{"type":31,"value":83},{"type":31,"value":272}," cookie to the CAPI tag. This mapping requires ",{"type":26,"tag":274,"props":275,"children":279},"a",{"href":276,"rel":277},"https:\u002F\u002Fwww.roibase.com.tr\u002Fen\u002Fppc",[278],"nofollow",[280],{"type":31,"value":281},"first-party data architecture",{"type":31,"value":283},"; without it, identifiers won't sync and EMQ drops.",{"type":26,"tag":34,"props":285,"children":287},{"id":286},"raising-event-match-quality",[288],{"type":31,"value":289},"Raising Event Match Quality",{"type":26,"tag":27,"props":291,"children":292},{},[293,295,300,302,307,309,315,317,322,324,329],{"type":31,"value":294},"EMQ is Meta's confidence score for \"which user gets this event.\" Max is 10. Above 8 is excellent; below 6 is problematic. Right identifier combinations raise EMQ. Meta's priority order: ",{"type":26,"tag":46,"props":296,"children":298},{"className":297},[],[299],{"type":31,"value":59},{"type":31,"value":301}," (email) > ",{"type":26,"tag":46,"props":303,"children":305},{"className":304},[],[306],{"type":31,"value":67},{"type":31,"value":308}," (phone) > ",{"type":26,"tag":46,"props":310,"children":312},{"className":311},[],[313],{"type":31,"value":314},"external_id",{"type":31,"value":316}," (CRM ID) > ",{"type":26,"tag":46,"props":318,"children":320},{"className":319},[],[321],{"type":31,"value":75},{"type":31,"value":323}," > ",{"type":26,"tag":46,"props":325,"children":327},{"className":326},[],[328],{"type":31,"value":83},{"type":31,"value":330},". Hash email and phone with SHA-256, lowercase, no whitespace. Example:",{"type":26,"tag":332,"props":333,"children":337},"pre",{"className":334,"code":335,"language":336,"meta":9,"style":9},"language-javascript shiki shiki-themes github-dark","\u002F\u002F Wrong hash\nconst email = \" John@Example.com \";\nconst hash = sha256(email); \u002F\u002F Spaces and caps cause problems\n\n\u002F\u002F Correct hash\nconst email = \"john@example.com\";\nconst hash = sha256(email); \u002F\u002F SHA-256: a665a...\n","javascript",[338],{"type":26,"tag":46,"props":339,"children":340},{"__ignoreMap":9},[341,353,386,419,429,438,463],{"type":26,"tag":342,"props":343,"children":346},"span",{"class":344,"line":345},"line",1,[347],{"type":26,"tag":342,"props":348,"children":350},{"style":349},"--shiki-default:#6A737D",[351],{"type":31,"value":352},"\u002F\u002F Wrong hash\n",{"type":26,"tag":342,"props":354,"children":356},{"class":344,"line":355},2,[357,363,369,374,380],{"type":26,"tag":342,"props":358,"children":360},{"style":359},"--shiki-default:#F97583",[361],{"type":31,"value":362},"const",{"type":26,"tag":342,"props":364,"children":366},{"style":365},"--shiki-default:#79B8FF",[367],{"type":31,"value":368}," email",{"type":26,"tag":342,"props":370,"children":371},{"style":359},[372],{"type":31,"value":373}," =",{"type":26,"tag":342,"props":375,"children":377},{"style":376},"--shiki-default:#9ECBFF",[378],{"type":31,"value":379}," \" John@Example.com \"",{"type":26,"tag":342,"props":381,"children":383},{"style":382},"--shiki-default:#E1E4E8",[384],{"type":31,"value":385},";\n",{"type":26,"tag":342,"props":387,"children":389},{"class":344,"line":388},3,[390,394,399,403,409,414],{"type":26,"tag":342,"props":391,"children":392},{"style":359},[393],{"type":31,"value":362},{"type":26,"tag":342,"props":395,"children":396},{"style":365},[397],{"type":31,"value":398}," hash",{"type":26,"tag":342,"props":400,"children":401},{"style":359},[402],{"type":31,"value":373},{"type":26,"tag":342,"props":404,"children":406},{"style":405},"--shiki-default:#B392F0",[407],{"type":31,"value":408}," sha256",{"type":26,"tag":342,"props":410,"children":411},{"style":382},[412],{"type":31,"value":413},"(email); ",{"type":26,"tag":342,"props":415,"children":416},{"style":349},[417],{"type":31,"value":418},"\u002F\u002F Spaces and caps cause problems\n",{"type":26,"tag":342,"props":420,"children":422},{"class":344,"line":421},4,[423],{"type":26,"tag":342,"props":424,"children":426},{"emptyLinePlaceholder":425},true,[427],{"type":31,"value":428},"\n",{"type":26,"tag":342,"props":430,"children":432},{"class":344,"line":431},5,[433],{"type":26,"tag":342,"props":434,"children":435},{"style":349},[436],{"type":31,"value":437},"\u002F\u002F Correct hash\n",{"type":26,"tag":342,"props":439,"children":441},{"class":344,"line":440},6,[442,446,450,454,459],{"type":26,"tag":342,"props":443,"children":444},{"style":359},[445],{"type":31,"value":362},{"type":26,"tag":342,"props":447,"children":448},{"style":365},[449],{"type":31,"value":368},{"type":26,"tag":342,"props":451,"children":452},{"style":359},[453],{"type":31,"value":373},{"type":26,"tag":342,"props":455,"children":456},{"style":376},[457],{"type":31,"value":458}," \"john@example.com\"",{"type":26,"tag":342,"props":460,"children":461},{"style":382},[462],{"type":31,"value":385},{"type":26,"tag":342,"props":464,"children":466},{"class":344,"line":465},7,[467,471,475,479,483,487],{"type":26,"tag":342,"props":468,"children":469},{"style":359},[470],{"type":31,"value":362},{"type":26,"tag":342,"props":472,"children":473},{"style":365},[474],{"type":31,"value":398},{"type":26,"tag":342,"props":476,"children":477},{"style":359},[478],{"type":31,"value":373},{"type":26,"tag":342,"props":480,"children":481},{"style":405},[482],{"type":31,"value":408},{"type":26,"tag":342,"props":484,"children":485},{"style":382},[486],{"type":31,"value":413},{"type":26,"tag":342,"props":488,"children":489},{"style":349},[490],{"type":31,"value":491},"\u002F\u002F SHA-256: a665a...\n",{"type":26,"tag":27,"props":493,"children":494},{},[495,497,503],{"type":31,"value":496},"Your CAPI ",{"type":26,"tag":46,"props":498,"children":500},{"className":499},[],[501],{"type":31,"value":502},"user_data",{"type":31,"value":504}," object should look like this:",{"type":26,"tag":332,"props":506,"children":510},{"className":507,"code":508,"language":509,"meta":9,"style":9},"language-json shiki shiki-themes github-dark","{\n  \"em\": [\"a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3\"],\n  \"ph\": [\"sha256_phone_hash\"],\n  \"fbc\": \"fb.1.1554763741205.AbCdEfGhIjKlMnOpQrStUvWxYz\",\n  \"fbp\": \"fb.1.1558571054389.1098115397\",\n  \"client_ip_address\": \"93.184.216.34\",\n  \"client_user_agent\": \"Mozilla\u002F5.0...\"\n}\n","json",[511],{"type":26,"tag":46,"props":512,"children":513},{"__ignoreMap":9},[514,522,545,566,589,610,631,648],{"type":26,"tag":342,"props":515,"children":516},{"class":344,"line":345},[517],{"type":26,"tag":342,"props":518,"children":519},{"style":382},[520],{"type":31,"value":521},"{\n",{"type":26,"tag":342,"props":523,"children":524},{"class":344,"line":355},[525,530,535,540],{"type":26,"tag":342,"props":526,"children":527},{"style":365},[528],{"type":31,"value":529},"  \"em\"",{"type":26,"tag":342,"props":531,"children":532},{"style":382},[533],{"type":31,"value":534},": [",{"type":26,"tag":342,"props":536,"children":537},{"style":376},[538],{"type":31,"value":539},"\"a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3\"",{"type":26,"tag":342,"props":541,"children":542},{"style":382},[543],{"type":31,"value":544},"],\n",{"type":26,"tag":342,"props":546,"children":547},{"class":344,"line":388},[548,553,557,562],{"type":26,"tag":342,"props":549,"children":550},{"style":365},[551],{"type":31,"value":552},"  \"ph\"",{"type":26,"tag":342,"props":554,"children":555},{"style":382},[556],{"type":31,"value":534},{"type":26,"tag":342,"props":558,"children":559},{"style":376},[560],{"type":31,"value":561},"\"sha256_phone_hash\"",{"type":26,"tag":342,"props":563,"children":564},{"style":382},[565],{"type":31,"value":544},{"type":26,"tag":342,"props":567,"children":568},{"class":344,"line":421},[569,574,579,584],{"type":26,"tag":342,"props":570,"children":571},{"style":365},[572],{"type":31,"value":573},"  \"fbc\"",{"type":26,"tag":342,"props":575,"children":576},{"style":382},[577],{"type":31,"value":578},": ",{"type":26,"tag":342,"props":580,"children":581},{"style":376},[582],{"type":31,"value":583},"\"fb.1.1554763741205.AbCdEfGhIjKlMnOpQrStUvWxYz\"",{"type":26,"tag":342,"props":585,"children":586},{"style":382},[587],{"type":31,"value":588},",\n",{"type":26,"tag":342,"props":590,"children":591},{"class":344,"line":431},[592,597,601,606],{"type":26,"tag":342,"props":593,"children":594},{"style":365},[595],{"type":31,"value":596},"  \"fbp\"",{"type":26,"tag":342,"props":598,"children":599},{"style":382},[600],{"type":31,"value":578},{"type":26,"tag":342,"props":602,"children":603},{"style":376},[604],{"type":31,"value":605},"\"fb.1.1558571054389.1098115397\"",{"type":26,"tag":342,"props":607,"children":608},{"style":382},[609],{"type":31,"value":588},{"type":26,"tag":342,"props":611,"children":612},{"class":344,"line":440},[613,618,622,627],{"type":26,"tag":342,"props":614,"children":615},{"style":365},[616],{"type":31,"value":617},"  \"client_ip_address\"",{"type":26,"tag":342,"props":619,"children":620},{"style":382},[621],{"type":31,"value":578},{"type":26,"tag":342,"props":623,"children":624},{"style":376},[625],{"type":31,"value":626},"\"93.184.216.34\"",{"type":26,"tag":342,"props":628,"children":629},{"style":382},[630],{"type":31,"value":588},{"type":26,"tag":342,"props":632,"children":633},{"class":344,"line":465},[634,639,643],{"type":26,"tag":342,"props":635,"children":636},{"style":365},[637],{"type":31,"value":638},"  \"client_user_agent\"",{"type":26,"tag":342,"props":640,"children":641},{"style":382},[642],{"type":31,"value":578},{"type":26,"tag":342,"props":644,"children":645},{"style":376},[646],{"type":31,"value":647},"\"Mozilla\u002F5.0...\"\n",{"type":26,"tag":342,"props":649,"children":650},{"class":344,"line":20},[651],{"type":26,"tag":342,"props":652,"children":653},{"style":382},[654],{"type":31,"value":655},"}\n",{"type":26,"tag":27,"props":657,"children":658},{},[659,661,667,669,674,676,682,684,689],{"type":31,"value":660},"sGTM auto-captures IP and user agent, but some hosting (Cloudflare proxy) requires parsing the ",{"type":26,"tag":46,"props":662,"children":664},{"className":663},[],[665],{"type":31,"value":666},"X-Forwarded-For",{"type":31,"value":668}," header. The ",{"type":26,"tag":46,"props":670,"children":672},{"className":671},[],[673],{"type":31,"value":75},{"type":31,"value":675}," parameter is the Facebook click ID—when a user clicks a Meta ad, the URL gets ",{"type":26,"tag":46,"props":677,"children":679},{"className":678},[],[680],{"type":31,"value":681},"fbclid=...",{"type":31,"value":683},". Write this to a cookie and send it to CAPI to close the attribution loop. Most implementations skip ",{"type":26,"tag":46,"props":685,"children":687},{"className":686},[],[688],{"type":31,"value":75},{"type":31,"value":690},", so Meta never knows which ad drove the conversion. EMQ stays at 4.2.",{"type":26,"tag":34,"props":692,"children":694},{"id":693},"deduplication-strategy",[695],{"type":31,"value":696},"Deduplication strategy",{"type":26,"tag":27,"props":698,"children":699},{},[700,702,707,709,714,716,721],{"type":31,"value":701},"When the same ",{"type":26,"tag":46,"props":703,"children":705},{"className":704},[],[706],{"type":31,"value":112},{"type":31,"value":708}," event arrives from both pixel and CAPI, Meta counts it once if ",{"type":26,"tag":46,"props":710,"children":712},{"className":711},[],[713],{"type":31,"value":120},{"type":31,"value":715}," matches. Usually a UUID v4. But if the frontend generates the ID, the backend must use it too. Solution: add ",{"type":26,"tag":46,"props":717,"children":719},{"className":718},[],[720],{"type":31,"value":120},{"type":31,"value":722}," as a hidden input during checkout or store it in localStorage. When the backend completes the order, it grabs the same ID for the CAPI request. Time gap must stay within 48 hours (Meta's dedup window). Beyond 48 hours, it's two separate events.",{"type":26,"tag":27,"props":724,"children":725},{},[726],{"type":31,"value":727},"Example flow:",{"type":26,"tag":154,"props":729,"children":730},{},[731,752,770],{"type":26,"tag":158,"props":732,"children":733},{},[734,736,742,744,750],{"type":31,"value":735},"User clicks \"Buy Now\" → pixel fires ",{"type":26,"tag":46,"props":737,"children":739},{"className":738},[],[740],{"type":31,"value":741},"InitiateCheckout",{"type":31,"value":743}," (event_id: ",{"type":26,"tag":46,"props":745,"children":747},{"className":746},[],[748],{"type":31,"value":749},"evt_12345",{"type":31,"value":751},", event_time: 1683820800)",{"type":26,"tag":158,"props":753,"children":754},{},[755,757,762,763,768],{"type":31,"value":756},"Backend approves payment → CAPI sends ",{"type":26,"tag":46,"props":758,"children":760},{"className":759},[],[761],{"type":31,"value":112},{"type":31,"value":743},{"type":26,"tag":46,"props":764,"children":766},{"className":765},[],[767],{"type":31,"value":749},{"type":31,"value":769},", event_time: 1683820802)",{"type":26,"tag":158,"props":771,"children":772},{},[773],{"type":31,"value":774},"Meta sees both, IDs match, 2-second gap → counts as one event.",{"type":26,"tag":27,"props":776,"children":777},{},[778,780,785],{"type":31,"value":779},"Without this, pixel and CAPI ",{"type":26,"tag":46,"props":781,"children":783},{"className":782},[],[784],{"type":31,"value":112},{"type":31,"value":786}," events both count. ROAS inflates. You see \"100 conversions\" on the dashboard but reality is 50. Wrong budget allocation follows.",{"type":26,"tag":27,"props":788,"children":789},{},[790,792,797],{"type":31,"value":791},"Sometimes the pixel event gets lost (ad blocker, no consent). CAPI alone works fine then. No dedup issue. But if the pixel fires late (user was offline, browser queue released the event 10 minutes later) and the ID is wrong, Meta counts it as new. Handle this edge case by locking server-side ",{"type":26,"tag":46,"props":793,"children":795},{"className":794},[],[796],{"type":31,"value":127},{"type":31,"value":798}," to your backend's order timestamp, not the user's browser clock.",{"type":26,"tag":34,"props":800,"children":802},{"id":801},"incrementality-and-testing-capi",[803],{"type":31,"value":804},"Incrementality and testing CAPI",{"type":26,"tag":27,"props":806,"children":807},{},[808],{"type":31,"value":809},"EMQ 8.5 and working dedup aren't enough. Real question: would these conversions happen without CAPI? Geo-based holdout tests or conversion lift studies answer this. Meta's Conversion Lift tool exists but needs high spend ($30k+). Alternative: simple A\u002FB test. Run CAPI on half your traffic, disable it on the other half. After 14 days, check incremental ROAS. If CAPI group performs 15% better, you've proven the infrastructure's value.",{"type":26,"tag":27,"props":811,"children":812},{},[813,815,820,821,826],{"type":31,"value":814},"Another metric: attribution windows. CAPI improves 7-day click attribution reliability because post-click events come from your backend—real users, not bots. Pixel sees 8-12% bot traffic. CAPI with server IP whitelist drops below 1%. Campaign optimization runs on cleaner signals. Some advertisers ditched the pixel entirely, CAPI-only (especially B2B lead gen). Risky for ecommerce though—you lose ",{"type":26,"tag":46,"props":816,"children":818},{"className":817},[],[819],{"type":31,"value":96},{"type":31,"value":98},{"type":26,"tag":46,"props":822,"children":824},{"className":823},[],[825],{"type":31,"value":104},{"type":31,"value":827}," signals. Dynamic retargeting audiences weaken.",{"type":26,"tag":34,"props":829,"children":831},{"id":830},"advanced-custom-events-and-offline-conversions",[832],{"type":31,"value":833},"Advanced: custom events and offline conversions",{"type":26,"tag":27,"props":835,"children":836},{},[837,839,845,847,853],{"type":31,"value":838},"CAPI isn't limited to standard events. Define custom ones and send from your backend. Examples: ",{"type":26,"tag":46,"props":840,"children":842},{"className":841},[],[843],{"type":31,"value":844},"SubscriptionRenewal",{"type":31,"value":846}," or ",{"type":26,"tag":46,"props":848,"children":850},{"className":849},[],[851],{"type":31,"value":852},"TrialStarted",{"type":31,"value":854},". Define them as custom conversions and set them as campaign optimization objectives. SaaS especially benefits: send long-term events (90-day retention, upsell) through CAPI to optimize lifetime value. Similar to Google Ads' offline conversion import.",{"type":26,"tag":27,"props":856,"children":857},{},[858,860,865,867,872],{"type":31,"value":859},"Offline conversion scenario: user fills an online lead form, sales closes the deal by phone 5 days later. Export that deal from your CRM and send it to CAPI as a ",{"type":26,"tag":46,"props":861,"children":863},{"className":862},[],[864],{"type":31,"value":112},{"type":31,"value":866},". ",{"type":26,"tag":46,"props":868,"children":870},{"className":869},[],[871],{"type":31,"value":127},{"type":31,"value":873}," will be past-dated. Meta accepts events up to 62 days old. But this event's impact on optimization is limited—algorithms optimize on real-time signals. Still necessary for reporting accuracy. Automate CRM-to-CAPI with Zapier or n8n; trigger a CAPI POST each time a deal closes.",{"type":26,"tag":34,"props":875,"children":877},{"id":876},"common-mistakes-and-fixes",[878],{"type":31,"value":879},"Common mistakes and fixes",{"type":26,"tag":27,"props":881,"children":882},{},[883,895,897,903,905,911,913,918],{"type":26,"tag":162,"props":884,"children":885},{},[886,888,893],{"type":31,"value":887},"1. Missing ",{"type":26,"tag":46,"props":889,"children":891},{"className":890},[],[892],{"type":31,"value":75},{"type":31,"value":894}," parameter:",{"type":31,"value":896}," User clicks a Meta ad, lands on your site with ",{"type":26,"tag":46,"props":898,"children":900},{"className":899},[],[901],{"type":31,"value":902},"fbclid",{"type":31,"value":904}," in the URL. If you don't store this in a cookie, you can't send it to CAPI. Fix: create a GTM cookie variable named ",{"type":26,"tag":46,"props":906,"children":908},{"className":907},[],[909],{"type":31,"value":910},"_fbc",{"type":31,"value":912},", set 90-day expiry, map it to ",{"type":26,"tag":46,"props":914,"children":916},{"className":915},[],[917],{"type":31,"value":75},{"type":31,"value":919}," in your CAPI tag.",{"type":26,"tag":27,"props":921,"children":922},{},[923,928,930,936],{"type":26,"tag":162,"props":924,"children":925},{},[926],{"type":31,"value":927},"2. Wrong email hash:",{"type":31,"value":929}," Leftover spaces or caps mean no hash match. Always ",{"type":26,"tag":46,"props":931,"children":933},{"className":932},[],[934],{"type":31,"value":935},"trim().toLowerCase()",{"type":31,"value":937}," before SHA-256.",{"type":26,"tag":27,"props":939,"children":940},{},[941,946,948,954],{"type":26,"tag":162,"props":942,"children":943},{},[944],{"type":31,"value":945},"3. Still in test mode:",{"type":31,"value":947}," Test events show in Events Manager's \"Test Events\" tab but real traffic doesn't flow. Remove the ",{"type":26,"tag":46,"props":949,"children":951},{"className":950},[],[952],{"type":31,"value":953},"test_event_code",{"type":31,"value":955}," parameter and use your production token.",{"type":26,"tag":27,"props":957,"children":958},{},[959,964],{"type":26,"tag":162,"props":960,"children":961},{},[962],{"type":31,"value":963},"4. Ignoring server container logs:",{"type":31,"value":965}," sGTM Cloud Run logs show CAPI responses. Anything other than 200 OK (401, 400, etc.) means bad token or payload.",{"type":26,"tag":27,"props":967,"children":968},{},[969,974,976,982,984,990],{"type":26,"tag":162,"props":970,"children":971},{},[972],{"type":31,"value":973},"5. Data type mismatch between pixel and CAPI:",{"type":31,"value":975}," Pixel sends ",{"type":26,"tag":46,"props":977,"children":979},{"className":978},[],[980],{"type":31,"value":981},"value",{"type":31,"value":983}," as float, CAPI as integer. Meta may round currency. Fix: use ",{"type":26,"tag":46,"props":985,"children":987},{"className":986},[],[988],{"type":31,"value":989},"value: parseFloat(orderTotal).toFixed(2)",{"type":31,"value":991}," on both.",{"type":26,"tag":27,"props":993,"children":994},{},[995,997,1003],{"type":31,"value":996},"One last point: CAPI setup isn't \"set and forget.\" iOS updates, Meta API version changes, new identifier types (",{"type":26,"tag":46,"props":998,"children":1000},{"className":999},[],[1001],{"type":31,"value":1002},"anon_id",{"type":31,"value":1004}," entered beta in 2025)—all require ongoing maintenance. Track EMQ monthly; if it drops below 8, review identifier mapping. Check deduplication rate too: should be 95%+ (95% of pixel+CAPI events successfully deduped). You won't find this in Meta Events Manager; build your own log pipeline. Write request IDs from sGTM to BigQuery, then compare.",{"type":26,"tag":1006,"props":1007,"children":1008},"style",{},[1009],{"type":31,"value":1010},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":9,"searchDepth":388,"depth":388,"links":1012},[1013,1014,1015,1016,1017,1018,1019],{"id":36,"depth":355,"text":39},{"id":139,"depth":355,"text":142},{"id":286,"depth":355,"text":289},{"id":693,"depth":355,"text":696},{"id":801,"depth":355,"text":804},{"id":830,"depth":355,"text":833},{"id":876,"depth":355,"text":879},"markdown","content:en:marketing:server-side-conversions-setting-up-meta-capi-correctly.md","content","en\u002Fmarketing\u002Fserver-side-conversions-setting-up-meta-capi-correctly.md","en\u002Fmarketing\u002Fserver-side-conversions-setting-up-meta-capi-correctly","md",1778164175958]