Diil Docs
  1. Documentación
  2. El widget

Eventos y analítica: visitas, eventos propios y conversiones

Actualizado:

La etiqueta widget.js que ya añadiste para la edición en vivo cuenta en silencio cada visita a una página, averigua de dónde viene el visitante y qué anuncio lo trajo. Añade una línea de JavaScript (o una petición desde tu servidor) y verás también quién añadió algo al carrito, quién se registró y quién pagó de verdad, con el dinero incluido. Sin un segundo script de analítica, sin gestor de etiquetas y sin reescribir el banner de cookies.

Esto es lo que hay en la carta:

  • Visitas a páginas: automáticas, cero código. Canales, etiquetas UTM, ids de clic publicitario, geografía, dispositivos.
  • Eventos propios desde el navegador: window.crmTrack('add_to_cart', …) para señales de UX.
  • Eventos de servidor: POST /marketing/event con una clave secreta, para compras y todo lo que deba contarse exactamente una vez.

Todo acaba en el CRM, en Marketing: Tráfico para las visitas, Eventos para tus propios eventos y Canales publicitarios para los enlaces etiquetados.

Lo que funciona desde el primer momento: visitas a páginas

Si widget.js está en la página, las visitas ya se están contando. La etiqueta es la misma de Edición en vivo:

<script src="https://widget.sitecog.com/widget.js" defer></script>

En cada carga de página el widget envía una visita con navigator.sendBeacon: una petición diminuta de tipo “dispara y olvídate” que no ralentiza la página y sobrevive aunque el visitante cierre la pestaña. El servidor responde 204 No Content; no hay nada que leer de vuelta.

Qué se recoge

Desde el navegador:

  • URL de la página, título y referrer;
  • etiquetas UTM: utm_source, utm_medium, utm_campaign, utm_term, utm_content;
  • ids de clic publicitario: gclid, gbraid, wbraid (Google), yclid (Yandex), fbclid (Meta), msclkid (Microsoft);
  • el código de enlace publicitario ?dl= de los enlaces de Canales publicitarios;
  • algunas cookies publicitarias, si tu web ya las tiene: _ga, _gcl_*, _ym_uid, _fbp, _fbc (con el modo de consentimiento, no se leen hasta que el visitante acepta, y con Global Privacy Control no se leen nunca);
  • tamaño de pantalla y de viewport, densidad de píxeles, idioma del navegador y zona horaria.

Lo que añadimos nosotros:

  • ubicación, a partir de la dirección IP;
  • tipo de dispositivo, navegador y sistema operativo;
  • canal de tráfico: de pago, social, orgánico, referido, email o directo;
  • visitante nuevo o recurrente.

Antes de guardar nada, recortamos la IP y limpiamos las URLs de fragmentos #… y de parámetros delicados como token o email. Los detalles, en Qué guardamos y durante cuánto.

Los bots no se tiran: se marcan y se filtran de los informes. Así, los números que miras hablan de personas, y los datos en bruto siguen ahí por si algún día te preguntas cuánto de tu tráfico son crawlers (spoiler: más de lo que te gustaría).

Los informes viven en Marketing → Tráfico: visitantes, sesiones, canales, páginas, geografía y dispositivos.

Días y horas en los informes

“Ayer” en un informe es ayer en la zona horaria de tu sitio, no en UTC ni en la zona de quien esté mirando el gráfico. Una tienda en Madrid ve su pico de la tarde a las 20:00, no a las 18:00, y una dueña en Madrid y un gestor en Nueva York miran los mismos días.

  • La zona se configura en CRM → Ajustes → Zona horaria. Mientras nadie elija una, el CRM toma la zona horaria del navegador del propietario.
  • Los días en Tráfico, Canales publicitarios y Eventos, y las horas de los gráficos, la siguen.
  • Cambiar la zona recalcula también los informes pasados. Los datos no cambian: solo cambia dónde cae la medianoche.

Ids de visitante y de sesión

El widget reconoce a un visitante que vuelve sin usar cookies. Guarda tres claves en localStorage:

ClaveQué esCuánto dura
crm_vidId del visitanteHasta que el visitante borre los datos del sitio
crm_sidId de sesiónEmpieza una nueva tras 30 minutos de inactividad
crm_satMomento de la última actividad: sirve para decidir cuándo termina una sesiónSe actualiza mientras el visitante navega

Si el almacenamiento está bloqueado (algunos modos de privacidad lo hacen), los ids viven en memoria mientras la pestaña siga abierta. Quédate con crm_vid y crm_sid: los necesitarás para vincular eventos de servidor al visitante.

Con el modo de consentimiento activado, estas claves solo aparecen después de que el visitante acepte, y crmConsent.deny() las borra.

Aplicaciones de una sola página

En una SPA (React Router, navegación de cliente de Next.js, Vue Router) la página no se recarga al cambiar de ruta, pero el widget se entera igualmente, sin que tengas que tocar nada. Escucha history.pushState, history.replaceState y el evento popstate y, tras una pausa de 300 ms (para que una ráfaga de redirecciones cuente como una sola visita), envía una visita si ha cambiado la ruta o la query string. El referrer de esa visita “virtual” es la página anterior de tu web, así que los recorridos se leen igual que en una web clásica.

El comportamiento se ajusta con el atributo data-spa de la etiqueta:

data-spaQué cuenta como una visita nueva
sin atributo (por defecto)Un cambio de ruta o de query string vía pushState / replaceState / popstate.
"hash"Lo mismo, y además los cambios de hash: para routers del estilo #/route.
"off"El seguimiento de SPA se apaga: una visita por cada carga del script, como antes.
Un router con hashhtml
<script src="https://widget.sitecog.com/widget.js" data-spa="hash" defer></script>

¿Un paso que no cambia la URL, como un modal o un asistente de varios pasos? Eso no es una visita: envíalo como evento propio.

Privacidad y consentimiento

El widget no pone cookies, y si tu web necesita consentimiento antes de la analítica, se enchufa a tu banner en vez de pelearse con él. Sin código de carga diferida ni trucos: un atributo en la etiqueta y dos botones.

Añade data-consent="required" a la etiqueta del widget:

<script src="https://widget.sitecog.com/widget.js" data-consent="required" defer></script>

Hasta que el visitante acepte:

  • no se envían visitas ni eventos. Se descartan, no se guardan en cola: si el visitante acepta más tarde, no se envía nada con efecto retroactivo;
  • no se lee document.cookie, así que las cookies publicitarias (_ga, _gcl_*, _ym_uid, _fbp, _fbc) quedan intactas;
  • no se crean ni se guardan crm_vid, crm_sid ni crm_sat.

Los formularios de leads y el chat de soporte siguen funcionando antes del consentimiento, solo que sin vid/sid, es decir, sin atribución: ese lead no queda vinculado a ningún canal, etiqueta UTM ni enlace publicitario.

Sin el atributo no cambia nada: la cuenta empieza en cuanto carga la página, como siempre.

Tu banner habla con el widget a través de window.crmConsent:

crmConsent.grant()functionopcional
El visitante acepta. Empieza la cuenta y se envía una vez la visita de la página actual, para que no se pierda justo la visita en la que alguien pulsó “Aceptar”.
crmConsent.deny()functionopcional
El visitante rechaza. Borra crm_vid, crm_sid y crm_sat. Se respeta incluso sin data-consent: mira más abajo.
crmConsent.status()() => 'granted' | 'denied' | 'pending'opcional
La elección actual. pending: el visitante aún no ha decidido.
crmConsent.requiredbooleanopcional
true si la etiqueta lleva data-consent="required".
crmConsent.gpcbooleanopcional
true si el navegador envía la señal Global Privacy Control.

La elección se guarda en localStorage con la clave crm_consent, así que en las siguientes visitas el widget ya la conoce y tu banner no tiene que volver a preguntar.

¿Y si el banner responde antes de que cargue widget.js? Lo mismo que con crmq: una cola. push sigue funcionando después de la carga, así que tu banner puede usar siempre push y olvidarse del orden de carga.

window.crmConsent = window.crmConsent || [];
crmConsent.push('grant');   // o 'deny'

Cada vez que cambia la elección, el widget lanza el evento crm:consent en window. En el manejador, lee el estado con window.crmConsent.status().

<div id="cookie-banner" hidden>
  <p>Usamos analítica propia para saber qué funciona en la web. ¿Te parece bien?</p>
  <button type="button" data-choice="grant">Aceptar</button>
  <button type="button" data-choice="deny">Rechazar</button>
</div>

<script>
  window.crmConsent = window.crmConsent || [];
  const banner = document.getElementById('cookie-banner');

  banner.addEventListener('click', (e) => {
    const choice = e.target.closest('[data-choice]')?.dataset.choice;
    if (!choice) return;
    crmConsent.push(choice); // 'grant' o 'deny': vale antes y después de cargar widget.js
    banner.hidden = true;
  });

  // Enseña el banner solo a quien todavía no ha elegido
  function syncBanner() {
    banner.hidden = window.crmConsent.status?.() !== 'pending';
  }
  window.addEventListener('load', syncBanner);
  window.addEventListener('crm:consent', syncBanner);
</script>

<script src="https://widget.sitecog.com/widget.js" data-consent="required" defer></script>

Global Privacy Control y “No me rastrees”

Si el navegador envía la señal Global Privacy Control (navigator.globalPrivacyControl), el widget no lee nunca las cookies publicitarias, en ningún modo: con data-consent o sin él. Puedes comprobarlo en crmConsent.gpc.

Además, crmConsent.deny() se respeta en cualquier web, también sin el atributo data-consent: borra los ids del visitante y la renuncia queda recordada. Así, un simple enlace en el pie de página basta para ofrecer una forma de darse de baja:

En el pie de páginahtml
<a href="#" id="dont-track">No quiero que me rastreen</a>

<script>
  document.getElementById('dont-track').addEventListener('click', (e) => {
    e.preventDefault();
    window.crmConsent = window.crmConsent || [];
    crmConsent.push('deny'); // borra crm_vid / crm_sid / crm_sat, con o sin data-consent
    e.currentTarget.textContent = 'Hecho: ya no te rastreamos';
  });
</script>

Qué guardamos y durante cuánto

Direcciones IP recortadas. En IPv4 el último octeto pasa a cero (203.0.113.57 → 203.0.113.0); en IPv6 la dirección se corta a /48. El país y la ciudad se calculan con la IP completa antes del recorte, así que los informes de geografía siguen funcionando.

URLs limpias. De las URLs de página, los referrers, la URL de entrada y las URLs de los eventos quitamos el fragmento (#…) y estos parámetros de query:

token *_token access_token id_token refresh_token auth_token auth password pass passwd pwd email e-mail mail key api_key apikey secret client_secret otp session sessionid jwt

*_token significa cualquier nombre que acabe en _token. En cambio, code se queda (los códigos promocionales son útiles en los informes), igual que las etiquetas UTM, los ids de clic publicitario y dl.

13 meses y fuera. Las visitas y los eventos se guardan 13 meses (395 días) y después se borran; quien opera la instalación puede cambiar ese plazo. Los leads no entran en esta limpieza: no se borran.

Un visitante puede pedir que lo olvidemos antes. El propietario del sitio borra su perfil, sus visitas y sus eventos por id de visitante directamente desde el CRM; los leads, solo si se elige aparte. Más detalles en Leads → Eliminar los datos de una persona.

En Marketing → Canales publicitarios creas enlaces etiquetados para cada sitio donde te anuncias: una publicación en una red social, una newsletter, un banner en la web de otro. Cada enlace apunta a tu web y lleva etiquetas UTM más un código corto:

https://shop.example/?utm_source=instagram&utm_medium=social&utm_campaign=autumn_sale&dl=k3Zp9QaW1x

dl es un código de 10 caracteres que identifica el enlace. No tienes que hacer nada en la web: el widget lo recoge con la primera visita, y cada visita, evento y lead de esa sesión se atribuye al enlace. Cada enlace tiene sus propias estadísticas en el CRM.

Eventos propios

Las visitas te dicen adónde fue la gente. Los eventos te dicen qué hizo: añadir al carrito, registrarse, abrir la calculadora de precios, pagar. Describes un evento una vez en el CRM y luego lo envías desde el navegador o desde tu servidor.

Paso cero: declara el evento en el CRM

Diil solo acepta los eventos que conoce. Y es una ventaja: una errata en tu código no puede crear en silencio un tipo de evento nuevo y partirte los informes en dos.

  1. Abre Marketing → Eventos y haz clic en “Crear evento”

    Necesitas tener un sitio (dominio) seleccionado arriba.
  2. Ponle exactamente el nombre que usa el código

    add_to_cart, signup_completed, purchase. Letras latinas, dígitos y guiones bajos, empezando por una letra, hasta 64 caracteres. Añade un título legible y una descripción: tu yo del futuro te lo agradecerá.
  3. Describe los parámetros

    Hasta 20 por evento, cada uno con un tipo: Texto, Número o Sí/no (booleano). Lo que no se describa aquí no llegará a la base de datos.
  4. ¿Hay dinero? Marca “Este evento genera ingresos”

    Y pon el nombre de la moneda (EUR, USD, USDT, incluso tus propios puntos de fidelidad). Sin esta casilla, el value del evento no se guarda.
  5. Copia la llamada lista

    El CRM te muestra la llamada exacta a crmTrack y la petición de servidor para este evento. Pegar y listo.

Un sitio puede tener hasta 100 tipos de evento activos. En cuanto un evento tiene datos, su nombre queda bloqueado (ya está en tu código) y el evento se puede archivar en lugar de eliminarse, para que los informes nunca acaben con filas sin nombre.

Envía eventos desde el navegador: crmTrack

Firmats
window.crmTrack(
  name: string,
  params?: Record<string, string | number | boolean>,
  options?: { id?: string; value?: number; currency?: string },
): void

Es de tipo “dispara y olvídate”: no devuelve nada, nunca te lanza excepciones y envía con sendBeacon, así que el evento sobrevive aunque el clic te saque de la página. Los ids de visitante y de sesión se adjuntan solos: tú solo describes qué ha pasado.

Argumentos

namestringobligatorio
El nombre del evento tal como está declarado en el CRM. Letras latinas, dígitos y guiones bajos, empieza por una letra, hasta 64 caracteres. Un nombre no declarado se descarta en silencio: mira Solución de problemas.
paramsobjectopcionalPor defecto: {}
Objeto plano con los detalles del evento: { sku: 'air3-graphite', price: 149 }. Las claves siguen las mismas reglas que los nombres, hasta 40 caracteres. Solo se conservan los parámetros descritos en el CRM; los valores se convierten al tipo declarado: mira Parámetros y tipos.
options.idstringopcionalPor defecto: ninguno
Clave de idempotencia, hasta 128 caracteres, única por sitio. Envía el mismo id dos veces y el evento se guarda una sola. Para compras, usa el id de tu pedido.
options.valueintegeropcionalPor defecto: ninguno
Importe en unidades menores: 149900 significa 1499,00. De 0 a 1012. Solo se guarda si el evento tiene activado “Este evento genera ingresos”.
options.currencystringopcionalPor defecto: la moneda del evento
Etiqueta de moneda, de 1 a 10 letras latinas o dígitos: EUR, USD, UAH. Si la omites, se usa la moneda configurada en el evento en el CRM.

Ejemplos

Los tres eventos que necesita casi cualquier tienda, esté hecha tu web con lo que esté hecha:

<button id="buy" data-sku="air3-graphite" data-price="149">Añadir al carrito</button>

<form id="signup">…</form>

<script>
  // ?. para que un bloqueador de anuncios que se haya comido widget.js no rompa tu botón
  document.getElementById('buy').addEventListener('click', (e) => {
    const { sku, price } = e.currentTarget.dataset;
    window.crmTrack?.('add_to_cart', { sku, price: Number(price) });
  });

  // Llámala cuando la cuenta se haya creado de verdad, no en el primer clic
  function onSignupSuccess() {
    window.crmTrack?.('signup_completed', { method: 'email', newsletter: true });
  }

  // En la página de "gracias". Esto se ejecuta antes que el widget.js diferido,
  // así que pasa por la cola (ver más abajo); el id hace que recargar no haga daño
  window.crmq = window.crmq || [];
  crmq.push([
    'purchase',
    { order_id: 'A-1024', items: 2 },
    { id: 'A-1024', value: 29800, currency: 'EUR' }, // 298,00 EUR
  ]);
</script>

Llamar antes de que cargue el script: la cola crmq

widget.js se carga con defer, así que durante un instante window.crmTrack todavía no existe. Los eventos lanzados demasiado pronto (al cargar la página, en un efecto, desde un script inline en el <head>) se perderían. La cola lo soluciona:

Funciona antes y después de que cargue widget.jsjs
window.crmq = window.crmq || [];
crmq.push(['purchase', { order_id: 'A-1024', items: 2 }, { id: 'A-1024', value: 29800, currency: 'EUR' }]);

Cada elemento es un array con los mismos tres argumentos que crmTrack: nombre, parámetros y opciones. Cuando widget.js se carga, reproduce todo lo que hay en la cola. A partir de ahí, crmq.push envía al instante, así que puedes usar siempre la cola y olvidarte del orden de carga.

Un helper diminuto que siempre se puede llamar sin riesgots
type CrmParams = Record<string, string | number | boolean>;
type CrmOptions = { id?: string; value?: number; currency?: string };

export function track(name: string, params?: CrmParams, options?: CrmOptions) {
  if (typeof window === 'undefined') return; // render en servidor: nada que hacer
  window.crmq = window.crmq || [];
  window.crmq.push([name, params || {}, options || {}]);
}

Parámetros y tipos

Los parámetros se comprueban contra la descripción del CRM y se normalizan al tipo declarado según estas reglas:

Tipo declaradoAceptaConviene saber
TextoCualquier valorSe recorta a 500 caracteres. Los objetos se convierten en cadenas JSON, aunque los valores planos quedan mucho mejor en los informes.
NúmeroNúmeros finitos de hasta 1012 en valor absolutoSe redondea a 4 decimales.
Sí/notrue, false, "true", "false", 1, 0Muy práctico cuando el valor sale de un atributo data-*.

Claves de parámetro: letras latinas, dígitos y guiones bajos, empezando por una letra, hasta 40 caracteres. Hasta 20 parámetros por evento.

Ingresos e idempotencia

value y currency

El dinero se envía como un entero en unidades menores: céntimos, kopeks, satoshis, lo que sea la unidad más pequeña de tu moneda. 29800 con EUR son 298,00 €. El dinero con decimales en una base de datos acaba tarde o temprano en descuadres de céntimos, así que directamente no lo permitimos.

  • value: un entero de 0 a 1012.
  • currency: de 1 a 10 letras latinas o dígitos (EUR, USD, UAH, USDT). Si la omites, se usa la moneda configurada en el evento.
  • Los dos se guardan solo si el evento tiene marcado Este evento genera ingresos; si no, value acaba siendo null.
const total = 298.0;                         // lo que muestra tu carrito
const value = Math.round(total * 100);       // 29800: lo que quiere Diil

id: cuéntalo una vez

Las páginas de “gracias” se recargan, se abren desde el historial, se comparten a un segundo dispositivo. Pasa un id (en el navegador) o un event_id (desde el servidor) y Diil guarda el evento una sola vez, llegue las veces que llegue. Hasta 128 caracteres, único por sitio. Tu número de pedido es el candidato perfecto.

Eventos de servidor: POST /marketing/event

Para todo lo que tenga que ver con dinero, el navegador no es el sitio donde contarlo. Los bloqueadores de anuncios pueden bloquear la petición, la gente cierra la pestaña antes de que cargue la página de “gracias” y cualquiera puede llamar a crmTrack('purchase') desde la consola. Tu servidor, en cambio, sabe exactamente cuándo se confirma un pago. Envía el evento desde ahí:

POST https://back.sitecog.com/marketing/event
content-type: application/json
x-event-key: sk_…

Claves secretas

Los eventos de servidor se firman con una clave secreta: Marketing → Eventos → Claves secretas → Emitir una clave. Tiene la forma sk_ + 48 caracteres hexadecimales. Puedes tener hasta 5 claves activas por sitio (una por servidor o integración) y revocar cualquiera de ellas en el CRM. Los leads enviados desde un servidor usan las mismas claves.

Petición

x-event-keyheaderstringobligatorio
Tu clave secreta, sk_….
namestringobligatorio
Nombre del evento tal como está declarado en el CRM. Alias corto: n.
event_idstringopcional
Clave de idempotencia, hasta 128 caracteres, única por sitio. Una repetición se responde con duplicate: true y no se vuelve a guardar. Alias: id.
visitor_idstringopcional
El crm_vid del visitante, sacado del navegador. Alias: vid.
session_idstringopcional
El crm_sid del visitante. Con él, el evento hereda el canal, las etiquetas UTM y el enlace publicitario de esa sesión. Alias: sid.
paramsobjectopcional
Parámetros del evento, con las mismas reglas que en el navegador. Alias: p.
valueintegeropcional
Importe en unidades menores, de 0 a 1012. Alias: val.
currencystringopcional
Etiqueta de moneda; por defecto, la moneda del evento. Alias: cur.
urlstringopcional
La página con la que tiene que ver el evento, por ejemplo tu checkout. Alias: u.

El cuerpo entero tiene que caber en 8 KB.

// Node 18+: fetch viene de serie
export async function sendPurchase(order) {
  const res = await fetch('https://back.sitecog.com/marketing/event', {
    method: 'POST',
    headers: {
      'content-type': 'application/json',
      'x-event-key': process.env.DIIL_EVENT_KEY,
    },
    body: JSON.stringify({
      name: 'purchase',
      event_id: order.id,              // "A-1024": los reintentos son seguros
      visitor_id: order.crmVid,        // guardado al pagar, puede ser null
      session_id: order.crmSid,
      params: { order_id: order.id, items: order.items.length },
      value: order.totalCents,         // 29800 = 298,00
      currency: 'EUR',
      url: 'https://shop.example/checkout',
    }),
    signal: AbortSignal.timeout(5000),
  });

  if (!res.ok) {
    console.error('Diil event failed:', res.status, await res.text());
  }
  return res.ok;
}

Respuesta

200 OKjson
{ "ok": true, "duplicate": false }
200 OK: el mismo event_id otra vezjson
{ "ok": true, "duplicate": true }
okboolean
El evento se ha aceptado.
duplicateboolean
True si ya existe un evento con este event_id. No se ha guardado nada nuevo, y eso está bien: no es un error.

Errores

A diferencia del navegador, la vía del servidor te dice exactamente qué ha fallado:

EstadoCuerpoQué ha pasado
400{"message":"invalid_body"}El cuerpo no es un JSON válido o no es el objeto esperado.
400{"message":"invalid_event_name"}El nombre no cumple el formato: letras latinas, dígitos y guiones bajos, empieza por una letra, hasta 64 caracteres.
400{"message":"unknown_event","name":"purchse"}No existe ese evento en Marketing → Eventos. Una errata, o aún no está declarado. El cuerpo te devuelve el nombre que enviaste.
401{"message":"invalid_key"}x-event-key falta, está mal formada o revocada.
413—El cuerpo pesa más de 8 KB.
429{"message":"rate_limit_exceeded"}Demasiadas peticiones en este minuto. Mira Límites.

Un evento de servidor por sí solo no sabe nada de anuncios: tu servidor no tiene ni idea de que el comprador llegó desde una publicación de Instagram hace tres días. El navegador sí. Así que el truco es llevar los ids del widget desde el navegador hasta tu backend junto con el pedido:

  1. al pagar, lee crm_vid y crm_sid de localStorage;
  2. envíalos a tu backend con el pedido y guárdalos junto a él;
  3. cuando se confirme el pago, pásalos como visitor_id y session_id.

Así el evento hereda el canal, las etiquetas UTM y el enlace publicitario de la primera visita de la sesión, y Marketing → Eventos muestra qué canal y qué enlace publicitario trajeron el dinero, no solo los clics.

// checkout.js: cuando el cliente pulsa "Pagar"
function diilIds() {
  try {
    return {
      crm_vid: localStorage.getItem('crm_vid'),
      crm_sid: localStorage.getItem('crm_sid'),
    };
  } catch {
    return { crm_vid: null, crm_sid: null }; // almacenamiento bloqueado: el pedido sigue adelante
  }
}

const res = await fetch('/api/orders', {
  method: 'POST',
  headers: { 'content-type': 'application/json' },
  body: JSON.stringify({ cart, ...diilIds() }),
});

Límites

Los límites son comunes a todas las rutas de marketing (visitas, eventos y leads juntos) y se cuentan en ventanas fijas de un minuto.

QuéLímite
Peticiones por IP120 por minuto
Peticiones por sitio6000 por minuto
Cuerpo de la petición8 KB (más → 413)
Tipos de evento activos por sitio100
Parámetros por evento20
Nombre de evento / clave de parámetro64 / 40 caracteres
Valor de un parámetro de texto500 caracteres
id / event_id128 caracteres
Claves secretas activas por sitio5

Solución de problemas

El evento no aparece

  • No está declarado. El endpoint del navegador siempre responde 204 y descarta en silencio los nombres de evento desconocidos: sin pistas, por diseño. El endpoint del servidor es sincero: 400 unknown_event con el nombre que enviaste. Ante la duda, envía el mismo evento una vez con curl y lee la respuesta.
  • El nombre no coincide. addToCart en el código y add_to_cart en el CRM son dos cosas distintas. Copia la llamada de la tarjeta del evento.
  • El modo de consentimiento está activado y aún no has aceptado. Si crmConsent.status() devuelve 'pending', las visitas y los eventos se descartan (no se guardan para después). Acepta en tu propio banner y vuelve a probar: mira Modo de consentimiento.
  • Estás probando en el modo Live. Dentro del iframe del CRM no se registra nada. Usa una pestaña normal.
  • El script no se ha cargado o se llamó a crmTrack demasiado pronto. Usa la cola crmq.
  • El dominio no es un sitio del CRM. El sitio se reconoce por el Origin de la página (el www. se ignora); uno desconocido recibe 400 unknown_domain: búscalo en la pestaña Network de DevTools.
  • Lo bloquea tu CSP. Permite https://widget.sitecog.com en script-src y https://back.sitecog.com en connect-src.

El evento está, pero faltan parámetros

Abre el evento en Marketing → Eventos. Si ves Llega pero no está descrito, el parámetro nos llegó pero no está en la descripción del evento: añádelo (o corrige la errata en el código). Comprueba también que los valores encajan con los tipos declarados de Parámetros y tipos.

La compra no tiene importe

Marca Este evento genera ingresos en el evento y envía value como un entero en unidades menores: 29800, no 298.00 y tampoco "298 €".

Los números no cuadran con el sistema de pagos

Algunos visitantes usan bloqueadores de anuncios o extensiones de privacidad que bloquean las peticiones de analítica, y otros cierran la pestaña antes de la página de “gracias”. Los eventos del navegador siempre se quedarán un poco cortos. Para señales de UX no pasa nada; para el dinero, sí: envía las compras desde el servidor. No se puede bloquear, no se puede falsear desde la consola y, con event_id, nunca se cuenta dos veces.

Nombrar eventos: unos cuantos hábitos que compensan

  • snake_case, en inglés y con aire de verbo: add_to_cart, signup_completed, purchase, calculator_opened. Nada de click1 ni ButtonPressed.
  • Nombra el resultado, no el botón. signup_completed sobrevive a un rediseño; green_button_click, no.
  • Un evento, muchos parámetros. add_to_cart con { sku, price } es mejor que add_to_cart_air3, add_to_cart_air4… y te mantiene lejos del límite de 100 tipos.
  • Pasa siempre un id en todo lo que pueda dispararse dos veces: compras, confirmaciones, registros únicos.
  • El navegador para el comportamiento, el servidor para el dinero. Clics y pasos con crmTrack; pagos, devoluciones y suscripciones desde tu backend.
  • Escribe la descripción en el CRM. “Se dispara cuando el proveedor de pagos confirma el cobro” te ahorra una reunión dentro de seis meses.

Qué leer después