Google Tag Manager (GTM) server-side: cómo trabajar con peticiones http GET y POST de cualquier origen

Google Tag Manager (GTM) server-side supone un cambio de paradigma en las implementaciones con este gestor de etiquetas. Un contenedor web de GTM (ojo, también hay contenedores iOS, Android y AMP) se implementa en un ‘site’ y lanza peticiones por http directamente desde el navegador de un usuario a los servidores de herramientas de terceros como Google Analytics. Con GTM server-side esto cambia. El contenedor se implementa en un servidor (Google Cloud, AWS, etc.) y actúa como un proxy: recibe requests por http desde un navegador (por ejemplo) y los deriva a un end-point final.

El esquema clásico de Google Tag Manager de tags, triggers y variables sigue vigente, pero se une a la ecuación una nueva pieza que es fundamental: el cliente. El cliente es el encargado de interceptar los ‘requests’ entrantes a tu servidor de tagueo y procesar la información para que pueda ser usada por los tags. Sin un cliente que recepcione estas peticiones entrantes tu implementación de GTM server-side no va a funcionar. ¡Es así de crudo! Es un elemento realmente crítico.

GTM server-side pone a tu alcance una serie de clientes que puedes usar desde ya mismo. Todos ellos están orientados -como es lógico- a ser usados en implementaciones basadas en otros productos de Google, como por ejemplo GA4.

Estos son los clientes que puedes usar por defecto en tu contenedor de GTM server-side a fecha de publicación de este post.
En este post te propongo un cliente que he desarrollado para trabajar con peticiones http de cualquier stack tecnológico, no sólo de Google. Ten en cuenta que esto no es nada nuevo. Si te das una vuelta por Github.com, seguro que encuentras un montón de desarrollos de este tipo. A mi mismo me inspiró mucho un artículo que escribió al respecto Nicolas Hinternesch: ‘Google Tag Manager Server-Side — Parsing Event Data From Any Custom Vendor Request’. Pero en cualquier caso, esta es mi forma de resolver esta situación. Espero que te sirva de ayuda en tus implementaciones.

El ‘event object’: el equivalente al dataLayer de GTM en tu ‘tagging server’

Es importante entender el papel que juega un cliente en Google Tag Manager server-side. Puede cumplir muchos cometidos. Si quisieras, podrías desarrollar una API a través de un cliente que solicite a un servicio un recurso. Esto es, de hecho, lo que te propongo en el primer post que escribí en este blog: ‘Trabajar con un dataLayer alojado en servidor (server-hosted) en Google Tag Manager (GTM). No obstante, lo más habitual es que un cliente intercepte un ‘request’ por http y genere un ‘event object’ con él.

Este ‘event object’ es un objeto que se forma con la información que transporta el ‘request’: los query strings de la url, los request headers, el request body, etc. Viene a ser el equivalente al dataLayer de GTM en un contenedor web. El cliente genera este objeto y ejecuta una instancia de tu contenedor de GTM server-side. De esta forma, los tags que se ejecuten en este momento harán uso de la información presente en este objeto

Como te decía al comienzo de este post, GTM server-side incluye unos clientes por defecto. Todos ellos están orientados a ser usados en flujos de datos de productos de Google. ¿Qué pasa si quieres trabajar con ‘requests’ de otros ‘players’? Google Tag Manager te anima a que en estos casos uses un flujo de datos de GA4. El cliente de GA4 genera un objeto cuyos datos pueden ser explotados por tags de otras herramientas. Esto es justo lo que pasa con Facebook CAPI, por ejemplo.

Puedes seguir la anterior senda, o puedes desarrollar un cliente propio.

El cliente de GA4 en GTM server-side intercepta una petición entrante de GA4 al servidor de tagueo y genera con ella un 'event object'

Un request por http es una comunicación que se establece entre dos 'end-points'

El cliente que te propongo a continuación es capaz de interceptar y digerir cualquier petición por http de tipo GET y POST que llegue a tu servidor de tagueo. ¿Y qué es exactamente una petición http? Es una comunicación que se establece entre dos ‘end-points’ conforme a un protocolo establecido. Un buen ejemplo es la comunicación que se establece entre un navegador y un servidor. Un navegador lanza un request por http a un servidor solicitando un recurso (una foto, por ejemplo). El servidor evalúa esta petición y responde al navegador con el recurso solicitado.

Si inspeccionas la pestaña ‘Network’ de la consola de depuración de tu navegador puedes ver todas las peticiones por http que lanza tu navegador…y las respuestas que reciben. Así, para poder leer este post, estas son algunas de las peticiones que ha lanzado tu navegador al servidor en el que se aloja mi ‘site’:

Algunas de las peticiones http que tu navegador lanza al servidor en el que está alojado https://analyticsimplementations.com para cargar el 'site'
Hay varios métodos de peticiones http: GET, POST, DELETE… Cada uno de estos métodos sirve un cometido diferente. El cliente que he desarrollado acepta solicitudes de tipo GET y POST. En líneas generales, un ‘request’ de tipo GET sirve para solicitar un recurso, y uno de tipo POST para enviar información.

El cliente para interceptar peticiones en GTM server-side

El ‘client template’ para Google Tag Manager server-side que he desarrollado acepta cualquier petición GET o POST entrante a tu servidor de tagueo. Genera un objeto con los siguientes componentes de dicha petición:

  • Los ‘query strings’ de la url de la solicitud
  • Los siguientes ‘request headers’: origen, país y ciudad (estos dos últimos los genera AppEngine en Google Cloud). Se incluyen además los encabezados que recogen la IP del usuario y el User-Agent. Como verás más adelante, puedes anonimizar el ‘event object’ excluyendo estos parámetros del mismo.
  • Puedes, además, incluir cualquier encabezado adicional que consideres. Lo único que tienes que hacer es editar la parte correspondiente del código.
  • Si la petición es de tipo POST y cuenta con un request body, las propiedades y valores del cuerpo de la petición
  • Si quieres, puedes incluir el valor de ciertas cookies en el ‘event object’ que genere el cliente. Si es así, es muy importante que estas cookies estén ‘seteadas’ en el mismo dominio que el de tu ‘tagging server’. Si no, el cliente no las va a procesar porque no se van a enviar en la propia petición http.
Y a continuación, el código del cliente. Date cuenta de que al tratarse de un ‘custom template’ de Google Tag Manager, se ha desarrollado con la versión javascript del ‘sandbox’ de GTM.
//API's needed to make this client template work
const claimRequest = require('claimRequest');
const getCookieValues = require('getCookieValues');
const getRequestBody = require('getRequestBody');
const getRequestHeader = require('getRequestHeader');
const getRequestMethod = require('getRequestMethod');
const getRequestPath = require('getRequestPath');
const getRequestQueryParameters = require('getRequestQueryParameters');
const JSON = require('JSON');
const logToConsole = require('logToConsole');
const returnResponse = require('returnResponse');
const runContainer = require('runContainer');
const setResponseHeader = require('setResponseHeader');
const setResponseStatus = require('setResponseStatus');

//API's saved for reuse
const requestBody = getRequestBody();
const requestMethod = getRequestMethod();
const requestPath = getRequestPath();
const requestQueryParameters = getRequestQueryParameters();

//Code starts here

//Logic to check if incoming request origin is allowed to be claimed
let allowedOrigins = data.allowedOrigins.toLowerCase().split(',');
let admitedRequest;

allowedOrigins.forEach((value,index,array)=>{
  
  array[index]= 'https://' + value;
  
});

allowedOrigins.forEach((value)=>{
  
  if(getRequestHeader("origin") === value){
    
    admitedRequest = true;
  } 
});

if(requestPath === data.requestPath && admitedRequest === true){

  claimRequest();
  
  //Code logic to be executed if http request type is GET
  if(requestMethod === 'GET'){
    
    //Set response headers to avoid CORS
    setResponseHeader("access-control-allow-credentials", "true");
    setResponseHeader("access-control-allow-origin", getRequestHeader("origin"));
    
    //Generate an object from the request url's query parameters
    let eventObject = requestQueryParameters;
    
    //Generate an event_name property for within eventObject to run the virtual cntainer instance    
    eventObject.event_name = requestQueryParameters[data.eventQueryParam] ? requestQueryParameters[data.eventQueryParam] : 'no_event';
    
    //Set aditional properties within eventObject from the incoming http request headers
    eventObject.page_referrer = getRequestHeader('referer');
    eventObject['x-params-country'] = getRequestHeader('X-Appengine-Country');
    eventObject['x-params-city'] = getRequestHeader('X-Appengine-City');
    //Uncoment the following line of code and configure to include any additional properties to eventObject from the desired request headers. Must include one line per requesHeader
    //eventObject[propertyName] = getRequestHeader('Header name');
    
    //Set aditional properties within eventObject from the incoming http request selected cookies. These cookies must be set at the same domain as the server tagging server
    if(data.cookieCheckbox){
      
      let cookies = data.cookieName.split(',');
      
      for(let i = 0; i < cookies.length; i++){
        
        eventObject['x-params-'+ cookies[i]+ '-cookie-value'] = getCookieValues(cookies[i]).toString();
      
      }
            
    }
     
    //If request request anonimization checkbox not checked, include user ip and user agent as properties of eventObject
    if(!data.anonymizeCheckbox){
    
      eventObject['x-params-user-Ip'] = getRequestHeader('X-Appengine-User-Ip');
      eventObject['x-params-user-agent'] = getRequestHeader('User-Agent');
      
    }
    
    //Generate a new object (containerEventParams) from eventObject (excluding the  eventObject[data.eventQueryParam] property that was used to generate the eventObject[event_name] property. This way we avoid object property duplication. 
    let containerEventParams = {};
    
    for(const property in eventObject ){
    
      if(property !== data.eventQueryParam){
        
        containerEventParams[property] = eventObject[property];
      
      }
    
    }
    
   //Run container     
   runContainer(containerEventParams, () => returnResponse());
  
  }
  
 //Code logic to be executed if http request type is POST
 else if(requestMethod === 'POST'){
   
    //Set response headers to avoid CORS
    setResponseHeader("access-control-allow-credentials", "true");
    setResponseHeader("access-control-allow-origin", getRequestHeader("origin"));
   
   //Code logic to be executed if POST http request includes a request body 
   if(requestBody){
     
     //Parse the request body JSON into an object
     const body = JSON.parse(requestBody);
     
     //Generate an object from the request url's query parameters
     let eventObject = requestQueryParameters;
     
     //Include the request body properties and values into eventObject
     for(const property in body){
     
       eventObject[property] = body[property];
       
     }
     
     //Generate an event_name property for within eventObject to run the virtual cntainer instance    
     eventObject.event_name = requestQueryParameters[data.eventQueryParam] ? requestQueryParameters[data.eventQueryParam] : 'no_event';
     
    //Set aditional properties within eventObject from the incoming http request headers
    eventObject.page_referrer = getRequestHeader('referer');
    eventObject['x-params-country'] = getRequestHeader('X-Appengine-Country');
    eventObject['x-params-city'] = getRequestHeader('X-Appengine-City');
    //Uncoment the following line of code and configure to include any additional properties to eventObject from the desired request headers. Must include one line per requesHeader
    //eventObject[propertyName] = getRequestHeader('Header name');
     
    //Set aditional properties within eventObject from the incoming http request selected cookies. These cookies must be set at the same domain as the server tagging server
     if(data.cookieCheckbox){
      
      let cookies = data.cookieName.split(',');
      
      for(let i = 0; i < cookies.length; i++){
        
        eventObject['x-params-'+ cookies[i]+ '-cookie-value'] = getCookieValues(cookies[i]).toString();
      
      }
            
    }
     
    //If request request anonimization checkbox not checked, include user ip and user agent as properties of eventObject
    if(!data.anonymizeCheckbox){
    
      eventObject['x-params-user-Ip'] = getRequestHeader('X-Appengine-User-Ip');
      eventObject['x-params-user-agent'] = getRequestHeader('User-Agent');
      
    }
     
    //Generate a new object (containerEventParams) from eventObject (excluding the  eventObject[data.eventQueryParam] property that was used to generate the eventObject[event_name] property. This way we avoid object property duplication.  
    let containerEventParams = {};
    
    for(const property in eventObject ){
    
      if(property !== data.eventQueryParam){
        
        containerEventParams[property] = eventObject[property];
      
      }
    
    } 
     
    //Run container 
    runContainer(containerEventParams, () => returnResponse());    
     
   }
   
   //Code logi to be executed if POST request does not include a request body
   else if(!requestBody){
     
     //Generate an object from the request url's query parameters
     let eventObject = requestQueryParameters;
     
    //Generate an event_name property for within eventObject to run the virtual cntainer instance 
    eventObject.event_name = requestQueryParameters[data.eventQueryParam] ? requestQueryParameters[data.eventQueryParam] : 'no_event';
     
    //Set aditional properties within eventObject from the incoming http request headers 
    eventObject.page_referrer = getRequestHeader('referer');
    eventObject['x-params-country'] = getRequestHeader('X-Appengine-Country');
    eventObject['x-params-city'] = getRequestHeader('X-Appengine-City');
     //Uncoment the following line of code and configure to include any additional properties to eventObject from the desired request headers. Must include one line per requesHeader
    //eventObject[propertyName] = getRequestHeader('Header name');
  
  //Set aditional properties within eventObject from the incoming http request selected cookies. These cookies must be set at the same domain as the server tagging server   
  if(data.cookieCheckbox){
      
      let cookies = data.cookieName.split(',');
      
      for(let i = 0; i < cookies.length; i++){
        
        eventObject['x-params-'+ cookies[i]+ '-cookie-value'] = getCookieValues(cookies[i]).toString();
      
      }
            
    }
    
     //If request request anonimization checkbox not checked, include user ip and user agent as properties of eventObject
    if(!data.anonymizeCheckbox){
    
      eventObject['x-params-user-Ip'] = getRequestHeader('X-Appengine-User-Ip');
      eventObject['x-params-user-agent'] = getRequestHeader('User-Agent');
      
    }
     
    //Generate a new object (containerEventParams) from eventObject (excluding the  eventObject[data.eventQueryParam] property that was used to generate the eventObject[event_name] property. This way we avoid object property duplication. 
    let containerEventParams = {};
    
    for(const property in eventObject ){
    
      if(property !== data.eventQueryParam){
        
        containerEventParams[property] = eventObject[property];
      
      }
    
    }  
     
    //Run container 
    runContainer(containerEventParams, () => returnResponse());    
   
   }
   
 } 
   
}

Para usar el código puedes copiar y pegarlo al crear tu plantilla de cliente en tu contenedor de GTM server-side. También puedes descargar el archivo .tpl que encontrarás en el repositorio que he creado en mi perfil de Github.com para este desarrollo. No te olvides de configurar los permisos de tu ‘custom template’ antes de guardarlo.

Campos a configurar para usar el cliente

Una vez hayas usado el anterior código para crear un client custom template en tu contenedor de Google Tag Manager server-side, estos son los campos que tendrás que configurar para poder usarlo:

Bien, ahora una foto del mismo cliente una vez configurado:

La configuración que ves en la anterior foto es la que voy a usar para mostrarte el funcionamiento del cliente. Cuando lo hayas configurado tú, tendrás que entrar en modo ‘preview’ en tu contenedor de GTM server-side. Listo, tu cliente está preparado para interceptar peticiones. Ahora toca enviar estos ‘requests’. 

El cliente en funcionamiento: envío de un request por http de tipo GET a Google Tag Manager server-side...

Puedes enviar una petición por http a tu contenedor de GTM server-side desde cualquier servicio, como por ejemplo Postman o tu propio navegador. Para ilustrar este post yo lo voy a hacer desde un contenedor web de Google Tag Manager. Para ello he desarrollado dos scripts que te muestro a continuación. Tanto estos scripts como el propio cliente están preparados para evitar fallos CORS. Ten esto en cuenta si tú lanzas tu petición desde otro origen diferente. Y como siempre, te recomiendo que veas el siguiente vídeo: ‘CORS in 100 seconds’, de Fireship. Explica este fenómeno muy bien.

Fíjate, este el código que he desarrollado para lanzar una petición http de tipo GET desde mi contenedor de GTM de tipo web a mi servidor de ‘tagging’:

<script>
  
  (function() {

    var httpRequest = new XMLHttpRequest();
    httpRequest.withCredentials = true;
    httpRequest.open('GET','https://<your end point goes here>/prueba?en=addToCart&price=25&sku=87098&category=books&product_name=test-product&user_type=returning',true);
    httpRequest.withCredentials = true;
    httpRequest.send();

    })();

</script>
Y así es cómo se envía y cómo lo recibe Google Tag Manager ‘server-side’:
El anterior script se ejecuta desde un contetenedor web de GTM y lanza una petición por http a mi servidor de tagueo.
La petición alcanza mi servidor de tagueo, en donde el cliente la intercepta.
El cliente usa el 'payload' de la petición entrante y genera un objeto con ella.
¿Lo ves? El objeto que genera el cliente incluye todos los componentes del request por http que te detallaba antes: query strings, encabezados de la consulta y cookies.

...y envío de un request por http de tipo POST

Bien, ahora voy a hacer el mismo ejercicio pero con una petición POST. La voy a lanzar desde mi contenedor web de GTM con este script:
<script>
  
  (function () {

    var httpRequest = new XMLHttpRequest();
    var bodyObject = {testProperty1:'testValue1',testProperty2:'testValue2',testProperty3:'testValue3',testProperty4:'testValue4',testProperty5:'testValue5', testProperty6:'testValue6'};
    httpRequest.withCredentials = true;
    httpRequest.open('POST','https://<your end point goes here>/prueba?en=test_event&user_type=new&category=testing&page_name=test-page',true);
    httpRequest.send(JSON.stringify(bodyObject));

})();

</script>  
Al ser de tipo POST, el ‘request’ incluye un ‘request body’ (el cuerpo de la solicitud). El cliente usará este JSON y lo incluirá en el objeto que genere. Pero ojo, también puede darse el caso de que una petición POST no tenga un ‘request body’. Sea como sea, para este ejemplo voy a mostrarte, además, cómo se excluyen la IP y el User-Agent del usuario si así lo configuras en el cliente. Recuerda que este es el campo que tienes que marcar para ello:
Venga, fíjate en cómo se desarrolla la petición:
La petición de tipo POST se ejecuta (a través del código detallado arriba) desde un contenedor web de GTM.
El request por http llega de forma correcta a Google Tag Manager server-side, en donde es interceptado por el cliente.
El cliente procesa el 'payload' del request y genera el 'event object'
El resultado final es un ‘event object’ generado por el cliente a partir de los siguientes elementos de la solicitud POST: los query strings de la url, el cuerpo de la solicitud, los encabezados de la solicitud y las cookies configuradas. ¿Y la IP y el User-Agent? No se han incluído en el objeto ya que se marcó el checkbox correspondiente.

Reflexiones finales

Google Tag Manager (GTM) server-side es en verdad un cambio de paradigma en lo que se refiere a este gestor de etiquetas. Tu servidor de tagueo puede -en efecto- ser un proxy para derivar hits a herramientas GA4 o Facebook. Pero puede ser mucho más que eso. Puedes convertir tu implementación de GTM server-side en un verdadero generador de API’s, por ejemplo. 

Sea el que sea el uso que des a GTM server-side, el cliente siempre va a ser el epicentro de ello. 

Es muy posible que jamás uses este desarrollo en tus implementaciones. Es mucho más operativo seguir las directrices de Google y explotar los beneficios de enviar a GTM server-side un flujo de datos de GA4 que luego aprovechar. Siendo esto así, ganas mucho si te sumerges en las tripas de un cliente y entiendes -de verdad- cómo funciona esta nueva pieza de Google Tag Manager. 

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