Código abierto

Un solo event_id para el navegador y tu servidor

ad-events es una librería pequeña de TypeScript que extrajimos de la propia medición de LoomaScale. Genera un identificador de deduplicación por conversión y entrega ese mismo valor al Meta Pixel, a Google Ads, al píxel de ChatGPT Ads y a sus Conversions API del servidor, así que una conversión reportada dos veces se cuenta una sola vez.

Ver en GitHubInstalaciónnpm install ad-events
  • Sin dependencias
  • TypeScript, ESM y CJS
  • Node, Bun, Deno y Edge

Por qué las conversiones se cuentan dos veces

Medir solo desde el navegador pierde entre una quinta parte y un tercio de las conversiones por los bloqueadores de anuncios y los límites de privacidad de los navegadores, así que la solución habitual es enviar las mismas conversiones otra vez desde el servidor. Si lo haces sin un identificador compartido, cada compra se reporta dos veces: el ingreso de tus informes queda inflado, el costo por conversión parece mejor de lo que es, y las estrategias automáticas de oferta optimizan hacia un número que nunca existió.

Todas las plataformas soportan la solución, y cada una la nombra distinto. Meta espera eventID en el cuarto argumento de la llamada al píxel y event_id dentro del evento del servidor. El píxel de ChatGPT Ads espera event_id en el argumento de opciones y el monto como entero en centavos, no como decimal. Google Ads no tiene pareja de navegador y servidor: deduplica por transaction_id. Si te equivocas en uno de ellos nada falla, y te enteras semanas después, por el gasto.

Cómo funciona la librería

Una sola llamada reporta la conversión a todas las plataformas que configuraste y devuelve el identificador que usó, para que lo envíes a tu propio backend en el mismo manejador del clic.

  1. 1El navegador genera el identificador

    Tiene que ser el navegador, porque es el lado que quizá no reporte nada. Genéralo donde ocurre el clic y el evento del servidor seguirá siendo deduplicable incluso cuando un bloqueador se coma el píxel, que es justamente el caso por el que agregaste el evento del servidor.

  2. 2Cada adaptador recibe el mismo valor

    Los adaptadores de Meta, Google Ads y ChatGPT Ads lo colocan donde su plataforma lo espera y convierten el monto a la unidad que esa plataforma espera. Un adaptador cuyo píxel no se cargó no hace nada, nunca lanza un error, así que un reporte fallido jamás rompe un checkout.

  3. 3El servidor lo repite

    Cuando llega el webhook del pago, la única prueba real de que el dinero se movió, el servidor envía el mismo nombre de evento y el mismo identificador. La plataforma reconoce el par y conserva una sola conversión.

En el navegador, en el manejador del clic
import {
  createTracker,
  metaAdapter,
  googleAdsAdapter,
  openAiAdapter,
} from "ad-events/browser";

const tracker = createTracker({
  adapters: [
    metaAdapter({ eventNames: { checkout_started: "InitiateCheckout" } }),
    googleAdsAdapter({
      conversionId: "AW-123456789",
      labels: { checkout_started: "abcDEF_ghi" },
    }),
    openAiAdapter(),
  ],
});

// One id, every platform — and it is the return value, so you
// cannot forget to send it to your own backend.
const eventId = tracker.track({
  name: "checkout_started",
  value: 49,
  currency: "USD",
});

await fetch("/api/checkout", {
  method: "POST",
  body: JSON.stringify({ plan, price, eventId }),
});
En el servidor, en el webhook del pago
import { createMetaConversionsApi } from "ad-events/server";

const meta = createMetaConversionsApi({
  pixelId: process.env.META_PIXEL_ID,
  accessToken: process.env.META_ACCESS_TOKEN,
});

await meta.send({
  eventName: "InitiateCheckout", // identical to the browser event
  eventId,                       // the id the browser minted
  email: order.email,            // normalized and hashed for you
  fbp, fbc,                      // the cookies the browser read
  customData: { value: order.price, currency: "USD" },
});

Qué cubre

  • Meta Pixel y Conversions API

    Un event_id compartido, las cookies _fbp y _fbc leídas en el navegador para que el evento del servidor las lleve, y los campos de identidad normalizados y hasheados con SHA-256 igual que los hashea Meta al hacer la coincidencia. Tu propio id de usuario se hashea antes de enviarse, nunca viaja en claro.

  • Google Ads

    Conversiones por gtag con deduplicación por transaction_id, una clave sincrónica e irreversible para que el mismo registro reportado desde dos páginas de retorno cuente una vez, y los identificadores de clic gclid, gbraid y wbraid tratados como un solo valor en lugar de tres columnas vacías.

  • ChatGPT Ads (OpenAI)

    El píxel oaiq y el Conversions API de OpenAI, incluidas las dos cosas fáciles de equivocar: el monto como entero en centavos y un tipo de payload que no se deduce del nombre del evento. Por ahora casi no existen herramientas públicas para esta plataforma.

Atribución de primer contacto, porque el embudo dura más que la URL

Un anuncio aterriza en una URL con parámetros utm y un identificador de clic. Después el visitante navega tres veces y paga dos días más tarde, y a esa altura esos parámetros ya no están: la URL de retorno de la pasarela de pago los sobrescribirá con los suyos sin pensarlo.

La librería los guarda una vez, conserva el primer contacto y los vuelve a leer cuando la conversión finalmente ocurre. También responde la pregunta que un embudo de tráfico frío se hace de verdad: ¿este visitante llegó desde un anuncio pagado? Para social pagado tienen que coincidir la fuente y el medio; un identificador de clic de Google es prueba por sí solo, porque Google solo lo agrega a un clic que te cobró.

Preguntas

¿ad-events reemplaza a Google Tag Manager?

No. Reemplaza el código de pegamento que escribirías a mano dentro de un gestor de etiquetas o de tu aplicación para mantener los identificadores consistentes entre plataformas. Los fragmentos de tus píxeles los sigues cargando tú.

¿Por qué debe generar el identificador el navegador y no el servidor?

Porque el navegador es el lado que quizá no reporte nada. Si lo genera el servidor, un píxel bloqueado significa que el evento del navegador nunca llega y no hay nada contra lo que deduplicar. Generarlo en el manejador del clic y reenviarlo mantiene el par intacto justo en el caso que motivó la medición del lado del servidor.

Mis eventos de Meta siguen sin deduplicarse. ¿Qué reviso?

Cuatro cosas, en este orden: que el nombre del evento sea idéntico en los dos lados, que el identificador del evento sea idéntico y no solo esté presente en ambos, que los dos eventos estén separados por segundos, y que ambos declaren el origen de acción website. Events Manager tiene una pestaña Test Events que te muestra qué mitad llegó.

¿Funciona en Cloudflare Workers o Vercel Edge?

Sí. El punto de entrada del servidor usa solo fetch y WebCrypto, sin módulos nativos de Node, así que corre sin cambios en Node, Bun, Deno y en entornos edge.

¿La librería envía datos a algún lugar además de las plataformas de anuncios?

No. No hay telemetría ni servicio alojado. Las únicas llamadas de red que hace son las que tú configuras hacia las plataformas que elegiste.

Hecho primero para nuestro propio embudo

LoomaScale maneja Google Ads y Meta Ads desde ChatGPT y Claude. Esta librería es la capa de medición debajo de ese producto, y la publicamos porque el problema de deduplicación que resuelve lo enfrenta todo anunciante.

Ver qué hace LoomaScale