Architektur

Welcher der 10 Kanäle bekommt welches Event

Ein Event landet im Dispatcher von Beaconry. Zehn Kanäle schauen zu. Keiner bekommt jedes Event. GA4 will den vollen Funnel, Meta will Conversions plus ein paar Mid-Funnel-Signale, X Ads will nur die fünf Conversion-Typen, die du vorkonfiguriert hast. Das Routing ist nicht ad-hoc pro Dispatch-Methode, es sind zwei kleine Datenstrukturen in einer Datei. Das ist die ganze Karte, verifiziert gegen den Quellcode.

Lesezeit: ca. 9 MinVeröffentlicht: 2026-06-08

Das Problem, das Routing löst

Jede Conversion-API hat ihr eigenes Vokabular an Standard-Events. Meta kennt Purchase und AddToCart. TikTok kennt Purchase und InitiateCheckout. Pinterest kennt checkout und view_content. Sie überlappen sich stark, aber nicht vollständig, und genau in den Lücken brechen naive Tracking-Setups leise auseinander.

Zwei Failure-Modes treten auf, wenn du blind fan-outest. Erstens, Double-Counting: eine WooCommerce-Shop-Seite, die Produkte auflistet, feuert sowohl view_item_list als auch view_item, und bei Meta mappen beide auf denselben Event-Namen (ViewContent). Schick beide, und Meta empfängt zwei ViewContent-Events mit unterschiedlichen event_ids für einen einzigen Page-View. Kein Dedup greift (unterschiedliche ids), also zählen Reporting-Counter und Smart-Bidding beide doppelt. Zweitens, Custom-Event-Rauschen: schick Meta ein scroll-Event, und es gibt keinen Standard-Namen dafür, also landet es als generisches Custom Event, das den Events Manager verschmutzt und null Optimierungswert trägt.

Das Ziel ist also präzise: schick jeder Plattform genau die Events, für die sie ein echtes, optimierbares Zuhause hat, und nichts sonst.

Zwei Datenstrukturen, eine Datei

Das gesamte Routing lebt in class-bcnr-forwarder.php. Es gibt kein Per-Channel-Routing, das über die Dispatch-Methoden verstreut ist. Zwei Konstanten tragen die Entscheidung, und die zentrale dispatch()-Schleife liest sie aus.

1. ANALYTICS_ONLY_EVENTS: Verhaltenssignale, die nie eine echte Ad-Conversion sind.

private const ANALYTICS_ONLY_EVENTS = [
    'form_start',
    'form_abandon',
];

2. SKIP_BY_CHANNEL: eine Per-Channel-Liste von GA4-Event-Namen, die dieser Kanal nicht empfangen sollte, jeder Eintrag mit dem Grund annotiert. Metas Liste enthält zum Beispiel view_item_list, user_engagement, refund und die Engagement-Signale (scroll, video_start, file_download, click).

Die Dispatch-Schleife ist dann fast langweilig zu lesen, was genau der Punkt ist:

// Central internal-vs-ad-vendor gate.
if ( in_array( $event['name'], self::ANALYTICS_ONLY_EVENTS, true ) ) {
    if ( self::has_ga4() ) {
        self::dispatch_ga4( $event );
        self::after_dispatch( 'ga4', $event );
    }
    return; // hard-stop before any ad channel
}

if ( self::has_ga4() ) { /* GA4 gets everything */ }

if ( self::has_meta() && self::should_dispatch_to_channel( 'meta', $event ) ) {
    self::dispatch_meta( $event );
    self::after_dispatch( 'meta', $event );
}
// ...same shape for tiktok, pinterest, x_ads, snapchat, reddit

should_dispatch_to_channel() ist der Türsteher für die Browser-Pixel-fähigen Kanäle. Es prüft das SKIP_BY_CHANNEL-Set des Kanals, respektiert einen Per-Form-Override name_<channel> (ein Lead-Formular kann ein normalerweise übersprungenes Event wieder hereinholen) und wendet eine Hybrid-Mode-Regel für user_engagement an (mehr dazu weiter unten).

Die Commerce-Matrix

Hier ist das vollständige Routing für den WooCommerce-10-Event-Funnel. Eine Zelle zeigt den Event-Namen, der tatsächlich auf der Leitung gesendet wird, oder "skip", wenn der Kanal ihn nicht empfängt. Die Spalten sind die sieben Kanäle, die nach Event-Namen routen. Die drei OAuth-Broker-Kanäle (LinkedIn, Google Ads, Microsoft Ads) funktionieren anders und bekommen ihren eigenen Abschnitt.

GA4-EventGA4MetaTikTokPinterestSnapchatRedditX Ads
view_itemViewContentViewContentview_contentVIEW_CONTENTVIEW_CONTENTskip
view_item_listskipskipview_categoryskipskipskip
view_cartViewCart (custom)skipskipskipskipskip
add_to_cartAddToCartAddToCartadd_to_cartADD_CARTADD_TO_CARTaddtocart
remove_from_cartRemoveFromCart (custom)skipskipskipskipskip
searchSearchSearchsearchSEARCHSEARCHskip
begin_checkoutInitiateCheckoutInitiateCheckoutinitiate_checkoutSTART_CHECKOUTCUSTOMskip
add_payment_infoAddPaymentInfoAddPaymentInfoadd_payment_infoADD_BILLINGCUSTOMskip
purchasePurchasePurchasecheckoutPURCHASEPURCHASEpurchase
refundskipskipskipskipskipskip

Ein paar Zellen verdienen das Warum, denn das Warum ist das ganze Design.

view_item_list: fast überall übersprungen

Nur GA4 und Pinterest empfangen es. GA4 hat ein echtes view_item_list-Event. Pinterest hat ein eigenes view_category-Event, also mappt es sauber. Alle anderen (Meta, TikTok, Snap, Reddit) mappen es auf denselben Namen wie view_item (ViewContent / VIEW_CONTENT), was bedeutet, dass eine einzelne Shop-Seite, die ein Produkt zeigt, zwei gleichnamige Events mit unterschiedlichen event_ids feuern würde. Das ist die Double-Count-Falle, also ist view_item_list im Skip-Set für alle vier.

view_cart und remove_from_cart: Meta-custom, sonst verworfen

Keines hat ein Standard-Event bei den meisten Plattformen. Meta akzeptiert sie als benannte Custom Events (ViewCart, RemoveFromCart), weil ein Custom-Meta-Event weiterhin sauber im Events Manager erscheint und fürs Audience-Building genutzt werden kann. Bei TikTok, Pinterest, Snap und Reddit werden sie übersprungen: ihre Custom-Event-Fallbacks würden entweder mit einem reservierten Slot kollidieren (Snaps CUSTOM_EVENT_1 ist für Form-Leads reserviert) oder still verschluckt werden (TikTok verwirft ungemappte Namen), das Event würde also Bytes kosten und nichts einbringen. Smart-Bidding optimiert auf AddToCart und Purchase, nicht auf Cart-Removes.

begin_checkout an Reddit: ein echtes Custom Event, mit Absicht

Reddits Standard-Vokabular hat kein Checkout-Initiation-Event. Statt es zu überspringen, lässt Beaconry es in Reddits CUSTOM-tracking_type durchfallen, mit custom_event_name: 'begin_checkout'. Das ist eine bewusste Wahl, kein Versehen: ein benanntes Custom Event bei Reddit ist weiterhin ein nutzbares, reportbares Signal, anders als das Verschluckt-bei-Ankunft-Schicksal, das dasselbe Event bei TikTok ereilen würde. Die allgemeine Regel lautet "überspringe, was Rauschen wäre, behalte, was ein nutzbares Custom Event ist", und die Linie verläuft pro Plattform an einer anderen Stelle, weil jede Plattform ungemappte Namen unterschiedlich handhabt.

X Ads: nur Conversions

X (Twitter) Ads verlangt eine vorkonfigurierte conversion_event_id aus seinem Events Manager für jeden Event-Typ. Es gibt keinen generischen Event-Stream, in den man Mid-Funnel-Signale schieben könnte. Also ist X' effektive Karte nur fünf Slots (purchase, add_to_cart, lead, signup, pageview), und der Rest des Funnels (view_item, view_cart, search, begin_checkout, add_payment_info) hat schlicht keinen Slot, in den er gehen könnte. Die Engagement-Signale sind aus demselben Grund in seinem Skip-Set.

refund: GA4 und nirgendwo sonst

Refund ist das sauberste Beispiel für "schick jeder Plattform, was sie nutzen kann", denn die Antwort für neun der zehn Kanäle lautet "nichts".

GA4 hat ein erstklassiges refund-Event, das Umsatz gegen den ursprünglichen Kauf zurückverrechnet, sodass Refund-Reporting tatsächlich funktioniert: eine erstattete Bestellung reduziert deinen gemeldeten Umsatz. Das ist echter Analytics-Wert, also geht Refund an GA4.

Keine Werbeplattform hat ein Refund-Event, das irgendetwas Nützliches tut. Meta ist eindeutig: Refund ist kein unterstützter Conversions-API-Event-Typ (Metas Refund-Handling lebt in der separaten Commerce-Platform-Order-Management-API, nicht in der CAPI). TikToks Events API 2.0 hat kein Refund-Standard-Event. Pinterest, Snapchat und Reddit haben ebenfalls kein natives Refund-Event. Du könntest ein Custom-"Refund"-Event auf jeden davon zwingen, aber es wäre ein toter Counter: kein Smart-Bidding nutzt es, kein Umsatz wird verrechnet, und bei Snap würde es sogar mit dem Lead-Slot kollidieren. Also listet jeder Werbekanal refund in seinem Skip-Set. Refund ist GA4-only, by design, verifiziert gegen die aktuellen Docs jedes Anbieters.

Es gibt eine längere Aufarbeitung dieser konkreten Entscheidung in Warum Erstattungen nur an GA4 gehen. Die Kurzfassung: ein Refund-Event, auf das niemand optimiert, ist kein Feature, es ist Leitungsrauschen.

form_start und form_abandon: das zentrale Gate

Form-Funnel-Signale sind Verhaltens-Analytics, keine Ad-Conversions. form_start feuert beim ersten Field-Focus, form_abandon feuert, wenn jemand ein Formular begonnen, mindestens ein Feld ausgefüllt und ohne Absenden verlassen hat. Sie sagen dir, wo ein Formular Leute verliert, was für dich nützlich ist, und als Conversion-Signal bei einer Werbeplattform nutzlos (oder schlimmer, irreführend).

Diese beiden werden nicht von SKIP_BY_CHANNEL behandelt. Sie werden eine Ebene höher behandelt, vom ANALYTICS_ONLY_EVENTS-Gate ganz oben in der Dispatch-Schleife. In dem Moment, in dem der Dispatcher einen dieser Namen sieht, schickt er an GA4 (falls GA4 überhaupt konfiguriert ist) und kehrt sofort zurück, bevor der Code-Pfad zu irgendeinem Werbekanal erreicht wird.

Der Grund, warum das ein separates, zentrales Gate ist und nicht einfach zehn Skip-Set-Einträge, ist Robustheit. Mit Per-Channel-Skip-Listen heißt das Hinzufügen eines elften Werbekanals, dass du daran denken musst, form_abandon zu seiner Skip-Liste hinzuzufügen, und Vergessen bedeutet, dass ein Verhaltens-Event still als Custom-Conversion an einen Werbeanbieter durchsickert. Mit dem zentralen Gate erbt ein neuer Kanal die Regel kostenlos: der Hard-Stop passiert, bevor sein Dispatch-Call in der Schleife überhaupt existiert. Um ein künftiges Verhaltens-Event als Analytics-only zu klassifizieren, fügst du seinen Namen zu diesem einen Array hinzu und änderst nichts sonst.

Es gibt ein zweites Gate, das speziell für form_abandon dahinter gestapelt ist. Standardmäßig erreicht ein Abandon nicht einmal GA4: BCNR_Form_Funnel::observe() trägt es in den plugin-internen Funnel-Store ein und bricht dann den Dispatch komplett ab, es sei denn, du hast dich für form_funnel_ga4 entschieden. Out of the box berührt form_abandon also überhaupt nichts auf der Leitung, und selbst wenn du dich dafür entscheidest, erreicht es nur GA4, nie einen Werbeanbieter. Die vollständige Drop-off-Analysis-Geschichte steht in Form-Funnel: finde das Feld, das die Conversion killt.

Die drei Broker-Kanäle routen anders

LinkedIn, Google Ads und Microsoft Ads haben kein flaches Event-Namen-Vokabular, auf das du mappst. Sie routen über Conversion-Slots, die der Kunde konfiguriert, also sind sie überhaupt nicht in SKIP_BY_CHANNEL. Sie überspringen sich selbst, wenn das Event auf keinen ihrer Slots mappt.

LinkedIn mappt jedes GA4-Event auf einen von fünf Conversion-Rule-Slots (purchase, lead, signup, addtocart, keypageview). Jeder Slot feuert nur, wenn der Kunde die passende Campaign-Manager-ConversionRule-URN in die Einstellungen eingefügt hat. Keine URN, kein Dispatch.

Google Ads und Microsoft Ads teilen sich einen Conversion-Kind-Mapper, ads_conversion_kind(). Er liefert einen Slot-Namen für genau die Events, die echte Google-/Microsoft-Conversion-Typen sind (purchase, add_to_cart, begin_checkout, generate_lead, subscribe, schedule, sign_up, contact) und einen leeren String für alles andere, was ein stiller Skip ist. Im Commerce-Funnel empfangen Google und Microsoft Ads also add_to_cart, begin_checkout und purchase und ignorieren die View- und Search-Events, weil das in diesen Plattformen keine konfigurierbaren Conversion-Actions sind.

Beide brauchen außerdem ihren Click-Identifier zur Attribution (gclid für Google, msclkid für Microsoft). Microsoft überspringt still jeden Upload ohne msclkid, weil die OfflineConversions-API ihn ohnehin ablehnt. Die Credentials leben nie im Plugin: der OAuth-Broker unter oauth.beaconry.app hält die Refresh-Tokens und Developer-Tokens, und das Plugin trägt nur einen HMAC-signierten Site-Bearer. Genau das lässt dich Google und Microsoft Ads verbinden, ohne auf eine Developer-Token-Freigabe zu warten, was seine eigene Geschichte ist in Google Ads verbinden ohne das Developer-Token-Warten.

Eine Feinheit: user_engagement und Hybrid-Modus

user_engagement (feuert nach ungefähr 10 Sekunden aktiver Zeit plus 50 Prozent Scroll) ist im Skip-Set für Meta und TikTok, aber der Skip ist bedingt. Es mappt bei beiden Plattformen auf ViewContent, und ein server-seitiges ViewContent zu schicken ergibt nur Sinn, wenn der passende Browser-Pixel ebenfalls läuft, denn dann feuern beide mit derselben event_id und die Plattform dedupliziert sie zu einem angereicherten Event. Mit ausgeschaltetem Browser-Pixel hat ein einzelnes server-seitiges ViewContent aus dem Engagement kein Browser-Gegenstück zum Mergen und bläht nur den ViewContent-Counter auf. Also lässt should_dispatch_to_channel() user_engagement nur durch, wenn der Hybrid-Browser-Pixel dieses Kanals aktiviert ist, und überspringt es sonst. Die event_id-Dedup-Mechanik dahinter ist abgedeckt in Hybrid-Modus und event_id-Dedup.

EDD und SureCart erben dasselbe Routing

Die Routing-Matrix ist auf GA4-Event-Namen geschlüsselt, nicht auf die Commerce-Plattform, also ist sie by construction plattform-agnostisch. Easy Digital Downloads feuert denselben Funnel minus search (es hat kein Produkt-Search-Event), und SureCart feuert purchase und refund server-seitig, während seine JS-Cart-Events über eine Browser-Bridge ankommen. All diese Events landen in genau derselben dispatch()-Schleife und treffen genau dieselbe SKIP_BY_CHANNEL- und Slot-Logik. Ein SureCart-add_to_cart routet zu denselben sieben Zielen wie ein WooCommerce-add_to_cart, weil der Dispatcher nicht weiß und sich nicht darum schert, welcher Adapter es erzeugt hat.

Wie du das Routing für dein eigenes Setup verifizierst

Du musst dieser Matrix nicht im Abstrakten vertrauen. Die Debug-Ansicht jeder Plattform ist die Ground Truth:

  • GA4 Realtime / DebugView: du solltest den vollen Funnel sehen, inklusive view_item_list, refund und (falls aktiviert) form_start.
  • Meta Test Events: du solltest ViewContent, AddToCart, Purchase und Konsorten sehen, aber nie ein scroll oder einen Refund. Ein ViewCart-Custom-Event ist erwartet und korrekt.
  • TikTok / Pinterest / Snap / Reddit Event-Manager: nur Standard-Events, kein Engagement-Rauschen. Reddit zeigt begin_checkout als CUSTOM-Event, was beabsichtigt ist.
  • Beaconry-Live-Conversions-Card: der plugin-interne Recorder zeigt den Namen, der pro Kanal tatsächlich gesendet wurde, eine Zeile "Meta ViewContent" für das, was GA4 als user_engagement geloggt hat, ist also das funktionierende Hybrid-Dedup-Mapping, kein Bug.

Wenn du ein Event bei einer Plattform siehst, das die Matrix als übersprungen ausweist, ist der erste Verdächtige ein Per-Form-Override name_<channel>, der es wieder hereinholt, was der eine sanktionierte Weg ist, von den Defaults abzuweichen.

Zum Mitnehmen

Beaconrys Routing ist kein Haufen von Conditionals, die über zehn Dispatch-Methoden verstreut sind. Es ist ein Analytics-only-Gate plus eine Per-Channel-Skip-Tabelle, beide in class-bcnr-forwarder.php, beide mit dem Grund für jeden Eintrag annotiert. Das Prinzip ist konsistent: eine Plattform empfängt ein Event nur, wenn sie einen Standard-Namen dafür hat, der Optimierung antreibt, oder ein benanntes Custom Event, das weiterhin nutzbar ist, und es wird übersprungen, wenn die Alternative ein Double-Count, ein verschlucktes Event oder ein toter Counter wäre. Refund beweist das Prinzip im Extrem (nur GA4, weil keine Werbeplattform irgendetwas damit anfangen kann), und das Form-Funnel-Gate beweist die Robustheit (Verhaltenssignale können nie zu einem Werbeanbieter durchsickern, inklusive Kanäle, die noch nicht hinzugefügt sind). Schick jeder Plattform genau das, was sie nutzen kann, und nichts sonst.