Google Tag Manager: 1 tag to rule them all

Google Tag manager. Met 1 container (ID), toch cross domain tracken. Het kan, maar vergt wel wat vinger gymnastiek. Importeer de json en start extra snel met cross domain tracking.

Google Tag Manager: 1 tag to rule them all

Google Tag Manager. Leuk concept, handig, en best makkelijk. Geen gedoe met allerlei snippets, alleen gedoe met 1 snippet in je website sjablonen/pagina's.

Snippets

De rest van de snippets worden allemaal aangeboden in een prettigere gebruikersinterface, zodat iemand die een beetje weet wat ie doet, maar geen programmeer-ervaring heeft (javascript met name), gewoon allerlei dingen kan instellen en aanpassen. Handig!

Vooral handig wanneer je gewoon een centraal overzicht wilt hebben van alle snippets en troep die je op je website laat draaien. Gewoon wat regeltjes (triggers in Tag manager) die bepalen welke tag wanneer wordt afgevuurd. En dat is echt prima wanneer je gewoon simpele tags hebt die per domein dienen te worden ingezet, of per soort pagina's, etc.

Cross domain && Tag Manager

Zodra je cross domain gaat, moet je wel nog even ervoor zorgen dat relevante informatie netjes wordt overgedragen. Bijvoorbeeld bij Analytics. En dan wordt het toch al snel een stuk minder overzichtelijker in Tag manager t.o.v. gewoon de snippets in je template(s) kwakken, of gewoon per .html pagina een snippet definiëren. Die nauwkeurige, verfijnde manier van controle hebben over elke pagina op basis van gewoon een stukje javascript, die ging ik al snel missen.

Vooral omdat de conventies binnen Tag manager weer net even iets anders werken dan een snippet.

Tag Manager mogelijkheden

In de loop van de tijd heeft Google de Tag manager van allerlei updates voorzien, waarbij je tegenwoordig ook echt gewoon Custom HTML en javascript macro's en functies kunt inzetten en laten gebeuren op basis van situaties/triggers.

Het mooiste daar van is dat je de variabelen/functies die je hebt gemaakt, kunt uitwisselen binnen andere functies of tags.

Elke variabele is binnen een andere variabele aan te roepen middels {{variabele x}}. Je kunt dan ook gewoon lekker javascripten er mee. Bijvoorbeeld:

    var a, b = {{variabele x}}.split(','),c,x,y;
        for  (a = 0; a < b.length; a++) {
            if (x === y) { 
                c = y;
                break;
            }
        }

en {{variabele x}} kan gewoon ook weer een functie met berekening zijn of wat dan ook. Erg leuk.

Cross domain Analytics.js

In mijn geval had ik bijvoorbeeld een domein A en een domein B, waarbij op domein A een iframe werd ingeladen waarin domein B werd aangeroepen om een formulier te presenteren en af te handelen. Helaas is dit dus crossdomain.

Wanneer je bijvoorbeeld met Google Analytics werkt, zal deze de aanroep van het iframe zien als een nieuwe sessie. Zelfs een nieuwe user-id voor aanmaken. Hierdoor krijg je dus statistieken die niet meer aan elkaar te knopen zijn. Het lijkt in dit scenario gewoon alsof de gebruiker die het formulier invulde, een compleet andere gebruiker is, die zomaar even landt op je formulierpagina, en weer vertrekt.

En nee, wanneer het op iframe's aankomt, kun je niet zomaar even de code van de iframe pagina manipuleren vanaf het hoofddomein (en daarmee bijvoorbeeld het user-id even corrigeren of overzetten) en vice versa. Dat is om veiligheidsredenen gewoon geblokkeerd. En maar goed ook.

postMessage

Eugh, dacht ik destijds. Mijn voorganger had dit verder ook nooit afgemaakt of juist geïmplementeerd. Daar was ik aan begonnen en had uiteindelijk toch gekozen voor het gebruik van de postMessage API, om analytics data van domein A naar domein B te communiceren.

Linker en url-decoreren

Google heeft ook mogelijkheden met de Linker plugin, of gewoon de Linker-functionaliteit in te zetten, maar dan krijg je dus van die lelijke links zoals https://www.domeinb.com/?_ga=1.213353566.4232634566

Dit is overigens wel de enige manier wanneer je geen controle hebt over domein b, wanneer je als domein a beheerder die informatie wil overdragen.

Wanneer je deze manier toepast bij cross domain iframe welke je op je hoofd domain inlaadt, zal het frame via een onload=maakErEenAnalyticsUrlVan(); moeten worden ingezet. Dan krijg je afhankelijk van de snelheid van de verbinding en webservers even een flikkerend iframe dat opnieuw wordt ingeladen. En je loopt het risico dat de pageview in het iframe al is getriggerd. 3 keer bah en onhandig.

Waarom toch postMessage?

postMessage is hierin beter, maar vergt wel even wat afstemming binnen je code die je zelf beheert, en eventueel de code van het andere domein, welke je wellicht niet beheerd. Heb je wel toegang tot de code op het andere domein (in je iframe bijvoorbeeld), ben je sneller klaar. Anders moet je met de beheerder van het andere domein afstemmen wat de communicatie zal zijn via postMessage tussen beide domeinen.

Timing

Zo kun je ervoor kiezen om vanuit het hoofddomein een bericht te sturen zodra de user-id in Analytics is geregistreerd. Dit bericht stuur je dan naar je iframe domein b. Maar... wat nu als het hoofddomein al klaar is en het bericht wil gaan sturen, maar het iframe domein is nog niet ingeladen en is dus nog niet aan het luisteren via de Eventlistener? Dan stuur je de user-id zomaar de ruimte in, want echte controle zit er niet zomaar even in. Iedere pagina kan berichten sturen via postMessage naar welke locatie dan ook. Wat je beter kunt doen, is het laatst ingeladen domein degene laten zijn die het verzoek doet. Dit child domain, in de iframe is degene die aan de parent domain vraagt wat de Google user-id is, waarna het child domain gaat zitten luisteren of er iets terug komt. Het parent domain krijgt dat het verzoek als luisterende binnen en geeft antwoord. Dit is de beste manier om de timings-uitdagingen op te lossen.

Geldige afzender

Het is aan de ontvanger om te luisteren en te bepalen of het een geldig soort bericht is, en of het van een vertrouwd domein komt. Ja, je kunt zeggen als luisteraar: '*', maakt mij niet uit waar het vandaan komt, en er klaar mee zijn, maar dat brengt potentiële veiligheidsrisico's met zich mee (zeker met de huidige AVG wil je daar voorzichtig mee zijn). Dus dit moet je ook weer aangeven in je Eventlistener. Geef daar jouw domeinen aan die mogen verzenden. Binnen Google Tag manager kan je dit doen met een Aangepaste HTML-tag waarbij je met variabele topOrigin een set aan domeinnamen kan meegeven, die geaccepteerd worden als afzender. Onderstaande code plaats je dus op je externe domein welke in de iframe wordt ingeladen:

<script>
// Add the expected origin domain inside gtm topOrigin variable
// Must use the following format http://example.com
var topOrigin, topOrigins = {{gtm topOrigin}}.split(',');
function xDomainHandler(event) {
  event = event || window.event;
  var origin = event.origin;
  // Check for the whitelist when cid gets returned
  var found = false;
  for (var i = 0; i < topOrigins.length; i++) {
    if (topOrigins[i] == origin) {
	topOrigin = topOrigins[i];
      found = true;
      break;
    }
  }
  if (topOrigin != '*' && topOrigin != event.origin) {
    return;
  }
  try {
    var data = JSON.parse(event.data);
  } catch (e) {
    // SyntaxError or JSON is undefined.
    return;
  }
  if (data.cid) {
    sendHit(data.cid);
  }
}

if (window.addEventListener) {
  window.addEventListener('message', xDomainHandler, false);
} else if (window.attachEvent) {
  window.attachEvent('onmessage', xDomainHandler);
}

var alreadySent = false;
function sendHit(cid) {
  if (alreadySent) return;
  alreadySent = true;
  // If cid exists, it will overwrite any existing cookies.
  var params = {};
  if (cid) params['clientId'] = cid;
  
  dataLayer.push({'event':'trackPage','cid': cid});
}
if (!window.postMessage) {
  // If no postMessage Support.
  sendHit();
} else {
  // Resolve current topOrigin for initial message request
  var a=document.createElement('a');
  a.href=document.referrer;
  topOrigin = a.href;
  // Tell top that we are ready.
  top.postMessage('send_client_id', topOrigin);
  // Set a timeout in case top doesn't respond.
  setTimeout(sendHit, 100);
}
</script>

Op het hoofd domein (parent domain), welke de iframe inlaadt, zorg je ervoor dat de volgende aangepaste HTML-tag wordt ingeladen:

<script>
// Edit the gtm allowedOrigins variable and add your domains to the whitelist
var allowedOrigins = {{gtm allowedOrigins}}.split(',');
function xDomainHandler(event) {
  event = event || window.event;
  var origin = event.origin;
  
// Check for the whitelist.
  var found = false;
  for (var i = 0; i < allowedOrigins.length; i++) {
    if (allowedOrigins[i] == origin) {

      found = true;
      break;
    }
  }
  if (!found) return;
  if (event.data != 'send_client_id') return;

// Get the clientId and send the message.
  var tracker = ga.getAll()[0];
    tracker.get('clientId');
    var data = {cid: tracker.get('clientId')};
    event.source.postMessage(JSON.stringify(data), origin);
}
if (window.addEventListener) {
  window.addEventListener('message', xDomainHandler, false);
} else if (window.attachEvent) {
  window.attachEvent('onmessage', xDomainHandler);
}
</script>

De variabele allowedorigins vul je dan weer met je child domeinen als geaccepteerde regexwaarde.

Om een lang verhaal kort te maken; Gebruik dit json-bestand en importeer deze container met instellingen in je werkruimte van Tag manager om een blanco startpakket te gebruiken. Op basis hiervan en wat tweaks voor jezelf (denk aan anonimizeIP = true in je analytics tag instellingen i.v.m. de AVG), en je kunt crossdomain gaan tracken. Het enige wat je moet doen, is op beide domeinen dezelfde GTM-container inladen en de juiste triggers gebruiken bij de juiste domeinen.