Anti-patterns
Lista consolidada do que NÃO fazer no setup de marketing. Cada item bate com erro real visto em produção da Cactus ou de plataformas similares.
Atribuição
1. UTM em link interno
❌ Header tem <a href="/?utm_source=header_btn">Início</a>.
Por quê é ruim: sobrescreve atribuição real do user. Canal "header_btn" infla até virar #1 em todos os relatórios; canais reais (Meta, Google) somem.
✅ Use data-* attributes pra rastrear UI ou eventos GTM custom (select_item).
2. Esquecer set completo de UTMs
❌ Link de campanha só com utm_campaign=cyber-monday.
Por quê é ruim: last-touch herda utm_source da campanha anterior. User que veio do Google primeiro e clicou em link de e-mail depois conta como conversão do Google (porque source não foi sobrescrito).
✅ Sempre passe os 4 canonical (utm_source, utm_campaign, utm_medium, utm_content) em todo link.
3. Mudar pra first-touch sem alinhar com BI
❌ Decidir unilateralmente "vamos preservar primeiro touch".
Por quê é ruim: todos os dashboards estão calibrados em last-touch. Mudança silenciosa quebra histórico de comparação.
✅ Discussão coordenada com BI antes. Se mudar, é deliberado e documentado.
Pixels e plataformas
4. Pixel duplicado (GTM tag + SDK próprio)
❌ Adicionar Meta Pixel via tag GTM custom e o front já carrega via useAnalytics.
Por quê é ruim: cada evento dispara 2x. Audiences inflam, conversion duplica, ROAS quebra.
✅ Olhe app/utils/metrics/* antes de adicionar pixel via GTM.
5. Tag GTM disparando Purchase em deposit_initiated
❌ Custom Event pix_generated → tag Purchase Meta.
Por quê é ruim: depósito gerado mas não confirmado dispara Purchase. Inflate brutal.
✅ Tag Purchase só no pix_confirmado_ftd (BFF confirmou).
6. Não passar transactionId na tag de conversion
❌ Conversion tag Google Ads sem transactionId.
Por quê é ruim: refresh da página dispara nova conversão. Mesma compra conta 2-3x.
✅ Sempre passa transactionId: {{DLV - transactionId}} — Google Ads deduplica por isso.
7. Mudar gtmId/pixelId em prod sem testar
❌ Update direto no backoffice → "deploy" instant.
Por quê é ruim: tag mal configurada dispara evento errado. Polui dashboards. Difícil de reverter.
✅ Testa no GTM Preview Mode primeiro. Validar com Pixel Helper/Tag Assistant.
Privacy / Consent
8. Mudar cookieConsentCosmeticOnly sem aprovação registrada
❌ Alterar o flag numa brand por conta própria, em qualquer direção.
Por quê é ruim: o flag decide se a recusa do usuário tem efeito. Mudança sem aprovação registrada dos donos de marketing e de LGPD gera divergência entre o que a brand faz e o que foi acordado — e o efeito é imediato no deploy seguinte.
✅ Aprovação conjunta registrada antes. Contexto do comportamento atual em Modo Cosmético — página que, ela mesma, aguarda validação do responsável por LGPD.
9. Assumir que o boot do GCM v2 é denied
❌ Construir tag ou raciocínio de atribuição partindo de "os sinais sobem denied e só liberam
depois do consent".
Por quê é ruim: não é o que o código faz. injectConsentDefaults() injeta os 7 sinais como
granted, sem wait_for_update, para toda brand. O único ramo condicional é o inverso: um
update para denied quando a brand não é cosmética e o usuário recusou em visita
anterior.
✅ Leia a política real em Consent LGPD antes de assumir estado de consent. E note que essa página aguarda validação do responsável por LGPD.
10. Não testar o estado denied
❌ Lançar tag GTM nova testando só o caminho "aceitou tudo".
Por quê é ruim: o estado denied é alcançável — brand não-cosmética + recusa registrada em
visita anterior dispara update all-denied no boot. Tag que dependa de cookie bloqueado falha em
silêncio, e você não vê isso testando só o caminho felizes.
✅ Teste com cookies_consent gravado com performance/functionality/others todos false,
numa brand com cookieConsentCosmeticOnly: false. Note que não há botão "Recusar" no banner —
o caminho é Personalizar → desligar tudo → salvar, ou gravar o cookie na mão.
Cookies
11. Cookie name com cactus ou bluetec
❌ cookieName: "rmkcactus" no remarketing.
Por quê é ruim: vaza plataforma white-label pro cliente final. Front rejeita em runtime e desativa o flag.
✅ rmk<abreviação-da-brand> (rmk7k, rmkbpb, rmkst77).
12. Tornar cookie_tracking HttpOnly
❌ Setar HttpOnly: true no cookie de tracking.
Por quê é ruim: client não consegue ler. Submit de signup/deposit vai sem UTMs.
✅ Mantém HttpOnly: false. Atribuição precisa client + server consumir.
13. Sanitizar lastclick (Clever)
❌ Aplicar mesmo blocklist do cookie_tracking no lastclick.
Por quê é ruim: Clever pode usar formatos com strings que o blocklist mangia (document, appendChild, {macro}). Sanitização mutila IDs e quebra atribuição.
✅ lastclick é opaco — sem sanitização. Mesmo padrão do legado Vue.
14. Mudar SameSite do lastclick
❌ SameSite: "Lax" no lastclick.
Por quê é ruim: Clever entrega tráfego cross-site (iframe/popup/pixel chain). Browsers rejeitam Set-Cookie cross-site sem SameSite=None; Secure. Quebra ~99% do tráfego Clever.
✅ SameSite: "None"; Secure: true — confirmado com time Clever em maio/2026.
15. Renomear cookie em produção sem migração
❌ Mudar cookie_tracking pra tracking_cookie sem migração one-shot.
Por quê é ruim: usuários existentes perdem atribuição e o cookie velho fica órfão no browser.
✅ Migração one-shot na leitura: lê o nome legado, adota o valor, deleta o legado, nunca escreve
nele. O exemplo implementado é LEGACY_CONSENT_COOKIE_NAMES = ["cactusCookiesConsent"] em
app/utils/consent.ts — forma completa em
Cookies → renomear cookie em produção.
16. TTL absurdo
❌ trackingCookieTtlHours: 99999 (~11 anos).
Por quê é ruim: atribuição vaza entre campanhas. User que clicou em ad de 2 anos atrás conta como conversão do canal de 2 anos atrás.
✅ Janelas razoáveis: 24h–90 dias. O default é 720h (30 dias) — e é ele que vale em 10 das 13 brands, porque não declaram a chave.
Headers / Payload
17. Adicionar UTM em header HTTP
❌ Custom header X-UTM-Source em toda request.
Por quê é ruim: headers têm limite de tamanho (~8KB). UTMs custom (utm_oferta, utm_term, etc) estouram. Body suporta.
✅ UTMs viajam só no body de signup/deposit.
18. Adicionar src ao deposit
❌ Deposit payload com src: "fb_ads".
Por quê é ruim: quebra paridade com legado Vue. BFF valida payload e pode rejeitar.
✅ src só em signup. Deposit é subset. (Lista exata em UTMs)
19. Adicionar affiliation_code/subid/app_source ao deposit
❌ Deposit payload com affiliation_code.
Por quê é ruim: mesma razão — paridade legado. Atribuição de depósito é resolvida pelo BFF via user.recommended_by, não pelo cookie.
✅ Deposit subset. Signup tem todos esses campos.
Modo App / TWA
20. Disparar pixel client em modo app
❌ Não desativar Meta Pixel quando ?app=true / ?is_twa=true.
Por quê é ruim: apps inflam métricas web. A atribuição mobile é AppsFlyer.
✅ shouldLoadTrackers = !isAppMode — a cláusula !isBot não existe mais (removida em
2026-06). Honra esse gate, e não presuma comportamento diferente pra bot.
21. Mover AppsFlyer pra dentro do gate de trackers
❌ if (!shouldLoadTrackers) return; appsFlyer.sendFTD(...).
Por quê é ruim: scopes inversos. App: gate=false (early return) → nunca roda. Web: appsFlyer.ready=false → nem chama. Resultado: nunca dispara. Bug histórico (maio/2026).
✅ AppsFlyer antes do gate. O hook decide internamente se está em app.
22. Modificar assetlinks.json
❌ Update direto em overrides/<brand>/public/.well-known/assetlinks.json.
Por quê é ruim: chaves criptográficas atreladas ao APK no Play Store. Typo quebra TWA em prod.
✅ Coordena com time mobile. Atualiza só quando há fingerprint novo do APK.
Debug / Prod
23. Tentar ?EnableDebug=1 em prod
❌ Esperar que toggle visual de debug funcione em prod.
Por quê é ruim: foi removido em maio/2026 por vazamento de cookies sensíveis (especificamente cf-worker-key em response).
✅ Use o Cloudflare Dashboard ou wrangler tail <worker-name> (sem --env — não existe environment
no wrangler.toml). Ver Debug.
24. Reportar bug sem X-LOG-INFO
❌ "Tá quebrado, dá uma olhada".
Por quê é ruim: dev sem X-LOG-INFO não consegue correlacionar request específica do user a logs server-side.
✅ Sempre incluir X-LOG-INFO da request afetada (Network tab) ao reportar.
Audiência / Remarketing
25. Persistir viewed_game_<slug> granular sem cap-prioritário
❌ Marcar tag por jogo individual no <rmkCookie>_aud esperando que as funnel tags sejam poupadas.
Por quê é ruim: cassino tem 2k-5k games e o cap é de 40 tags. A eviction é puramente por
idade — não existe prioridade canônica protegendo funnel tags, então ftd_completed (o sinal mais
valioso) sai pra dar lugar a viewed_game_random.
✅ Granularidade provider-level (viewed_provider_pgsoft) ou dataLayer-only (sem persistir cookie). Detalhes em Remarketing.
26. Confiar no external_id antes de BFF aceitar
❌ Setar sendToBff: true no remarketing config sem coordenar com back.
Por quê é ruim: front envia external_id no payload, BFF ignora (não mapeou o campo). Tracking morto.
✅ Sempre alinha com BFF antes. Liga sendToBff: true só após backend support confirmado.
Geral
27. Pedir captura de campo novo só no front
❌ "Adiciona um tcclid no cookie, marketing pediu."
Por quê é ruim: front captura, mas BFF não processa. Dashboard de BI nunca vê o campo. Tracking morto.
✅ Sempre alinha com BFF primeiro. Front + back juntos.
28. Configurar feature flag em app/config/features/features.ts (default base) sem propagar pros overrides
❌ Adicionar remarketingId: { enabled: true } no default base e esquecer das brands com override.
Por quê é ruim: brand override é file-replacement (não deep-merge). Brands com override ficam com undefined, não herdam o default.
✅ Adicionar campo no tipo + default + propagar pra todos overrides existentes (ou documentar explicitamente que override é opt-in).
29. Esquecer que cada brand tem analytics config diferente
❌ Testar campanha em 7k-bet-br e deduzir que vai funcionar em betpontobet-bet-br.
Por quê é ruim: o override muda pixel ID, GTM ID, container (anaLayer vs dataLayer), whitelist
de eventos, Floodlight, Pendo, TTL de atribuição. Funcionar em uma ≠ funcionar em outra — e
7k-bet-br é justamente a menos representativa (GTM primário desligado, anaLayer, estratégia
engagement).
✅ Testa explicitamente em cada brand alvo. Diferenças no config map.
30. Assumir que os pixels de conversão são carregados com atraso
❌ "O pixel não disparou porque o scheduler ainda não liberou — ele espera LCP, primeira interação, ou 25s em auditoria."
Por quê é ruim: esse sistema de gating não existe mais. Foi removido em 2026-06 justamente
porque quebrava marketing: eventos disparados antes dos gates eram descartados (não havia fila de
replay), usuário que convertia sem interagir nunca era rastreado, e a auditoria media uma página
que nenhum usuário real via. Hoje scheduleThirdParty(fn) executa fn() imediatamente
(modulo o guard de prerender e o kill switch da brand). GTM, Meta Pixel, Taboola e Kwai são
eager por default.
✅ Modelo atual, três faixas:
| Faixa | Quem | Quando carrega |
|---|---|---|
| eager (default) | GTM, Meta Pixel, Taboola, Kwai, Webtrends, Floodlight | no mount |
engagement (opt-in por brand — hoje só 7k-bet-br) | os mesmos | filas primadas no mount (nenhum evento é perdido), download na 1ª interação real ou no marketingScriptsMaxDelayMs (5000) |
| deferred | Clarity, Hotjar, selo RA | idle, ou 1ª interação, ou timeout de segurança |
| interaction-only | Pendo | só após a 1ª interação real |
Kill switch global das duas faixas: featuresConfig.thirdPartyScriptsDisabled.
31. Presumir que bots e auditorias sintéticas carregam menos script
❌ "O Lighthouse não conta porque a gente não carrega tracker pra ele."
Por quê é ruim: carrega. O branch de auditoria sintética foi removido em 2026-06 — decisão deliberada de não inflar score. Um número de Lighthouse ruim é um número real.
✅ Trate a métrica de laboratório como representativa. Se precisa melhorar, mude o carregamento
(faixa engagement), não o gate.