Data enrichment con Google Tag Manager server-side: enriquece el payload de tus hits de GA4 con el Promise API de GTM

En este post voy a hablar de una de las funcionalidades de Google Tag Manager (GTM) server-side que bajo mi punto de vista tienen más potencial: la posibilidad de enriquecer tus flujos de datos con APIs externas. Es decir: enriquecer el event object que GTM server-side genera al procesar una petición entrante.

Esto es algo de lo que ya escribí hace algún tiempo en el primer post que publiqué en este blog: ‘Trabajar con un dataLayer alojado en servidor (server-hosted) en Google Tag Manager’. Pero ahora le he dado una vuelta a mi enfoque gracias a dos nuevas APIs de server-side Google Tag Manager: el Promise API y el Object API. La principal diferencia es que ahora el flujo de datos se enriquece en el servidor, no en el cliente.

Te voy a mostrar un caso de uso que espero te sirva de ayuda: enriquecer el payload de las peticiones de GA4 que envías a tu servidor de tagging con el contenido que devuelve una API o un endpoint propio. Para ello he desarrollado un custom client template que he llamado: GA4 stream enricher

Mucho más que un tag manager: GTM server-side como un API hub

No me canso de decirlo: Google Tag Manager server-side es mucho más que un gestor de etiquetas. Bien aprovechado puede ser un auténtico API hub, un gestor de comunicaciones con otras APIs. Siempre me ha atraído mucho la idea de enriquecer una capa de datos (el dataLayer de GTM o el objeto utag_data de Tealium, por ejemplo) con fuentes externas. Esto es algo que ya hace, por ejemplo, AudienceStream. ¿Y cómo puedes enriquecer un data layer con un endpoint externo? Correcto: lanzando una petición a una API para que te devuelva un contenido determinado. 

Hace unos meses GTM server-side lanzó una serie de APIs nuevas.  Entre ellas está el Promise API, que te permite, como su propio nombre indica, trabajar con promesas en JavaScript, es decir, desarrollar una lógica a sabiendas de que la API en cuestión devolverá -o no- un resultado (JavaScript es un lenguaje de programación asíncrono). Aunque, todo sea dicho, el caso es que no hace falta trabajar con promises para llamar a una API. Si lo prefieres puedes lanzar un request y apoyarte una función callback, pero trabajar con promesas es más eficiente y sobre todo más fácil.

Un cliente de GTM server-side procesa el hit de GA4 y lo enriquece llamando una API

Puedes usar el Promise API de muchas formas. El gran Simo Ahava ha escrito un post al respecto en el que analiza esta API en profundidad y resalta el potencial que tiene para desarrollar custom templates de variables asíncronas. Yo te propongo usarla en un cliente que se encargará de:

  • ’Interceptar’ flujos entrantes de GA4 a GTM server-side y procesar su payload
  • Lanzar una llamada a una API o endpoint externo, recibir la respuesta a esta petición y procesar también su payload
  • Generar un event object con el  payload de ambos requests: el de GA4 y el de la respuesta de la API/endpoint al que hayas llamado

Como ves, toda la lógica se desarrolla en tu servidor de tagging. Tu navegador (o cualquier otro cliente web) sólo interviene para enviar a GTM server-side una petición de GA4. Cuando esta petición llega al servidor, un cliente de GTM server-side la ‘intercepta’ y lanza una petición a una API externa. Cuando este endpoint responde, el cliente construye un event object usando la información de ambas peticiones: la de GA4 y la de la API a la que has llamado.

Esta forma de trabajar tiene una ventaja desde el punto de vista del rendimiento: el navegador no tiene que ejecutar más peticiones ni procesar más JavaScript. Además, la conexión que haces desde tu servidor de tagueo y tu API de elección se mantiene oculta, algo que no pasa si la conexión se establece desde el navegador y cualquier otro endpoint

Por otra parte, no tendrás que lidiar con CORS (Cross Origin Resource Sharing), un mecanismo de seguridad implementado a nivel de navegador que controla -y limita- la información que se descarga desde servidores cuyo dominio no es el mismo que el de la página que se está visitando. En este caso, la comunicación se va a establecer entre tu tagging server y una API, es decir, entre servidores.

GA4 stream enricher al rescate: el custom client template

Como ya te he adelantado un poco más arriba, la pieza clave de esta implementación es un cliente en Google Tag Manager (GTM) server-side que articula la petición entrante a tu servidor de tagging de GA4 y la llamada a una API externa. Este el código del cliente que he desarrollado, fíjate (también puedes encontrarlo en el repositorio que he creado en mi perfil de Github):
//API's required for this client to work
const claimRequest = require('claimRequest');
const getRequestQueryParameter = require('getRequestQueryParameter');
const runContainer = require('runContainer');
const sendHttpGet = require('sendHttpGet');
const getRequestHeader = require('getRequestHeader');
const isRequestMpv2 = require('isRequestMpv2');
const extractEventsFromMpv2 = require('extractEventsFromMpv2');
const returnResponse = require('returnResponse');
const JSON = require('JSON');
const Object = require('Object');


const hostedJsonQueryString = getRequestQueryParameter('ep.'+ data.ga4Param);
const origin = getRequestHeader('Origin');

//Declare enrichGa4Stream variable and set it to false by default
let enrichGa4Stream = false;

//Generate an array with the GA4 hit params, doesn't matter if GET or POST request
const ga4EventParams = extractEventsFromMpv2();

//Declare eventObject variable
let eventObject;

//Populate the eventObject variable with the object contained in the ga4EventParams array
for(let i = 0; i < ga4EventParams.length; i++){
    
    eventObject = ga4EventParams[i];
      
}
      
//Generate an array with the keys contained in the eventObject object
const eventsKeys = Object.keys(eventObject);

//Run a for loop against the eventsKey array to look for the data.ga4Param    
for(var i = 0 ; i < eventsKeys.length ; i++){
    //Does the eventsKeys array contain the value of template field data.ga4Params? If 'yes', set enrichGa4Stream variable to true
    if(eventsKeys[i] === data.ga4Param){
          
        enrichGa4Stream = true;
          
    }
        
}

//Claim incoming request if all these conditions are met 
if(origin === data.origin && isRequestMpv2() && enrichGa4Stream === true ){

    claimRequest();
   
    //Variable containing the url to which the request is going to be sent
    const requestUrl = data.endpoint;
  
    //Necessary request headers  
    const requestHeaders = function(){
      //If endpoint requires authentication...
      if(data.apiAuthentication){
  
        return {
          headers: {'content-Type':'application/json', 'Authorization':'Bearer ' + data.bearerToken}
        }; 
    
      } 
      //...and if it doesn't
      else {
    
        return {
          headers: {'content-Type' : 'application/json'}
        };    
    
      }
  
    };

   //GET request that returns a promise: the server hosted dataLayer
  sendHttpGet(requestUrl,requestHeaders()).then((results => {
    
    //If promise comes back with a succesful response...  
    if(results.statusCode === 200){
    
    const dataLayer = JSON.parse(results.body);
    const dataLayerKeys = Object.keys(dataLayer);
    const dataLayerValues = Object.values(dataLayer);

    for(let i = 0; i < dataLayerKeys.length; i++){
      
    //If eventObject and dataLayer objects don't have keys that are named the same...
      
      if(!eventObject.hasOwnProperty(dataLayerKeys[i])){
    
      eventObject[dataLayerKeys[i]] = dataLayerValues[i];
      
      }
      //Otherwise, rename the property to be added to the already existing evenObject object concatenating a _ at the end
      else{
      
        let renamedKey = dataLayerKeys[i] + '_';
        eventObject[renamedKey] = dataLayerValues[i];
      
      }
          
    }
    
   //Run container  with eventObject 
   runContainer(eventObject, () => returnResponse());
   }
    
   //If promise comes back with a response other than 200, run a virtual instance of the container with the GA4 event data object alone
    
   else{
   
   runContainer(eventObject, () => returnResponse());
   
   }

  }));
   
}

Ya lo ves, esto es JavaScript puro y duro. Eso sí, adaptado al sandbox de JS de Google Tag Manager con el que hay que trabajar en GTM server-side (aquí no hay Custom HTML templates ni Custom Javascript Variables)

El código está comentado, así que no voy a explicarlo en detalle aquí. No obstante, sí que me gustaría llamar la atención sobre unos detalles. 

  • Si buscas en las APIs que declaro al principio del código verás que no llamo al Promise API  ¿Cómo es posible? Porque ciertas APIs de Google Tag Manager han incorporado de forma nativa este Promise API. Este es el caso del sendHttpRequest API, que es justo la que uso en este cliente.
  • Si te fijas en el código, verás que hago uso del Object API. Me ha parecido muy útil. Te permite extraer el contenido de un objeto (propiedades, valores) en un array con el que luego es fácil trabajar. En mi caso yo estoy usando esta API para fusionar dos objetos: el del payload de GA4 y el JSON que devuelve a la que llamo
  • El código está preparado para operar en caso de que haya un problema con la respuesta de la API. En este caso, el cliente generará un event object sólo con el payload de la petición de GA4

Configura el cliente para empezar a usarlo

Para empezar a usar este cliente en tu contenedor de GTM server-side, debes copiar el anterior código (o descargar el archivo .tpl de su repositorio de Github) y pegarlo en un custom client template, en tu contenedor de GTM server-side. Una vez lo hayas hecho, podrás ver el siguiente interfaz:
Este es el aspecto que tiene el cliente que he desarrollado y que te propongo en este post. El campo 'Prioridad' es común a todos los clientes, y determina qué cliente se ejecuta primero a igualdad de condiciones ante una solicitud entrante a tu servidor de tagging,

Los campos que tienes que configurar para trabajar con el cliente son los siguientes:

  • Claim GA4 requests with this param. Rellena este campo con el nombre del parámetro que vayas a incluir en los requests de GA4 que quieras enriquecer (sigue leyendo, un poco más adelante te muestro cómo hacer esto). Si no añadieses este parámetro, el cliente estaría lanzando llamadas a una API de forma sistemática por cada petición de GA4, y esto puede subir el coste asociado al uso de GTM server-side.
  • Origin of incoming request. En este campo debes introducir el protocolo y dominio del site que enviará el hit de GA4 a tu contenedor de Google Tag Manager server-side. Ojo, el cliente sólo funciona cuando la comunicación entre tu site y tu servidor de tagging se produce en un contexto de 1st party. En otras palabras: cuando ambos comparten el mismo dominio superior (tudominio.com y gtmservidor.tudominio.com, por ejemplo)
  • Endpoint where enriching JSON is hosted. Aquí debes introducir la url de la API con la que quieres comunicarte para enriquecer tu flujo de datos. El cliente mandará un request a este endpoint, recibirá la respuesta y la procesará.
  • Authentication required to call this API? En Internet vas a encontrar miles de APIs públicas. Algunas son completamente abiertas, otras requieren que te autentiques con un token. Si es tu caso, marca este checkbox, se activará un campo en el que tendrás que introducir un token para trabajar con la API en cuestión. Ojo, el cliente está desarrollado para trabajar con bearer tokens, pero hay más métodos de autenticación.

Bien, hasta aquí la explicación del desarrollo. Ahora te voy a mostrar al cliente en funcionamiento en dos ejemplos diferentes.

Primer ejemplo: el cliente llama un endpoint propio

Para este primer ejemplo voy a enriquecer el payload de un evento de GA4 con un JSON que previamente he subido a un endpoint propio. La url de este endpoint es https://analyticsimplementations.com/tests/dataLayer.json , y el JSON que he subido a este directorio es el siguiente:
{
	"pageType": "blog post",
	"category": "tag managers",
	"userStat": "returning user",
	"event":"dataLayerLoaded",
	"planet":"earth",
	"happy":true,
	"age":39,
	"favourite food":"gazpacho",
	"color":"blue",
	"number":12,
	"language": "javascript",
	"screen_resolution":"aquí mi valor de prueba"
}

Bien, lo primero que voy a hacer es añadir un parámetro al evento de GA4 que quiero enriquecer (esto es algo que voy a hacer en el contenedor de GTM que he implementado en mi site). Puedes añadir el parámetro que quieras con el valor que quieras, el cliente sólo se va a fijar en que el payload del hit de GA4 contenga ese parámetro. El único requisito es que luego añadas el mismo parámetro a la configuración del cliente.

Fíjate, yo he optado por añadir el siguiente par clave-valor (ten en cuenta que si incluyes este parámetro en el base event de GA4, todos los eventos que vengan después lo heredarán):

Sólo los eventos que se ejecuten a través de esta etiqueta llevarán el parámetro 'hosted_json' en su payload. Si lo añades al tag de configuración de GA4, todos los eventos lo heredarán y el cliente de GTM server-side responderá a todos ellos.
Ahora voy a configurar el cliente siguiendo los pasos que te he explicado un poco más arriba:

Y ahora, la secuencia de acontecimientos. El evento de GA4 se ejecuta desde el contenedor de GTM implementado en mi site, que lanza una petición a mi servidor de tagging en el que tengo implementado GTM server-side. Fíjate que el evento incluye la clave hosted_json en su payload.

El cliente ‘intercepta’ la llamada entrante de GA4, lanza una llamada a mi endpoint y procesa el event object con los dos payloads: el de GA4 y el del JSON alojado en mi endpoint:

En esta foto puedes ver cómo mi endpoint responde a la llamada del cliente devolviendo en su body el JSON que he subido al directorio.
El cliente procesa la respuesta del endpoint al que llama y genera un event object con su payload y el de la petición de GA4.

Segundo ejemplo: el cliente llama a una API pública que necesita de authentication

Para este segundo ejemplo voy a llamar a una API pública que se llama The ONE API y que devuelve resultados sobre la obra de JRR Tolkien “El Señor de los Anillos”. Es un ejemplo que me viene muy bien, ya que es una API que requiere de autenticación para poder usarse (para conseguir el authentication token tienes que registrate en THE ONE API).

Este ejemplo es en realidad idéntico al anterior, con la excepción de que hay que configurar el cliente para añadir el authentication token a los request headers de la petición que se manda a a la API (hay más formas de añadir authentication tokens a un request). Y al igual que con el ejemplo anterior, lo primero que voy a hacer es añadir al evento de GA4 que quiero enriquecer con esta API un parámetro para diferenciarlo del resto de hits:

Lo siguiente es configurar el cliente en mi contenedor de GTM server-side para que intercepte esta petición entrante de GA4. Puesto que voy a lanzar una petición a The ONE API, tengo que configurar el cliente de forma consecuente para añadir el authentication token que esta API exige (recuerda que para conseguir este token tiene que darte de alta en la misma).

Al marcar el checkbox 'Authentication required to call this API?' el cliente activa de forma automática un campo de texto para añadir el bearer token necesario para trabajar con la API en cuestión. Como puedes observar, lo he ocultado para mantenerlo secreto.

Y a continuación, la secuencia de acontecimientos. Al igual que antes, el evento de GA4 a enriquecer se envía a mi servidor de tagging desde el contenedor de GTM implementado en mi site:

El evento de GA4 cuyo payload quiero enriquecer con la API se ejecuta desde el contenedor de GTM implementado en mi site. Fíjate en cómo su payload viene 'marcado' con el parámetro authenticated_json.

A partir de ahí todo sucede en el contenedor de GTM server-side, en donde el cliente intercepta la llamada de GA4 y llama a la API en cuestión:

El cliente intercepta el request entrante de GA4 y lanza una llamada a la API de The ONE API.
La API responde a la petición del cliente de GTM server-side devolviendo en su responde body la información solicitada.
El cliente genera un event object combinando el payload del hit de GA4 con el de la respuesta de la API. ¡El objeto es tan grande que no cabe en este pantallazo!

Reflexiones finales

Ahí lo tienes, acabas de ver cómo puedes usar tu contenedor de GTM server-side para enriquecer el payload de tus hits de GA4 llamando a una API externa. Esto es algo que tiene mucho potencial y que ya aplican otros productos como AudienceStream o EventStream vía Tealium Functions. 

Tiene sus implicaciones también. GTM server-side es un producto de pago, y uno de los puntos por los que se paga son precisamente los requests que salen desde el servidor de tagging. Pero la posibilidad y el potencial están ahí, sólo hay que saber cómo usarlos y aprovecharlos de una forma óptima. A partir de ahí es la imaginación al poder.  

pornance.net
www.fuck-videos.net
zettaporn.com