Pular para o conteúdo principal

Catálogo unificado de eventos

Esta página é o mapa canônico de todos os eventos que o front rastreia, organizados por fase do funil com a matriz de destinos (quem recebe o quê). Substitui o antigo eventos-gtm.md (que listava apenas o canal GTM).

Fonte de verdade

  • Type registry: repos/front-web-base/app/analytics/events.ts — discriminated union de todos os eventos. Adicionar evento novo = uma variant aqui.
  • Matrix: repos/front-web-base/app/analytics/destinations.ts — mapa EventId → DestinationMap. Marketing pede destino novo num evento existente = uma linha aqui.
  • Dispatcher: repos/front-web-base/app/analytics/dispatch.ts — fan-out engine. Lê a matriz e chama os adapters non-null.

Princípios

  1. Front mapeia o que o front observa. Eventos backend-only (pix paid via webhook PSP, bet placed via game iframe webhook, dormant detection via cron) NÃO estão aqui — vão pra Contract BFF.
  2. Adicionar um destino é uma linha na matriz. Se a Meta quer receber viewed_casino como ViewContent, marketing pede e dev troca meta_pixel: null por meta_pixel: { name: "ViewContent", payloadFn: ... }.
  3. Dispatch é exhaustivo at compile time. TypeScript erra se evento novo entra no registry sem destino mapeado.

Canais de destino

A interface DestinationMap em destinations.ts declara 11 canais (lista viva — confira o arquivo antes de assumir). Todo evento precisa declarar os 11 explicitamente (null ou slot).

CanalComo é dispatchedStatus
gtmdataLayer.push({ event, ... })✅ Ativo (brands com GTM habilitado — ver disableGtm)
meta_pixelfbq("track", name, params)✅ Ativo (brands com pixelId)
kwaikwaiq.instance(id).track(name)✅ Ativo (brands com kwaiPixelId)
taboola_tfa.push({ notify: "action", name })✅ Ativo (brands com taboolaId)
floodlightgtag('event','conversion',{ send_to }) — Google DV360✅ Ativo (brands com analyticsConfig.floodlight)
pendopendo.track(name, payload)✅ Ativo (brands com pendoConfig.enabled)
audience_cookieaddAudienceTag(name) no <rmkCookie>_aud✅ Ativo (brands com remarketing habilitado)
tiktok_pixelttq.track(name, params) direto🟡 Hoje só via GTM custom HTML — slot fica null na matriz pra evitar dupla contagem
smarticoSDK próprio🟡 Fluxo separado (não consome a matriz)
webtrendsA/B testing🟡 Fluxo separado (não consome eventos)
bff_capiexternal_id no body de signup/deposit pro BFF🟡 Roadmap (depende do BFF — ver Contract BFF)

Os dois canais que dependem de config de brand

Marketing costuma reportar "esse evento não chega" nesses dois — porque o slot está na matriz mas o canal está inerte na brand:

  • floodlight só dispara quando a brand define analyticsConfig.floodlight. Hoje: cl-bet7k-com e ng-7k-bet. O slot.name na matriz é a chave canônica de atividade (allPages, signUp, addToCart, ftd, purchase) — a string cat real é resolvida por brand em analyticsConfig.floodlight.activities. Detalhes em Plataformas → Floodlight.
  • pendo só dispara quando pendoConfig.enabled === true. Hoje: cl-bet7k-com e ng-7k-bet. Só eventos de conversão passam pela matriz; login fica deliberadamente null porque o usePendo já emite login reativamente — rotear pela matriz duplicaria.

Dispatch durante prerender (Speculation Rules)

Enquanto document.prerendering === true, o dispatcher enfileira tudo e só libera no prerenderingchange (quando a página vira aba visível de fato). É uma proteção de atribuição: sem ela, página pré-renderizada e nunca visitada geraria pageview/conversão fantasma. Código: PRERENDER_QUEUE em app/analytics/dispatch.ts.

Mapa por fase do funil

A. Pré-cadastro (visitor anônimo)

EventoTrigger no frontDestinos ativos
page_viewtoda navegação SPAgtm, meta_pixel (PageView), kwai (contentView), floodlight (allPages)
view_modal_loginabrir modal de logingtm
view_modal_cadastroabrir modal de cadastrogtm
viewed_casinorota sob casinoaudience_cookie
viewed_live_casinorota casino.liveaudience_cookie
viewed_game_detailrota casino.playaudience_cookie
viewed_sportsrota sob sportsaudience_cookie
viewed_promotionsrota sob promotionsaudience_cookie
played_demo 🆕clique no botão "Demo" no GameIframegtm, audience_cookie
clicked_registerabertura do modal de registeraudience_cookie
started_register 🆕user digita em email/document do modalgtm, audience_cookie
abandoned_registermodal fecha sem auth completaaudience_cookie
viewed_app_install_banner 🆕banner PWA/APK aparece (surface: footer hoje)gtm, audience_cookie
scrolled_to_payment_seals 🆕viewport hit nos seals do footer (após scroll)gtm, audience_cookie

B. Pós-cadastro / pré-FTD

EventoTrigger no frontDestinos ativos
sign_upBFF /auth/register retornou OKgtm, meta_pixel (Lead), kwai (completeRegistration), taboola, bff_capi, floodlight (signUp), pendo (sign_up)
loginBFF /auth/login retornou OKgtm
registeredmodal de register fechou + auth bem-sucedidaaudience_cookie
viewed_deposit_modalabertura do modal de depositaudience_cookie
abandoned_depositmodal de deposit fecha (sempre — set-once)audience_cookie
home_deposit_loginclique no CTA de deposit (logado)gtm
home_deposit_registerauto-open de deposit pós-registergtm
deposit_initiatedsubmit do form de depositgtm (pix_generated/astropayDeposit/etc), meta_pixel (PixGenerated custom), floodlight (addToCart)

C. Pós-FTD / engajado

EventoTrigger no frontDestinos ativos
deposit_confirmedpolling do BFF retorna approvedgtm (pix_confirmado/pix_confirmado_ftd), pendo (deposit_confirmed)
first_depositquando deposit_confirmed.isFirstDeposit === truegtm, meta_pixel (Purchase), kwai (purchase), taboola, bff_capi, floodlight (ftd)
second_depositquando deposit_confirmed.isFirstDeposit === falsegtm
purchaseversão legacy do FTD (compat)gtm, meta_pixel (Purchase), kwai (purchase), taboola, floodlight (purchase)
ftd_completedderivado de first_depositaudience_cookie
multi_depositderivado de second_depositaudience_cookie
withdrawsubmit do form de saquegtm (saque), pendo (withdraw)

Behavior

EventoTrigger no frontDestinos ativos
searchsubmit de busca (jogo ou esporte)gtm
view_game_pageabrir página de detalhe do jogogtm
game_page_depositclique em deposit dentro do iframe do jogogtm

Ecommerce (GA4 + interno)

EventoTrigger no frontDestinos ativos
view_item_listseção entra no viewport (interno)gtm
view_item_list_ga4seção entra no viewport (GA4 ecommerce padrão)gtm
select_itemclique num card de jogogtm
view_promotionbanner/promoção entra no viewportgtm
select_promotionclique no banner/promoçãogtm

🆕 = adicionado na refatoração do registry (2026-05).

Contrato do view_item_list_ga4

É o único evento que carrega um array de items no formato ecommerce padrão do GA4 — é contra esse shape que a tag de GA4 tem que ser construída (definido em app/analytics/events.ts):

{
type: "view_item_list_ga4",
itemListName: string,
currency?: string,
items: Array<{
index: number;
item_id: string | number;
item_name: string;
item_brand?: string; // opcional
item_list_name: string;
}>,
}

view_item_list (sem sufixo) é a versão interna, com payload plano — os dois convivem e ambos chegam no GTM com o nome de evento view_item_list.

Como adicionar evento novo

Caso 1 — evento novo que o front pode observar

  1. Adiciona a variant em app/analytics/events.ts (AnalyticsEvent).
  2. Adiciona o ID no array ANALYTICS_EVENT_IDS (TS força — vai erra se faltar).
  3. Adiciona a phase em EVENT_PHASE.
  4. Adiciona entrada em app/analytics/destinations.ts (DESTINATIONS) com todos os canais explícitos (null ou slot) — inclusive floodlight e pendo.
  5. No componente que observa o trigger, importa pushMetricEvent de ~/utils/metrics/index e chama pushMetricEvent({ type: "...", ...payload }).
  6. Atualiza esta tabela.

Caso 2 — destino novo num evento existente

Marketing pede "agora viewed_casino também vai pra Meta como ViewContent":

// destinations.ts
viewed_casino: {
// ...outros canais
meta_pixel: { name: "ViewContent" }, // ← era null
// ...
}

Adapter pushFacebookEvent precisa de uma case pra viewed_casino se o payload for não-trivial — quando vazio (fbq("track", "ViewContent") direto), basta destravar a matriz. Adiciona case em app/utils/metrics/facebook.ts.

Caso 3 — evento backend-only

Não vai pra cá. Vai pra Contract BFF. Front não vê.

User fields enriquecidos (em todos os eventos GTM)

Quando existe user logado, todo evento GTM é enriquecido via gtmUserParams(user) (app/utils/gtm-user-params.ts). São 7 chaves — e o dataLayer nunca recebe PII em texto:

{
// base do evento
event: "first_deposit",
// ...

// user fields injetados por gtmUserParams()
user_id: 12345, // cru (numérico, não é PII)
btag_id: "AFIL1", // cru — vem de user.recommended_by
email: "<sha256>", // HASH
phone: "<sha256>", // HASH
first_name: "<sha256>", // HASH
last_name: "<sha256>", // HASH
dob: "<sha256>", // HASH
}

:::warning Os nomes das chaves são legados, os valores são hashes email / phone / first_name / last_name / dob mantêm os nomes antigos de propósito (decisão de marketing — sem sufixo _hash), pra que as tags de GTM já configuradas continuem lendo as mesmas chaves. O valor, porém, é sempre SHA-256. Uma tag que hoje espera texto (ex: enviar email cru pra um endpoint que não aceita hash) vai receber hash e falhar em silêncio. Só user_id e btag_id seguem crus.

Não existem as chaves user_email / user_phone / user_first_name / user_last_name / user_dob / affiliate_btag — se sua tag referencia alguma delas, ela está lendo undefined. :::

Quando o hash aparece: os hashes são pré-computados no login (app/utils/gtm-identity-hash.ts, cache populado pelo useAnalyticsBootstrap). A leitura é síncrona no cache — enquanto o refresh assíncrono não resolveu, os campos vêm vazios e o primeiro push pode sair sem eles. Do push seguinte em diante estão preenchidos.

É esse formato que habilita Enhanced Conversions / Meta CAPI / TikTok Events API pelo lado do GTM, sem que PII em texto passe pelo dataLayer em nenhum momento.

Schema customizável por brand (renomear no GTM)

O analyticsSchemaConfig continua existindo e brands podem renomear nomes de evento GTM:

{
events: {
firstDeposit: "first_deposit", // ← pode ser "ftd_legacy" em brand específica
pixGenerated: "pix_generated", // ← pode ser "pix_brl_v2" em outra
}
}

Útil pra GTM containers legacy com triggers já configurados em event names específicos. Renomeação acontece no adapter GTM — outros canais (Meta, Kwai, Taboola) usam os nomes da matriz direto.

Whitelist de eventos por brand (enabledSpecificEvents)

analyticsSchemaConfig.enabledSpecificEvents é uma lista de permissão: quando presente, só as chaves listadas passam pelo dispatcher GTM — todo o resto vira no-op silencioso. Ausente (undefined) = todos os eventos passam.

// overrides/<brand>/app/config/analytics/schema.ts
enabledSpecificEvents: ["pageView", "signUp", "login", "pixGenerated", /* ... */]
  • As chaves são camelCase, vindas de AnalyticsEventNameMap (ex: pageView, firstDeposit) — não são os IDs snake_case da matriz.
  • Só afeta o GTM. Meta Pixel / Kwai / Taboola já se autofiltram no switch interno de cada adapter, então listar ou não listar ali não muda nada pra eles.
  • Duas razões pra existir: (1) o container da brand não tem trigger pros eventos mais novos; (2) evitar que um trigger catch-all encaminhe payloads enriquecidos com PII hasheada (email/phone/dob) pra destino não configurado.

Hoje em uso por betpontobet-bet-br e donald-bet-br (mesmas 11 chaves). É a primeira coisa a checar quando marketing pergunta "por que o evento X não dispara nessa brand?".

Como debuggar

dataLayer manual

window.dataLayer.filter(e => e.event === "first_deposit")
window.dataLayer.filter(e => e.event === "rmk_audience_tag")

:::caution dataLayer não é o único container Brands com anaLayerConfig.enabled rodam um segundo container GTM sobre um dataLayer próprio chamado anaLayer. Em 7k-bet-br o container do dataLayer primário está desligado em prod (disableGtm: true) e só o anaLayer recebe eventos — inspecionar window.dataLayer ali dá resultado vazio e leva a conclusão errada. Use window.anaLayer. Ver Plataformas → anaLayer. :::

DevTools → Application → Cookies → <rmkCookie>_aud (JSON com tags). O <rmkCookie> é o cookieName da brand (ex: rmk7krmk7k_aud) — ver Remarketing.

Console em dev

window.__rmk
// → { id: "...", ts: ..., tags: [...], ... }

Pixel helpers

Anti-patterns

  1. Adicionar evento custom só no GTM tag. Sem entry no registry, o front nunca dispara.
  2. Mudar nome de evento sem atualizar GTM. Quebra trigger no container — eventos viram silenciosos.
  3. Disparar Purchase em deposit_initiated. Infla conversão (depósito gerado mas não pago vira Purchase). A matriz já segrega — deposit_initiated só vai pra GTM + Meta (PixGenerated custom). Purchase real vem de first_deposit/purchase.
  4. Adicionar PII em texto no dataLayer. Isso já é resolvido pelo código: gtmUserParams() só emite SHA-256 nos campos de PII. O anti-pattern hoje é reintroduzir texto — ex: passar email cru num payloadFn custom, ou criar uma variável de GTM que leia PII de outra fonte. Se sua tag precisa de PII, ela precisa aceitar hash.
  5. Adicionar case num adapter sem atualizar a matriz. Adapter dispara mesmo se matriz disser null na ordem dos eventos. Usa o registry como SoT.
  6. Esquecer da matriz quando declarar evento novo. TS pega — Record<AnalyticsEventId, DestinationMap> força exhaustividade.

Histórico

  • 2026-05 — Migração pro registry unificado. MetricEvent virou alias de AnalyticsEvent. Audience tags do RemarketingCapture passam pelo registry. 4 flags MVP novas (played_demo, started_register, viewed_app_install_banner, scrolled_to_payment_seals). Doc reescrita pra refletir matriz multi-canal.
  • Anterior — Doc cobria apenas GTM (mapeamento legacy 7k Vue → React 19).