Naar de inhoud

GidsenAdvertentieplatformen10 min leestijd

Meta CAPI-deduplicatie: hoe event_id echt werkt

Hoe Meta browser-Pixel- en server-CAPI-events koppelt via event_id en event_name, waarom mismatches purchases dubbel tellen, en hoe je dedup verifieert.

Gepubliceerd
Gecontroleerd
Twee overeenkomende event-ID’s, één getelde aankoop.

Daniil MaximkinProduct & Solutions Engineer

Kort antwoord

Meta dedupliceert een browser-Pixel-event en zijn server-Conversions-API-tweeling wanneer beide dezelfde event_id en dezelfde event_name dragen, ontvangen binnen 48 uur. Als de id's verschillen, per paginalaad opnieuw worden gegenereerd, of één kant ze weglaat, houdt Meta beide aan — wat het aantal purchases en de ROAS opblaast. Genereer één id per event en geef die door aan de eventID-optie van fbq en aan het event_id-veld op de server.

— Daniil

Belangrijkste punten

  • Deduplicatie vereist een overeenkomende event_id ÉN event_name in beide bronnen. Zit je fout bij een van de twee, dan tellen beide events mee.
  • Het dedup-venster is 48 uur vanaf het eerste event dat Meta ontvangt met een gegeven event_id.
  • Op Shopify is de gebruikelijke oorzaak dat twee systemen dezelfde purchase afvuren: het native Facebook & Instagram-kanaal plus een CAPI via GTM of een app, of een CAPI-app die naast de thema-pixel draait.
  • Verifieer in Test Events van Events Manager, waar gekoppelde paren het label "Deduplicated" dragen. Het geaggregeerde dashboard verbergt het probleem.
  • Gebruik een stabiele id die aan de bestelling is gekoppeld, geen nieuwe UUID per paginalaad, zodat browser en server het eens blijven bij herladen en retries.
In deze gids

Controleer eerst één Purchase, zonder iets te wijzigen

Lijkt Meta een aankoop twee keer te tellen? Begin met één bekende aankoop en de events die al beschikbaar zijn in Events Manager of je integratielogs. Schakel geen werkende integratie uit en plaats geen nieuwe betaalde bestelling alleen voor deze controle.

  1. Noteer de bronnen. Welke native kanalen, apps en GTM-containers versturen Purchase, en naar welke dataset?
  2. Vergelijk één paar. Noteer voor dezelfde aankoop de browser-eventID, server-event_id en eventnamen. Geef per veld aan: gelijk, verschillend of niet beschikbaar. Raad ontbrekende waarden niet.
  3. Scheid observatie en diagnose. Twee ontvangen records of een verschil tussen de totalen van Meta en Shopify bewijzen op zichzelf niet dat een aankoop dubbel is geteld. Bewaar de eventdetails en de periode voor verder onderzoek.

Verschillen de waarden of verbergt een app ze? Bereid een aanvraag voor een Meta Purchase-controle voor. Zo neem je deze observaties mee, zonder klantgegevens te versturen of eerst de configuratie te wijzigen.

Hoe koppelt Meta een browser-event aan zijn server-tweeling?

De Conversions API van Meta is ontworpen om naast de browser-Pixel te draaien, niet in plaats ervan. Beide zijn bedoeld om dezelfde actie in de echte wereld te beschrijven vanuit twee gezichtspunten: de Pixel vuurt af vanuit de browser van de koper, de Conversions API stuurt hetzelfde event vanaf je server.

Beide versturen is de aanbevolen opzet, omdat elk de blinde vlekken van de ander dekt — de browser vangt events die een server zou kunnen missen, de server vangt events die een adblocker of een afgeknotte cookie in de browser om zeep helpen.

Matchtabel: browser-events en server-events gekoppeld via een gedeelde sleutel; één ongekoppeld event gemarkeerd

De adder onder het gras is dat Meta nu twee registraties heeft van één purchase. Deduplicatie is het mechanisme dat ze weer tot één samensmelt. Als het werkt, krijg je de dekking van twee bronnen en het aantal van één. Als het faalt, krijg je het aantal van twee, en elk cijfer stroomafwaarts — conversievolume, kosten per purchase, ROAS — klopt niet, en wel in de richting die slechte campagnes er goed laat uitzien.

Deduplicatie is geen automatische magie. Het is een match op sleutels die jij correct moet instellen.

Waarop matcht Meta om Pixel- en CAPI-events te dedupliceren?

De primaire deduplicatiemethode van Meta vergelijkt twee velden tussen het browser- en het server-event:

  • event_id — een unieke identifier voor het specifieke event. In de Pixel wordt hij doorgegeven als eventID; in de Conversions API-payload is het het veld event_id. Dezelfde waarde, twee verschillende schrijfwijzen.
  • event_name — het standaard event, bijvoorbeeld Purchase. Het event van de Pixel moet gelijk zijn aan de event_name van de Conversions API.

Als een browser-event en een server-event dezelfde event_id en dezelfde event_name delen, en Meta het tweede binnen 48 uur na het eerste ontvangt, worden ze als één event behandeld. Volgens de documentatie van Meta gebeurt matching op event id plus event name samen — het ene zonder het andere telt niet.

Dit is wat er in elk faalgeval daadwerkelijk gebeurt:

  • event_id verschilt tussen de twee bronnen. Geen match. Meta registreert twee conversies. Eén purchase wordt er twee.
  • event_id wordt bij elke paginalaad opnieuw gegenereerd. Browser en server dragen nooit tegelijk dezelfde waarde, dus niets matcht. Dit is de meest voorkomende zelfveroorzaakte bug.
  • event_id ontbreekt aan één kant. Niets om tegen te matchen. Beide tellen mee.
  • event_name verschilt (Purchase versus purchase). Id’s kunnen perfect matchen en toch faalt het, omdat de naam deel uitmaakt van de sleutel.
  • Het tweede event komt na 48 uur binnen. Buiten het venster heeft Meta het eerste al afgerond; de late tweeling telt op zichzelf mee.

Het zakelijke gevolg heeft altijd dezelfde vorm: opgeblazen conversieaantallen voeden een opgeblazen ROAS. Een campagne die een ROAS van 4,0 rapporteert terwijl hij dubbel telt, draait in werkelijkheid op 2,0. Je schaalt hem op omdat het dashboard zegt dat hij wint, en je pompt budget in een campagne die hoogstens quitte speelt. Dubbele events voegen niet alleen ruis toe — ze belonen actief je slechtste beslissingen.

Waarom vuurt Shopify Meta Purchase-events dubbel af?

Op Shopify komt duplicatie zelden van één verkeerd geconfigureerde tag. Het komt van twee systemen die allebei denken dat ze eigenaar zijn van het Purchase-event, zonder van elkaar te weten. De terugkerende combinaties:

  • Native Facebook & Instagram-kanaal + een CAPI via GTM of app. Het native kanaal verstuurt zijn eigen browser- en server-events. Voeg een tweede server-route toe via GTM of een app en je hebt nu twee Purchase-events per bestelling zonder gedeelde id.
  • Een CAPI-app + de thema-/Customer Events-Pixel. De app verstuurt server-side Purchase, de storefront-Pixel verstuurt browser-Purchase, en de app genereert zijn eigen id’s die de Pixel nooit ziet.
  • Twee CAPI-apps tegelijk geïnstalleerd — vaak een overblijfsel van een eerdere opzet die nooit werd verwijderd. Beide vuren af; geen van beide coördineert.
  • checkout.liquid-Pixel + een Customer Events-Pixel tijdens een halfafgeronde migratie naar checkout extensibility, waarbij beide op de bedankpagina overleven.

De rode draad: elke bron kan afzonderlijk correct zijn en de combinatie verdubbelt je data toch, omdat deduplicatie alleen werkt als de bronnen het eens zijn over event_id. Twee brave bronnen die nooit id’s uitwisselen zijn erger dan één, omdat ze compleet lijken terwijl ze stilletjes dubbel tellen. Als je aan het uitzoeken bent welke bronnen actief zijn, doorloopt de Meta CAPI-deduplicatiediagnose de inventarisatie stap voor stap.

Diagnostische beslisboom

Wat je ziet in Events ManagerWaarschijnlijke oorzaakWat er gebeurtOplossing
Twee Purchase-rijen per bestelling, beide “Processed”, geen “Deduplicated”-labelTwee bronnen zonder gedeelde idevent_id ontbreekt of verschilt bij elke bronRoute beide via één id-bron; geef dezelfde waarde door aan Pixel en server
Paren matchen in Test Events maar het productieaantal blijft ~2× de bestellingenevent_id opnieuw gegenereerd per paginalaadId’s matchen alleen binnen één laadbeurt, niet tussen browser en serverLeid event_id af van het order-ID, niet van Date.now() of een nieuwe UUID
Server-events tellen apart; browser-events zien er goed uitevent_name-mismatchServer stuurt purchase/PURCHASE, Pixel stuurt PurchaseStandaardiseer aan beide kanten op Purchase (hoofdlettergevoelig)
Sommige bestellingen dedupliceren, andere nietRace voorbij het 48-uursvenster, of incidentele server-retries met nieuwe id’sLate of herkeyde tweeling valt buiten de matchStabiliseer de id; zorg dat de server prompt verstuurt
Aantallen kloppen in het aggregaat maar EMQ is laagGeen dedup-probleemDedup is prima; user-data-verrijking is dunApart probleem — zie event match quality

Hoe inspecteer ik deduplicatie in Events Manager?

Je kunt dit niet diagnosticeren vanaf het hoofddashboard — dat toont totalen, en totalen verbergen dubbeltellingen. Ga naar de event-bron en open Test Events:

  1. Selecteer in Events Manager je dataset (Pixel), open het tabblad Test Events, en kopieer de testcode.
  2. Trigger een echte testpurchase (of gebruik de test-event-code in het test_event_code-veld van je server-payload, zodat ook het server-event verschijnt).
  3. Kijk naar de live feed. Een correct gekoppeld event verschijnt met een “Deduplicated”-indicator, en als je het uitklapt zie je dat het van zowel Browser als Server is ontvangen met dezelfde event_id.
  4. Zie je in plaats daarvan twee losse Purchase-vermeldingen — één Browser, één Server — zonder dedup-label, dan komen je id’s of namen niet overeen.
  5. Open voor historische data een individueel event in Overview/het eventdetail en controleer de connection method. Een gezonde koppeling rapporteert zowel browser als server; twee onafhankelijke rijen per bestelling is het teken.

Het signaal waar je naar op zoek bent, is letterlijk het label “Deduplicated” op een browser+server-paar. De afwezigheid ervan, terwijl je beide verstuurt, is de bug.

Een schets van een correcte implementatie

De hele oplossing is één idee: genereer de event_id één keer, koppel hem aan de bestelling, en geef exact dezelfde string aan zowel de Pixel als de server. Hier is een technisch correcte vorm.

Browser (Customer Events-pixel of thema), waarbij eventID als vierde argument aan fbq wordt doorgegeven:

// Eén id per purchase, afgeleid van de bestelling zodat hij identiek is op de server.
// NIET Date.now(), NIET een nieuwe crypto.randomUUID() bij elke laadbeurt.
const eventId = `purchase_${order.id}`; // bijv. "purchase_4512890"

fbq('track', 'Purchase', {
  value: 129.00,
  currency: 'USD',
  contents: [{ id: 'SKU-123', quantity: 1 }],
  content_type: 'product'
}, { eventID: eventId });

Server (Conversions API-payload), met dezelfde waarde in het snake_case-veld event_id en dezelfde event_name:

{
  "data": [
    {
      "event_name": "Purchase",
      "event_time": 1690000000,
      "event_id": "purchase_4512890",
      "action_source": "website",
      "event_source_url": "https://store.example/checkout/thank-you",
      "user_data": {
        "em": ["<sha256 of lowercased, trimmed email>"],
        "fbp": "fb.1.1690000000.1234567890",
        "fbc": "fb.1.1690000000.AbCdEfGh"
      },
      "custom_data": { "value": 129.00, "currency": "USD" }
    }
  ]
}

Drie details doen al het werk: de event_id-strings zijn byte voor byte identiek, de event-namen zijn beide exact Purchase (hoofdletters doen ertoe), en de id is afgeleid van de bestelling zodat een paginaherlaad hem niet kan veranderen. De velden fbp/fbc/em verbeteren de matchkwaliteit maar zijn niet wat dedupliceert — dat is alleen event_id + event_name.

Hoe ik dit verifieer in echte implementaties

Openheid: ik bouw server-side pipelines voor de kost en ben de oprichter van Fixel Pixel, dus ik heb een voorkeur voor server-side opzetten — precies daarom verifieer ik dedup met aantallen, niet met beweringen.

Mijn protocol bij elk CAPI-traject:

  1. Inventariseer eerst elke bron. Voordat ik code aanraak, zet ik elk systeem op een rij dat Purchase kan afvuren: het native Facebook-kanaal, elke CAPI-app, GTM web, server-GTM, thema-pixels. Duplicatie is bijna altijd een extra bron die niemand zich herinnerde, geen kapotte tag.
  2. Draai parallel en lees Test Events, niet het dashboard. Ik vuur een echte purchase af met een test_event_code op de server zodat beide tweelingen verschijnen, en bevestig daarna letterlijk het label “Deduplicated” op het gekoppelde paar.
  3. Reconcilieer aantallen tegen bestellingen. Voor een vaste periode vergelijk ik het door Meta gerapporteerde Purchase-aantal met het werkelijke aantal Shopify-bestellingen. Een gezonde opzet komt dicht bij 1:1 uit; een aantal dat rond 2× de bestellingen ligt, is de vingerafdruk van een gemiste dedup, zelfs als individuele testevents er goed uitzien.
  4. Bevestig dat de id stabiel is. Ik herlaad de bedankpagina en trigger opnieuw om te controleren dat de event_id niet verandert tussen laadbeurten. Verschuift hij, dan faalt de productie-dedup, ook al slaagde een enkele schone test.

De reconciliatiestap is degene die mensen overslaan, en het is de enige die incidentele duplicatie opvangt.

Veelvoorkomende faalpatronen

  • Id’s opnieuw gegenereerd per paginalaad. event_id opgebouwd uit Date.now(), Math.random(), of een nieuwe UUID bij het renderen. Elke laadbeurt levert een nieuwe waarde op, dus de server-tweeling matcht nooit. Leid hem af van de bestelling.
  • De afweging order-ID versus willekeurige UUID. Een aan de bestelling ontleende id is stabiel en gratis te reproduceren op de server, maar is raadbaar en herhaalt zich als dezelfde bestelling opnieuw rendert — meestal geen probleem voor dedup. Een willekeurige UUID is niet te raden, maar werkt alleen als je hem één keer genereert en bewaart zodat beide kanten dezelfde waarde uitlezen; twee keer gegenereerd, garandeert hij duplicatie. De meeste Shopify-opzetten zijn veiliger met de aan de bestelling ontleende id.
  • Afwijkende hoofdletters en namen. purchase versus Purchase, of een custom event aan de ene kant en een standaard event aan de andere.
  • Verwarring tussen camelCase en snake_case. eventID in de Pixel, event_id in de server-payload. Het versturen van event_id naar fbq (of eventID in de server-JSON) doet stilletjes niets.
  • Het 48-uursvenster. Gebundelde of vertraagde server-verzendingen die meer dan 48 uur na het browser-event binnenkomen, dedupliceren niet.
  • Verwijderde apps die blijven afvuren. Een verwijderde app waarvan het pixel-snippet of de webhook in het thema blijft hangen.

Beperkingen

Deduplicatie reconcilieert alleen hetzelfde event dat twee keer is beschreven. Het voegt geen werkelijk verschillende events samen, en het redt geen opzet waarin de twee bronnen verschillende dingen beschrijven (bijvoorbeeld: de browser vuurt af op de bedankpagina terwijl de server afvuurt bij het vastleggen van de betaling met een andere id — dat zijn twee events, correct geteld).

Het is bovendien beperkt tot Meta; Meta schoonmaken doet niets voor GA4 of Google Ads, die hun eigen telregels hebben. En een perfect dedup-percentage zegt niets over matchkwaliteit — je kunt foutloos dedupliceren terwijl je events verstuurt die Meta nauwelijks kan toeschrijven. Dedup gaat over één keer tellen; matching is een aparte discipline.

Alternatieven

Als je geen gedeelde event_id kunt doorgeven — bijvoorbeeld een dichtgetimmerde app die hem niet blootgeeft — matcht de fallback van Meta op fbp of external_id plus event_name, maar alleen als het browser-event eerst binnenkomt en beide kanten de identifier consequent dragen. Behandel dat als een vangnet, geen plan.

Het duurzamere alternatief is architecturaal: consolideer naar één server-route zodat er één plek is die de id bezit, en laat de browser-Pixel die vervolgens spiegelen. Dat maakt meestal deel uit van een bredere opzet van server-side tracking of Meta Conversions API, en daarom geef ik over het algemeen de voorkeur aan één goed geïnstrumenteerde pipeline boven drie half gecoördineerde. De mechaniek van die pipeline staat in Server-Side Tracking: voordelen, grenzen en architectuur.

Hulp nodig bij het controleren van Meta Purchase?

Gebruik deze beschrijving om de bronnen en eventidentifiers te laten onderzoeken. De volgende stap is vaststellen wat controleerbaar is en eventuele implementatie afspreken. Je hoeft niet eerst een nieuwe CAPI-app te kiezen.

Vul in wat je weet; geef aan wat nog onbekend is. Voeg geen toegangstokens, klantgegevens of volledige payloads toe. Scope en prijs spreken we af voordat betaald werk begint.

Er opent een bewerkbaar concept. Je controleert de samenvatting voordat je verstuurt.

Task openen zonder deze beschrijving over te nemen ↗

Als de gids geen uitsluitsel gafEUR 375

Ik doe deze afstemming op je winkel alleen-lezen, voor EUR 375 — schriftelijk oordeel twee werkdagen na de kickoff.

Vraag een Health Check aan

Vragen

Vragen die deze gids beantwoordt

Moet event_id het Shopify order-ID zijn of een willekeurige UUID?

Kies bij voorkeur een waarde die is afgeleid van het order-ID. Die is hetzelfde op de browser en de server zonder dat je een token tussen beide hoeft door te geven, en blijft stabiel als de bedankpagina herlaadt. Een willekeurige UUID werkt alleen als je hem één keer genereert en exact dezelfde string aan beide kanten deelt; zodra een van beide kanten hem opnieuw genereert, breekt de dedup.

Mijn events zeggen 'Processed' maar nooit 'Deduplicated' — is dat erg?

Ja, als je dezelfde conversie zowel vanaf de browser als vanaf de server verstuurt. 'Deduplicated' is het label dat je wilt zien op een gekoppeld paar. Als beide tweelingen alleen 'Processed' tonen, telt Meta ze apart, wat purchases dubbel telt en de ROAS opblaast.

Moet event_name echt ook overeenkomen?

Ja. Meta matcht op event_id plus event_name samen. Een browser-'Purchase' en een server-'purchase' of 'PURCHASE' zullen niet dedupliceren omdat de namen verschillen. Standaard event-namen zijn hoofdlettergevoelig; stuur 'Purchase' aan beide kanten.

Zullen fbp of external_id voor mij dedupliceren als ik event_id weglaat?

Gedeeltelijk en onbetrouwbaar. Meta kan terugvallen op matchen via fbp of external_id plus event_name, maar alleen als het browser-event eerst binnenkomt en beide kanten consequent dezelfde identifier dragen. Het is een vangnet, geen ontwerp. Stuur een gedeelde event_id.

Ik gebruik een CAPI-app van EUR 9 per maand en het native Facebook-kanaal. Vuur ik dubbel af?

Heel waarschijnlijk. Beide versturen Purchase, en tenzij ze een gedeelde event_id coördineren — wat de meeste combinaties van app plus native niet doen — ontvangt Meta twee ongekoppelde Purchase-events per bestelling. Kies één server-route en zorg dat die id's deelt met de browser-Pixel.

Daniil Maximkin

Hoi, ik ben Daniil.

Ik werk met je samen, van probleemdefinitie tot implementatie en overdracht. Je praat met degene die het werk doet. Ik werk in het Engels en het Russisch.

Geprobeerd en kom je er nog niet uit?

Beschrijf je taak

Het eerste antwoord is gratis, binnen één werkdag. Of schrijf rechtstreeks: next@taskfordaniel.com