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
//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
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
{
"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):
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:
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).
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:
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:
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.