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— mapaEventId → 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
- 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.
- Adicionar um destino é uma linha na matriz. Se a Meta quer receber
viewed_casinocomoViewContent, marketing pede e dev trocameta_pixel: nullpormeta_pixel: { name: "ViewContent", payloadFn: ... }. - 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).
| Canal | Como é dispatched | Status |
|---|---|---|
gtm | dataLayer.push({ event, ... }) | ✅ Ativo (brands com GTM habilitado — ver disableGtm) |
meta_pixel | fbq("track", name, params) | ✅ Ativo (brands com pixelId) |
kwai | kwaiq.instance(id).track(name) | ✅ Ativo (brands com kwaiPixelId) |
taboola | _tfa.push({ notify: "action", name }) | ✅ Ativo (brands com taboolaId) |
floodlight | gtag('event','conversion',{ send_to }) — Google DV360 | ✅ Ativo (brands com analyticsConfig.floodlight) |
pendo | pendo.track(name, payload) | ✅ Ativo (brands com pendoConfig.enabled) |
audience_cookie | addAudienceTag(name) no <rmkCookie>_aud | ✅ Ativo (brands com remarketing habilitado) |
tiktok_pixel | ttq.track(name, params) direto | 🟡 Hoje só via GTM custom HTML — slot fica null na matriz pra evitar dupla contagem |
smartico | SDK próprio | 🟡 Fluxo separado (não consome a matriz) |
webtrends | A/B testing | 🟡 Fluxo separado (não consome eventos) |
bff_capi | external_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:
floodlightsó dispara quando a brand defineanalyticsConfig.floodlight. Hoje:cl-bet7k-comeng-7k-bet. Oslot.namena matriz é a chave canônica de atividade (allPages,signUp,addToCart,ftd,purchase) — a stringcatreal é resolvida por brand emanalyticsConfig.floodlight.activities. Detalhes em Plataformas → Floodlight.pendosó dispara quandopendoConfig.enabled === true. Hoje:cl-bet7k-comeng-7k-bet. Só eventos de conversão passam pela matriz;loginfica deliberadamentenullporque ousePendojá emiteloginreativamente — 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)
| Evento | Trigger no front | Destinos ativos |
|---|---|---|
page_view | toda navegação SPA | gtm, meta_pixel (PageView), kwai (contentView), floodlight (allPages) |
view_modal_login | abrir modal de login | gtm |
view_modal_cadastro | abrir modal de cadastro | gtm |
viewed_casino | rota sob casino | audience_cookie |
viewed_live_casino | rota casino.live | audience_cookie |
viewed_game_detail | rota casino.play | audience_cookie |
viewed_sports | rota sob sports | audience_cookie |
viewed_promotions | rota sob promotions | audience_cookie |
played_demo 🆕 | clique no botão "Demo" no GameIframe | gtm, audience_cookie |
clicked_register | abertura do modal de register | audience_cookie |
started_register 🆕 | user digita em email/document do modal | gtm, audience_cookie |
abandoned_register | modal fecha sem auth completa | audience_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
| Evento | Trigger no front | Destinos ativos |
|---|---|---|
sign_up | BFF /auth/register retornou OK | gtm, meta_pixel (Lead), kwai (completeRegistration), taboola, bff_capi, floodlight (signUp), pendo (sign_up) |
login | BFF /auth/login retornou OK | gtm |
registered | modal de register fechou + auth bem-sucedida | audience_cookie |
viewed_deposit_modal | abertura do modal de deposit | audience_cookie |
abandoned_deposit | modal de deposit fecha (sempre — set-once) | audience_cookie |
home_deposit_login | clique no CTA de deposit (logado) | gtm |
home_deposit_register | auto-open de deposit pós-register | gtm |
deposit_initiated | submit do form de deposit | gtm (pix_generated/astropayDeposit/etc), meta_pixel (PixGenerated custom), floodlight (addToCart) |
C. Pós-FTD / engajado
| Evento | Trigger no front | Destinos ativos |
|---|---|---|
deposit_confirmed | polling do BFF retorna approved | gtm (pix_confirmado/pix_confirmado_ftd), pendo (deposit_confirmed) |
first_deposit | quando deposit_confirmed.isFirstDeposit === true | gtm, meta_pixel (Purchase), kwai (purchase), taboola, bff_capi, floodlight (ftd) |
second_deposit | quando deposit_confirmed.isFirstDeposit === false | gtm |
purchase | versão legacy do FTD (compat) | gtm, meta_pixel (Purchase), kwai (purchase), taboola, floodlight (purchase) |
ftd_completed | derivado de first_deposit | audience_cookie |
multi_deposit | derivado de second_deposit | audience_cookie |
withdraw | submit do form de saque | gtm (saque), pendo (withdraw) |
Behavior
| Evento | Trigger no front | Destinos ativos |
|---|---|---|
search | submit de busca (jogo ou esporte) | gtm |
view_game_page | abrir página de detalhe do jogo | gtm |
game_page_deposit | clique em deposit dentro do iframe do jogo | gtm |
Ecommerce (GA4 + interno)
| Evento | Trigger no front | Destinos ativos |
|---|---|---|
view_item_list | seção entra no viewport (interno) | gtm |
view_item_list_ga4 | seção entra no viewport (GA4 ecommerce padrão) | gtm |
select_item | clique num card de jogo | gtm |
view_promotion | banner/promoção entra no viewport | gtm |
select_promotion | clique no banner/promoção | gtm |
🆕 = 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
- Adiciona a variant em
app/analytics/events.ts(AnalyticsEvent). - Adiciona o ID no array
ANALYTICS_EVENT_IDS(TS força — vai erra se faltar). - Adiciona a phase em
EVENT_PHASE. - Adiciona entrada em
app/analytics/destinations.ts(DESTINATIONS) com todos os canais explícitos (nullou slot) — inclusivefloodlightependo. - No componente que observa o trigger, importa
pushMetricEventde~/utils/metrics/indexe chamapushMetricEvent({ type: "...", ...payload }). - 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
switchinterno 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.
:::
Audience cookie
DevTools → Application → Cookies → <rmkCookie>_aud (JSON com tags). O <rmkCookie> é o
cookieName da brand (ex: rmk7k → rmk7k_aud) — ver Remarketing.
Console em dev
window.__rmk
// → { id: "...", ts: ..., tags: [...], ... }
Pixel helpers
- Meta Pixel Helper
- TikTok Pixel Helper
- GTM Preview Mode
Anti-patterns
- Adicionar evento custom só no GTM tag. Sem entry no registry, o front nunca dispara.
- Mudar nome de evento sem atualizar GTM. Quebra trigger no container — eventos viram silenciosos.
- Disparar
Purchaseemdeposit_initiated. Infla conversão (depósito gerado mas não pago vira Purchase). A matriz já segrega —deposit_initiatedsó vai pra GTM + Meta (PixGenerated custom).Purchasereal vem defirst_deposit/purchase. - 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: passaremailcru numpayloadFncustom, ou criar uma variável de GTM que leia PII de outra fonte. Se sua tag precisa de PII, ela precisa aceitar hash. - Adicionar
casenum adapter sem atualizar a matriz. Adapter dispara mesmo se matriz dissernullna ordem dos eventos. Usa o registry como SoT. - Esquecer da matriz quando declarar evento novo. TS pega —
Record<AnalyticsEventId, DestinationMap>força exhaustividade.
Histórico
- 2026-05 — Migração pro registry unificado.
MetricEventvirou alias deAnalyticsEvent. Audience tags doRemarketingCapturepassam 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).