Volver al blog

Blog

Eventos de comercio electrónico sin plataforma de tienda: cómo hacer que un checkout propio informe a GA4

Publicado · 6 min de lectura

Una plataforma de tienda al menos te da un plugin con el que pelearte. Un checkout que has construido tú —una aplicación React, un flujo de reservas, un registro de SaaS con Stripe detrás— no te da nada: ningún evento se dispara hasta que tu propio código lo envía. La buena noticia es que el contrato es pequeño: un puñado de nombres, la forma de un objeto y unas pocas reglas sobre dónde va cada envío. Aquí está todo, incluida la parte que la mayoría de las guías se salta: las compras que ocurren cuando no hay nadie en tu página.

El contrato: un nombre y un objeto ecommerce

Todo lo que aparece aquí llega a Analytics por el mismo camino. Tu página envía un evento a window.dataLayer con su nombre de GA4 y un objeto ecommerce en la forma de GA4. Un activador de Tag Manager reconoce el nombre, y una etiqueta de evento de GA4 lee el objeto y lo reenvía a tu contenedor de servidor. Si le dijiste a SignalHost que tu sitio es una tienda —o pulsaste «Configurar» en la sección «Eventos de conversión» de la página del contenedor—, esos activadores y etiquetas ya existen en tu contenedor web, con el prefijo SH en el nombre. Lo que falta es el envío.

Dos reglas evitan la mayoría de los errores. Vacía primero el objeto ecommerce anterior, o un view_item de hace tres páginas se colará dentro del siguiente evento. Y en cada evento que lleve dinero, envía value como número y currency como código ISO: un value en forma de cadena o la falta de currency se aceptan sin una sola queja y quedan fuera de los ingresos sin que nadie lo note.

window.dataLayer = window.dataLayer || [];
window.dataLayer.push({ ecommerce: null });
window.dataLayer.push({
  event: 'begin_checkout',
  ecommerce: {
    currency: 'EUR',
    value: 49.00,
    items: [
      { item_id: 'plan-pro', item_name: 'Pro', price: 49.00, quantity: 1 }
    ]
  }
});

Qué evento, y dónde va

Un flujo a medida tiene los mismos momentos que una plataforma de tienda. Solo tienes que encontrarlos en tu propio código.

  • view_item_list: una página o un componente que muestra varios productos o planes. Una vez, cuando se muestra la lista, no con cada desplazamiento.
  • view_item: la página de un solo producto o plan.
  • add_to_cart y remove_from_cart: en el manejador del botón, después de que tu carrito haya cambiado de verdad.
  • begin_checkout: el momento en que el visitante entra en el checkout, ya sea el primer renderizado de la página de checkout o el clic que abre un diálogo de checkout.
  • add_payment_info: cuando se aceptan los datos de pago, no cuando aparece el formulario.
  • purchase: una vez, cuando se confirma el pedido. La mayor parte de este artículo trata de hacer bien este evento.
  • sign_up y start_trial: para un negocio de suscripciones, las conversiones anteriores a cualquier pago. SignalHost construye etiquetas para ambos junto a los eventos de tienda.

Envía desde el manejador que provocó el cambio, no desde un renderizado. Una aplicación de página única vuelve a renderizar sin reparos, y React ejecuta los efectos dos veces en desarrollo: un envío dentro de un efecto se duplica en la compilación de desarrollo y se repite cada vez que un componente se vuelve a montar.

La compra en tu página de confirmación: lee el pedido de tu servidor

Cuando el visitante vuelve a una página tuya después de pagar, esa página es donde va purchase, con dos condiciones. Toma el pedido de tu propio backend, no de la URL: los parámetros de consulta se pueden editar, las redirecciones los eliminan y también llegan con los pagos fallidos. Stripe, sin ir más lejos, devuelve al visitante a la misma dirección tanto si el pago se ha completado como si no, y lo indica en un parámetro propio. Y envíalo una sola vez: una recarga, el botón Atrás o una página de recibo guardada en marcadores vuelven a pasar por ella.

// On your confirmation page. The order comes from YOUR server, by an id the
// page already knows — never from price or status parameters in the URL.
const order = await fetch(`/api/orders/${orderId}`).then((r) => r.json());

const key = `purchase-sent-${order.id}`;
if (order.status === 'paid' && !localStorage.getItem(key)) {
  window.dataLayer = window.dataLayer || [];
  window.dataLayer.push({ ecommerce: null });
  window.dataLayer.push({
    event: 'purchase',
    ecommerce: {
      transaction_id: order.id,
      value: order.total,
      tax: order.tax,
      currency: order.currency,
      items: order.items.map((i) => ({
        item_id: i.sku, item_name: i.name, price: i.price, quantity: i.quantity
      }))
    }
  });
  localStorage.setItem(key, '1');
}

transaction_id es una red de seguridad, no un plan. Analytics descarta un transaction_id repetido dentro de una misma sesión, pero no una recarga de la página del recibo a la mañana siguiente.

El pago en la página de otro: Stripe Checkout, PayPal, una redirección al banco

Una página de pago alojada es la opción habitual en un desarrollo propio, y tiene un punto peligroso: puede que el visitante nunca vuelva. Paga, cierra la pestaña y tu página de confirmación nunca llega a renderizarse. Con tarjeta, eso es un pequeño porcentaje de las compras. Con transferencias bancarias, facturas y métodos de pago aplazado puede ser la mayoría, porque el dinero llega horas o días después.

Así que decide, para cada método de pago, dónde está la verdad. Si tu backend se entera del pago por un webhook, el webhook es el lugar fiable para informar de él —lo verás en la siguiente sección—, y la página de confirmación no debe enviar purchase además, o cada pago con tarjeta se contará dos veces. Si el único sitio donde te enteras es la página de retorno, informa allí y asume el hueco.

Las compras que ningún navegador presencia: envíalas desde tu servidor

Con las suscripciones esto es inevitable. Una prueba se convierte a los siete días, una renovación llega cada mes, una factura se paga por transferencia la semana que viene. Cada una es una compra, y ninguna ocurre delante de un navegador. La solución es enviar el evento de servidor a servidor, desde el webhook que se entera del pago, al mismo punto de entrada de etiquetado que usan tus páginas —tu nombre de host de SignalHost o tu propio dominio—, para que pase por tu contenedor de servidor como cualquier otro hit.

Del checkout necesita una sola cosa: el client id de Analytics del visitante, para que la compra se atribuya a la persona cuyas visitas la produjeron. Está en la cookie _ga, que tu solicitud de checkout ya lleva cuando va a tu propio dominio. Guárdalo con el pedido. Si no hay cookie _ga, el visitante no concedió el consentimiento de analítica: envía el hit como consentimiento denegado con un id desechable, o no lo envíes; nunca le inventes una identidad.

// In your checkout request handler: keep the visitor's Analytics id with the order.
const ga = req.cookies._ga;                        // "GA1.1.1234567890.1700000000"
order.gaClientId = ga ? ga.split('.').slice(-2).join('.') : null;

// Later, in your payment webhook — a trial converting, an invoice paid:
const params = new URLSearchParams({
  v: '2',
  tid: 'G-XXXXXXXXXX',                              // your GA4 measurement ID
  cid: order.gaClientId ?? `${Date.now()}.${Math.floor(Math.random() * 1e9)}`,
  gcs: order.gaClientId ? 'G101' : 'G100',          // no _ga cookie = no consent
  en: 'purchase',
  'ep.transaction_id': invoice.id,
  'epn.value': '49.00',
  cu: 'EUR',
  pr1: 'idplan-pro~nmPro~pr49.00~qt1',
});
await fetch(`https://metrics.example.com/g/collect?${params}`, { method: 'POST' });

Elige una fuente por evento y no te salgas de ella. Así es exactamente como signalhost.io mide sus propias ventas: el navegador envía sign_up, begin_checkout y start_trial; el webhook de Stripe envía purchase cuando se paga la primera factura; ningún evento sale de los dos. El Measurement Protocol de Google también funcionaría, pero va directo a Google y se salta tu contenedor de servidor, así que ninguna de tus etiquetas del lado del servidor llega a ver la venta.

El consentimiento no te pide nada más

Envía los eventos elija lo que elija el visitante. Las etiquetas obedecen a Consent Mode: con consentimiento envían con normalidad; sin él, envían pings sin cookies que Analytics modela en lugar de contarlos en sus informes. Retener tú los envíos solo le oculta conversiones a ese modelado. Lo que sí importa es el orden: el fragmento de Tag Manager va después del script de tu banner de consentimiento, para que los valores por defecto del consentimiento existan antes de que se envíe el primer evento.

Comprobar que funciona

  • Abre el contenedor en SignalHost. La tarjeta de eventos enumera lo que llegó, por nombre, con la proporción que llevaba consentimiento. Un nombre que enviaste y que no ves ahí nunca llegó a tu servidor de etiquetado.
  • Recorre tu checkout en el modo Vista previa de Tag Manager. Muestra cada envío, qué etiqueta se disparó y el objeto ecommerce que se envió.
  • En Analytics, DebugView muestra los eventos a medida que llegan. Los informes estándar pueden tardar un día.
  • Recarga la página de confirmación dos veces y cuenta las compras. Debería seguir habiendo una.

Aquí todos los fallos son silenciosos: un nombre con las mayúsculas mal puestas, value enviado como cadena, items anidado un nivel de más. Ninguno produce un error en ningún sitio. Todos producen un informe con un agujero.

Deja de perder datos por los bloqueadores

Diez mil solicitudes al mes, gratis, todo el tiempo que quieras. Cuatro minutos para saber si te ayuda.