Zum Inhalt springen

Google Ads Enhanced Conversions in Drupal

Drupal AJAX Forms & Iframes: Die „Universal-Lösung“ für Google Ads Enhanced Conversions

Jeder Performance-Marketer, der schon einmal versucht hat, ein komplexes Drupal-Setup zu tracken, kennt diesen Schmerz.

Du hast ein Formular. Es ist wichtig. Aber es ist ein Drupal Webform. Das bedeutet: Kein Page-Reload (AJAX), keine Standard-Submit-Events und – als „Endgegner“ – läuft es vielleicht noch in einem Iframe oder nutzt File-Uploads (Multipart-Data).

Das Ergebnis? Deine Google Ads Conversions bleiben auf „Inaktiv“, oder schlimmer noch: Du trackst zwar die Conversion, verlierst aber die Enhanced Conversions (Erweiterte Conversions), weil Google die E-Mail-Adresse des Nutzers nicht sieht.

In diesem Artikel teile ich eine JavaScript-Lösung, die wir entwickelt haben, um dieses Problem ein für alle Mal zu lösen. Kein „Vielleicht klappt’s“, sondern eine robuste Lösung, die sich die Daten direkt aus dem Netzwerk-Traffic holt – datenschutzkonform und sicher.

Das Problem: Warum Standard-Tracking bei Drupal versagt

Bevor wir zur Lösung kommen, müssen wir verstehen, warum der Google Tag Manager (GTM) hier oft blind ist:

  1. AJAX Hijacking: Drupal fängt das normale Browser-Submit-Event ab und sendet die Daten im Hintergrund. Der GTM-Trigger „Formular senden“ feuert nie.
  2. Element Visibility ist zu langsam: Viele Workarounds feuern einen Trigger, wenn die „Vielen Dank“-Nachricht erscheint. Das Problem? Zu diesem Zeitpunkt hat Drupal die Formularfelder (und damit die E-Mail-Adresse für Enhanced Conversions) oft schon aus dem DOM gelöscht. Die Variable ist leer.
  3. Die Iframe-Mauer: Wenn dein Formular (wie z.B. ein Konfigurator) in einem Iframe läuft, sieht der GTM der Hauptseite nicht hinein (Same-Origin Policy).
  4. Multipart/Form-Data: Wenn dein Formular Datei-Uploads erlaubt, ändert der Browser das Datenformat von einfachem Text zu einem komplexen Datenpaket. Standard-AJAX-Listener können dieses Paket nicht lesen.

Die Lösung: Der „Universal Spy“

Anstatt auf Klicks oder sichtbare Elemente zu warten, nutzen wir einen XHR-Interceptor. Dieses Skript klinkt sich in die XMLHttpRequest-Schnittstelle des Browsers ein. Es „hört“ zu, was an den Server gesendet wird.

Der Clou an diesem Skript:

  • Es ist universell: Es decodiert sowohl normale Text-Formulare als auch komplexe Multipart-Uploads.
  • Es überwindet Iframes: Es wird auf der Hauptseite geladen, sucht aggressiv nach dem Iframe und injiziert den Listener direkt in das Iframe-Fenster (vorausgesetzt, beide liegen auf derselben Domain).
  • Es filtert: Es feuert nur bei Erfolg (Status 200) und kann spezifische Buttons (z.B. „Absenden“ vs. „Weiter“) unterscheiden.

Das Skript für den Google Tag Manager

Erstelle im GTM ein neues Benutzerdefiniertes HTML Tag. Trigger: All Pages (Alle Seiten).

Füge folgenden Code ein:

<script>
(function() {
    // --- KONFIGURATION ---
    // Hier den Text deines FINALEN Buttons eintragen (Case-Sensitive!)
    // Dies verhindert, dass "Weiter"-Buttons in Multi-Step-Formularen feuern.
    var finalButtonText = 'Absenden'; 
    
    // Der CSS-Selektor deines Iframes (falls vorhanden)
    var iframeSelector = 'iframe.carport-configurator-iframe'; 

    // --- HELPER: Form Data Decoder ---
    // Entschlüsselt Text-Daten UND Multipart/File-Uploads
    function getFormData(body) {
        var data = { email: null, phone: null, button: null };
        try {
            // Szenario A: Standard Text String (z.B. "email=test%40test.com&...")
            if (typeof body === 'string') {
                var params = new URLSearchParams(body);
                // Sucht nach typischen Drupal Feldnamen
                data.email = params.get('mail') || params.get('email') || params.get('submitted[email]');
                data.phone = params.get('phone');
                data.button = params.get('op') || params.get('_triggering_element_value');
            } 
            // Szenario B: Multipart/File Upload (Browser native object)
            // Wichtig für Formulare mit Datei-Uploads!
            else if (body && typeof body.forEach === 'function') {
                body.forEach(function(value, key) {
                    if (key.indexOf('email') > -1 || key.indexOf('mail') > -1) data.email = value;
                    if (key.indexOf('phone') > -1) data.phone = value;
                    if (key === 'op' || key === '_triggering_element_value') data.button = value;
                });
            }
        } catch(e) { console.log("GTM Parser Error: " + e); }
        return data;
    }

    // --- MAIN SPY FUNCTION ---
    function attachSpy(targetWindow, contextName) {
        try {
            if (targetWindow._gtm_spy_attached) return; 

            var originalSend = targetWindow.XMLHttpRequest.prototype.send;
            
            // Wir überschreiben die 'send' Funktion, um mitzulesen
            targetWindow.XMLHttpRequest.prototype.send = function(body) {
                this._body = body; 
                this.addEventListener('load', function() {
                    // Filter: Nur erfolgreiche Requests (Status 200-299)
                    if (this.status >= 200 && this.status < 300) {
                        var formData = getFormData(this._body);

                        // Nur feuern, wenn eine E-Mail gefunden wurde
                        if (formData.email) {
                            var isConversion = false;
                            
                            // LOGIK: Hauptformular immer tracken, Iframe filtern
                            if (contextName === 'Main Page') {
                                isConversion = true; 
                            } else if (contextName === 'Iframe') {
                                // Im Iframe prüfen wir, ob es wirklich der "Absenden" Button war
                                if (formData.button && formData.button.toLowerCase().indexOf(finalButtonText.toLowerCase()) > -1) {
                                    isConversion = true;
                                }
                            }

                            if (isConversion) {
                                console.log("GTM: Conversion Detected from " + contextName);
                                // DataLayer Push für den GTM
                                window.dataLayer.push({
                                    'event': 'drupal_form_success_enhanced',
                                    'user_data': {
                                        'email': formData.email,
                                        'phone_number': formData.phone
                                    },
                                    'form_source': contextName
                                });
                            }
                        }
                    }
                });
                originalSend.apply(this, arguments);
            };
            targetWindow._gtm_spy_attached = true;
            console.log("GTM: Spy Attached to " + contextName);
        } catch(e) { }
    }

    // --- INITIALISIERUNG ---
    // 1. Native Formulare auf der Hauptseite überwachen
    attachSpy(window, "Main Page");

    // 2. Iframe überwachen (Polling, falls das Iframe später lädt)
    setInterval(function() {
        var iframe = document.querySelector(iframeSelector);
        if (iframe && iframe.contentWindow) {
            attachSpy(iframe.contentWindow, "Iframe");
        } 
    }, 1000); 

})();
</script>

Warum Google Tag Manager? (Die Vorteile)

Vielleicht fragst du dich: „Warum nicht einfach ein Skript direkt in den Code einfügen?“

Hier sind drei Gründe, warum die Integration über GTM überlegen ist:

  1. Sicherheit durch Hashing: Wenn du ein natives Skript (gtag('event', ...)) verwendest, musst du die E-Mail-Adresse selbst hashen (SHA-256), bevor du sie an Google sendest. Machst du hier einen Fehler, verstößt du gegen Google-Richtlinien und Datenschutzgesetze. Der GTM übernimmt das Hashing automatisch und korrekt für dich.
  2. Consent Management (CMP): Der GTM integriert sich nahtlos in Cookie-Banner (wie Cookiebot oder Usercentrics). Wir können sicherstellen, dass das Tag nur feuert, wenn der Nutzer dem „Marketing“-Consent zugestimmt hat.
  3. Wartbarkeit: Du trennst die Logik (das „Spy“-Skript) vom Marketing-Tag. Wenn du morgen die Conversion-ID ändern musst, fasst du den Code nicht an.

Die Einrichtung im GTM (Step-by-Step Guide)

Sobald das Skript läuft, pusht es bei Erfolg ein Event in den DataLayer. Jetzt müssen wir es nur noch abfangen.

Schritt 1: Variablen erstellen

Wir müssen dem GTM beibringen, die E-Mail aus dem DataLayer zu lesen.

  1. Gehe zu Variablen > Neu.
  2. Wähle den Typ Datenschichtvariable (Data Layer Variable).
  3. Name der Datenschichtvariable: user_data.email (muss exakt so heißen).
  4. Gib der Variable im GTM den Namen: DLV - Email.
  5. (Optional) Wiederhole das für user_data.phone_number.

Schritt 2: Trigger erstellen

Wir brauchen einen Auslöser, der auf unser Spy-Skript reagiert.

  1. Gehe zu Trigger > Neu.
  2. Wähle den Typ Benutzerdefiniertes Ereignis (Custom Event).
  3. Ereignisname: drupal_form_success_enhanced.
  4. Gib dem Trigger den Namen: Event - Drupal Form Success.

Schritt 3: Google Ads Tag konfigurieren

Jetzt verknüpfen wir alles im Conversion Tag.

  1. Erstelle oder öffne dein Google Ads Conversion Tracking Tag.
  2. Fülle Conversion-ID und Label aus.
  3. Aktiviere die Checkbox „Von Nutzern bereitgestellte Daten einschließen“.
  4. Wähle deine Variable DLV - Email aus. Das ist der magische Moment: Der GTM nimmt jetzt die E-Mail, hasht sie sicher und sendet sie an Google.
  5. Als Trigger wählst du den eben erstellten Event - Drupal Form Success.

Wichtig: Datenschutz & DSGVO (GDPR)

Enhanced Conversions sind ein mächtiges Werkzeug, aber sie verarbeiten personenbezogene Daten (PII). Hier ist, was du beachten musst, um auf der sicheren Seite zu sein:

  1. Kein Klartext-Versand: Das oben genannte Setup liest die E-Mail zwar im Browser des Nutzers („Client-Side“), aber der GTM wandelt sie sofort in einen SHA-256 Hash um, bevor die Daten deinen Browser verlassen. Google erhält niemals die E-Mail im Klartext, sondern nur den Hash-Wert zum Abgleich.
  2. Consent ist Pflicht: Das „Spy“-Skript selbst liest nur Daten, sendet aber nichts. Das Senden geschieht erst durch das Google Ads Tag.
    • Stelle sicher, dass dein Google Ads Tag im GTM unter „Consent Settings“ (Einwilligungseinstellungen) korrekt konfiguriert ist.
    • Es darf nur feuern, wenn ad_user_data oder marketing_storage akzeptiert wurde.
  3. Datenschutzerklärung: Informiere deine Nutzer in der Datenschutzerklärung darüber, dass du „Erweiterte Conversions“ nutzt, um die Werbepräzision zu verbessern, und dass die Daten gehasht (pseudonymisiert) übertragen werden.

Der ultimative Test (Vertraue nicht dem „Status“)

Ein häufiges Missverständnis: Google Ads zeigt oft stundenlang „Inaktiv“ oder „Nicht verifiziert“ an, obwohl das Tracking funktioniert.

So prüfst du es wirklich:

  1. Öffne deine Website und öffne die Entwickler-Tools (F12).
  2. Gehe zum Tab Netzwerk (Network).
  3. Filtere oben links nach conversion.
  4. Fülle dein Formular aus und sende es ab.
  5. Suche nach einem Request an googleadservices.com oder google.com.
  6. Klicke darauf und schaue in den Payload. Suche nach dem Parameter em.

Siehst du dort einen kryptischen String (z.B. tv.1~em.G6NfWzSr...)? Glückwunsch! Das ist die gehashte E-Mail-Adresse. Die Daten haben deinen Browser verlassen und sind sicher bei Google angekommen. Du kannst den „Inaktiv“-Status ignorieren – er wird morgen grün sein.


Fazit: Drupal-Tracking muss kein Glücksspiel sein. Mit diesem Skript umgehst du DOM-Abhängigkeiten und holst dir die Daten direkt von der Quelle – egal ob Iframe, AJAX oder Multipart-Upload.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert