DAS Werkzeug für den Tag Manager entdecken.Zum GTMSPY

Race Condition
Doppelte Transaktionen in Google Analytics vermeiden ohne Cookies
Google Analytics

17. Februar 2019

Doppelte Transaktionen in Google Analytics vermeiden ohne Cookies

Die Nischenlösung für weder 100% clientseitige noch 100% serverseitige Deduplizierung auf Knopfdruck.

Eines der schlimmsten Dinge, die Ihrem Analytics-Konto passieren können, ist das mehrfache Zählen einer Transaktion, d.h. die künstliche Aufblähung Ihrer Erfolgskennzahlen. Leider passiert genau dies aus Gründen, die normalerweise damit zusammenhängen, wie die Trigger eingerichtet werden, da Analytics Transaktionskennungen nicht als einzigartig betrachtet. Stattdessen wird jeder (Transaktions-)Treffer einfach verarbeitet.

Um diesen Mangel zu beheben, setzen Analytics-Spezialisten normalerweise eine von zwei Lösungen ein.

  1. Senden von Transaktionstreffern ausschließlich server-side.
    Meiner Meinung nach ist dies in jedem Fall die beste Lösung, da die entscheidenden Informationen einer erfolgreichen Transaktion nur durch das Backend-System ausgelöst werden sollten.

  2. Nutzung von Browser-Cookies/Storage, um zu verhindern, dass der Client mehrere Treffer für dieselbe Transaktions-ID sendet.
    Perfektioniert von Simo Ahava unter Verwendung eines Custom Task zur Erweiterung von Analytics' sendHitTask.

Im Gespräch mit verschiedenen Profis stellte sich jedoch heraus, dass eine dritte Lösung interessant sein kann, die in diesem Artikel vorgestellt wird, einschließlich des erforderlichen Codes.

Was das Backend-Tracking betrifft, stehen viele vor dem Problem, dass ihre Kunden nicht über die erforderlichen Entwicklungskapazitäten verfügen, um das Backend-Transaktionstracking zu implementieren. Zumindest nicht in einer angemessenen Zeitspanne. Dennoch ist der Analytik-Spezialist für eine genaue Nachverfolgung verantwortlich.

Und obwohl das Speichern von Transaktions-IDs im Browser des Kunden für die meisten Web-Properties ausreichen sollte, berichten einige Branchenkollegen, dass es dennoch zu doppelten Treffern kommen kann, beispielsweise nach dem Löschen von Cookies.

Hinzufügen eines serverless Deduplizierungs-Proxys

Hier haben wir also eine dritte Lösung, die eine Art Backend-Layer hinzufügt, um zuverlässiger als Cookies zu sein, aber ohne die Notwendigkeit, Backend-Dev-Ressourcen zu buchen.

So funktioniert's

Unter Berücksichtigung eines typischen Setups, bei dem Google Tag Manager eingesetzt wird, fügen wir unseren relevanten Google Analytics-Tags einen customTask hinzu, der den sendHitTask überschreibt. Obwohl wir technisch gesehen nur den ursprünglichen sendHitTask reproduzieren, besteht der große Unterschied darin, dass wir den Treffer nicht an den google-analytics.com Endpunkt senden.
Stattdessen senden wir den Treffer an unsere eigene benutzerdefinierte Domäne, die sich an einen Cloudflare Worker auflöst. Der Cloudflare Worker holt den Treffer inkl. des Analytics-Payloads ab, sucht in einer Firebase-Echtzeitdatenbank nach früheren Treffern mit derselben Transaktions-ID und leitet den Treffer nur dann an den ursprünglichen Google Analytics-Collector weiter, wenn die Transaktion zum ersten Mal gesehen wird.

Kurz gesagt, unsere Bestandteile sind

  1. customTask, um den sendHitTask außer Kraft zu setzen, d.h. den Kunden zu veranlassen, den Tracking-Treffer an our-domain.com/collect statt an google-analytics.com/collect zu senden

  2. Listening des Cloudflare Worker-Skripts auf our-domain.com/collect als Proxy zwischen dem Client und Google Analytics

  3. Firebase-Echtzeitdatenbank zur Speicherung von Transaktions-IDs (wird benötigt, bis Cloudflare den KV-Store innerhalb von Workers öffentlich zugänglich macht)

Schritt für Schritt zum Einstieg

  • Wenn Sie eine separate Domain für das Tracking nutzen wollen, registrieren Sie eine und fügen Sie diese Ihrem Cloudflare-Konto hinzu.

  • Erstellen Sie ein neues Firebase Projekt und erhalten Sie sowohl die ID als auch das Datenbankgeheimnis.

  • Erstellen Sie eine neue benutzerdefinierte JavaScript-Variable im Tag Manager und füllen Sie sie mit dem untenstehenden Skript, während Sie den Platzhalter durch Ihre eigene Domain in Zeile 3 ersetzen


function () {
  return function (model) {
    var proxyDomain = "https://<yourdomain.com>/collect";
    var proxySendHitTask = function () {
      function pixel(hitPayload) {
        var result = false;
        try {
          document.createElement("img").src = proxyDomain + "?" + hitPayload, result = true
        } catch (e) {}
        return result
      }
      return {
        send: function (hitPayload) {
          var result;
          if (!(result = 2036 >= hitPayload.length && pixel(hitPayload))) {
            result = false;
            try {
              result = navigator.sendBeacon && navigator.sendBeacon(proxyDomain,hitPayload)
            } catch (e) {}
          }
          if (!result) {
            var xhr;
            try {
              window.XMLHttpRequest && "withCredentials" in (xhr = new XMLHttpRequest) && (xhr.open("GET", proxyDomain, true), xhr.setRequestHeader("Content-Type", "text/plain"), xhr.send(hitPayload), result = true)
            } catch (e) {}
          }
          return result || pixel(hitPayload)
        }
      }
    }();
    tracker.set("sendHitTask", function (model) {
      proxySendHitTask.send(model.get("hitPayload"));
    }, true)
  }
}
  • Bearbeiten Sie alle Google Analytics-Tags in Ihrem Tag Manager, die Transaktionen senden. Geben Sie das Feld customTask an, das mit der JavaScript-Variablen gefüllt werden soll, die Sie oben erstellt haben.

  • Öffnen Sie Ihren Cloudflare Workers Editor und fügen Sie das untenstehende Skript ein. Vergessen Sie nicht, den Abschnitt Config entsprechend Ihrer Firebase-Anmeldeinformationen zu ändern. Drücken Sie Deploy.
addEventListener('fetch', event => {
  if(event.request.url.indexOf('/collect') > -1)
    event.waitUntil(process(event.request))
  event.respondWith(new Response('',{status:200}))
})

/**
 * CONFIG SECTION
 */
const ANALYTICS_URL = 'https://www.google-analytics.com'
const FB_URL = 'https://<your-database-id>.firebaseio.com/transactions/';
const FB_KEY = '<your-database-secret>';

/**
 * Process the hit, but only for transactions that haven't been seen before
 * param {Request} Original Request
 */
const process = async (request) => {
  const url = new URL(request.url)
  const transactionId = url.searchParams.get('ti')
  const hitType = url.searchParams.get('t')

  let entry = await lookup(transactionId)
  let entryData = await entry.json()

  if(entryData)
    return (hitType != 'item') ? logDuplicateHit(transactionId,++entryData.hits) : Promise.resolve(1)
  else {
    return Promise.all([
      (hitType != 'item') ? logTransaction(transactionId) : Promise.resolve(1),
      analyticsHit(decorateHitPayload(url,request.headers.get('CF-Connecting-IP'),encodeURIComponent(request.headers.get('user-agent'))))
    ])
  }
}

/**
 * Check for transaction in Firebase database
 * @param {string} Transaction ID
 */
const lookup = async (id) => (fetch(FB_URL+id+'.json?auth='+FB_KEY))

/**
 * Log new transactions in Firebase database
 * @param {string} Transaction ID
 */
const logTransaction = async (id) => (fetch(new Request(FB_URL+id+'.json?auth='+FB_KEY, {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    createdAt: new Date().getTime(),
    hits: 1
  })
})))

/**
 * Increase hit count for duplicate transactions in Firebase database
 * @param {string} Transaction ID
 * @param {number} Hit Count
 */
const logDuplicateHit = async (id,count) => (fetch(new Request(FB_URL+id+'.json?auth='+FB_KEY, {
  method: 'PATCH',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    hits: count
  })
})))

/**
 * Decorate the Analytics hit with client's IP and client's User Agent
 * @param {URL} request url
 * @param {string} User's IP
 * @param {string} User's User Agent
 */
const decorateHitPayload = (url,userIP,userAgent) => (url.pathname+url.search+'&ua='+userAgent+'&uip='+userIP)

/**
 * Forward hit to Google Analytics
 * @param {string} Hit Payload 
 */
const analyticsHit = async (hitPayload) => (fetch(ANALYTICS_URL+hitPayload))

Dies war die sehr kurze Schritt-für-Schritt-Anleitung, die voraussetzt, dass Sie über ein grundlegendes Wissen über die verwendeten Technologien verfügen, da die Schritte sonst möglicherweise nicht detailliert genug sind, um von Ihnen reproduziert werden zu können. Sollten noch Fragen offen sein, können Sie diese gerne in den Kommentaren stellen.

Ansonsten reicht dies bereits aus, um einen fast kostenlosen Transaktions-Deduper laufen zu lassen. Das Worker-Skript wird zusätzlich alle Versuche als Treffer in jedem Transaktionsdatensatz in der Datenbank protokollieren, um einen Überblick darüber zu erhalten, wie oft doppelte Treffer auftreten.