Pular para o conteúdo principal

UTMs

Esta página documenta todos os parâmetros UTM que o front captura, persiste e envia pro BFF — incluindo o wildcard utm_* que permite criar tags custom sem precisar deploy.

Parâmetros canonical

Os 4 UTMs padrão Google + dois aliases de uso comum:

Campo internoAliases aceitos na URLVai pro BFF como
utm_sourceutm_sourceutm_source
utm_campaignutm_campaignutm_campaign
utm_contentutm_contentutm_content
utm_mediumutm_mediumutm_medium
srcutm_src, srcsrc (apenas em signup, não em deposit)
app_sourceapp_source (+ detecção PWA via matchMedia)app_source

Por que src é separado de utm_source?

Paridade legado. O Vue antigo usava src para rotular a "fonte composta" (ex: fb_ads, gads_search) enquanto utm_source ficava com o nome canônico do canal (facebook, google). Mantemos os dois pra não quebrar dashboards de BI.

Wildcard utm_*

QUALQUER query param que comece com utm_ é capturado dinamicamente. Permite marketing adicionar UTMs custom sem deploy:

?utm_oferta=black-friday-50
?utm_term=cassino-online
?utm_creative=banner-vermelho-v3
?utm_placement=feed-mobile
?utm_position=top
?utm_network=display
?utm_target=lookalike-1pct
?utm_match=exact

Todos esses são persistidos no cookie_tracking e enviados flat no payload de signup/deposit (cada um vira top-level key).

Limites do wildcard

  • Máximo 20 chaves totais no cookie (proteção contra URL stuffing)
  • Canonicals + clicked-IDs têm prioridade — quando cheio, utm_* custom são descartados primeiro
  • Cada valor: max 500 chars, sanitizado (HTML entities, scripts, etc strippados)

Política last-touch (sobrescrita)

Já detalhado em Atribuição → Overview, em resumo:

  • Cada UTM nova sobrescreve o valor antigo
  • UTMs ausentes na URL nova mantêm o valor antigo (não são apagadas)
  • Vale tanto pra canonicals quanto pra wildcard

:::warning Implicação operacional importante Se a campanha A tem utm_source=facebook&utm_campaign=feed, e o usuário clica depois numa URL com utm_campaign=cyber-monday, ele fica com utm_source=facebook herdado. Isso pode inflar atribuição do canal anterior.

Solução: sempre passe o set completo de UTMs em cada link de campanha. Use uma ferramenta de UTM builder (Google Analytics URL Builder, Bitly campaigns) que enforce o set completo. :::

TTL configurável por brand

cookie_tracking tem TTL de 7 dias por default. Brands podem override via featuresConfig.trackingCookieTtlHours:

BrandTTL configuradoOnde
Default base7 dias (168h)app/config/features/features.ts
Vera7 dias (explícito)overrides/vera-bet-br/.../features.ts (linha trackingCookieTtlHours: 168)
7k7 dias (explícito)overrides/7k-bet-br/.../features.ts
Cassino7 dias (explícito)overrides/cassino-bet-br/.../features.ts
Betpontobet10 dias (240h)overrides/betpontobet-bet-br/.../features.ts

Valores possíveis:

  • 24 → 1 dia (atribuição curta, conversão rápida)
  • 168 → 7 dias (default)
  • 720 → 30 dias (atribuição longa, comportamento legado pré-2026)
  • 2160 → 90 dias (janela do cookie do Google Ads)
  • 0.5 → 30 minutos (testes de atribuição extra-curta — pouco usado)

Trocar o TTL encurta ou alonga a janela de atribuição. Em campanhas de venda rápida (BlackFriday flash), TTL curto faz sentido pra evitar atribuir conversão a clique antigo. Em campanhas de awareness (brand campaign), TTL longo faz mais sentido.

Feature flag keepTrackingParamsInUrl

Controla se os UTMs ficam na URL após captura ou são removidos via redirect 302.

ValorComportamentoImpacto
true (default em todas as brands hoje)UTMs permanecem na URLURL compartilhável carrega atribuição; SEO afetado por params variados; canonical mantém UTMs
falseServer faz 302 pra mesmo path sem UTMs; cookie já foi escrito no redirectURL "limpa"; melhor pra SEO; mas UTMs viram não-compartilháveis

Mudar pra false é uma decisão coordenada — nunca seta no features.ts do base, só em override de brand específica.

Exceção: rota /clever (Clever Advertising affiliate) sempre preserva UTMs na URL, independente da flag, pq o próprio loader da rota lê os params.

Sanitização

Cada valor passa por sanitizeTrackingValue() (porta direta de getSecureQueryParam do legado Vue):

removidos: < > " ' `
"document" "createElement" "appendChild"
<script>...</script>
<img ... src=...>
descartado: {fbclid} {{campaign.id}} __CLICKID__ __CAMPAIGN_ID__
truncado: valores > 500 chars

Bloqueio absoluto (nunca persistido, mesmo em URLs maliciosas):

KeyPor quê
user_phonePII — TikTok ad macros vazam isso quando mal-configuradas (user_phone=__USER_PHONE__)
user_emailIdem
user_nameIdem
pixelRuído — não é tracking, é ID de pixel da plataforma
currencyRuído — moeda é resolvida pelo BFF, não pela URL

Payload final pro BFF

Signup (POST /bff/register-simplified ou POST /api/auth/register)

Body em snake_case com todos os campos flat:

{
"email": "user@example.com",
"password": "...",
"country": "BRA",

"utm_source": "facebook",
"utm_campaign": "black-friday",
"utm_content": "banner-v3",
"utm_medium": "cpc",
"utm_oferta": "first-deposit-100pct",
"utm_term": "cassino",

"src": "fb_ads",
"app_source": "web",
"ga_client_id": "GA1.1.123456789.987654321",
"affiliation_code": "AFIL1",
"subid": "rt_subid_42",

"gclid": "Cj0KCQjw...",
"fbclid": "IwAR3...",
"media_source": "google",
"media_clid": "Cj0KCQjw..."
}

Deposit (POST /wallet/add-credit)

Body em snake_case, subset diferente do signup:

{
"user_id": 42,
"credit_amount": 5000,
"payment_method": "pix",
"currency": "BRL",

"utm_source": "facebook",
"utm_campaign": "black-friday",
"utm_content": "banner-v3",
"utm_medium": "cpc",
"utm_oferta": "first-deposit-100pct",

"ga_client_id": "GA1.1.123456789.987654321",

"gclid": "Cj0KCQjw...",
"media_source": "google",
"media_clid": "Cj0KCQjw..."
}

:::caution Paridade estrita: src NÃO vai no deposit Nenhum dos 4 projetos legado (base, 7k, cassino, vera) jamais enviou src em /wallet/add-credit — só em /auth/register. Manter essa distinção evita surpresas em dashboards de BI.

affiliation_code, subid, app_source também não vão no deposit. :::

Como testar manualmente

  1. Limpa cookies do site
  2. Acesse http://localhost:5173/?utm_source=teste&utm_campaign=abc&utm_oferta=novo&ref=AFIL1
  3. DevTools → Application → Cookies → confirme cookie_tracking populado
  4. Clica em outro link com ?utm_campaign=cyber-monday → confirma que utm_source=teste foi preservado e utm_campaign foi sobrescrito
  5. Submete signup → Network → POST /api/auth/register → Form Data deve ter utm_source, utm_campaign, utm_oferta, affiliation_code, app_source
  6. Submete deposit → Network → POST /api/payments/deposit → body deve ter os UTMs sem src/affiliation_code/subid/app_source

Detalhes em Operação → Testar Campanha.

Anti-patterns

  1. Setar UTM em link interno (ex: header → /?utm_source=header_btn). Sobrescreve a atribuição real e infla "header_btn" como canal. Use data-* ou eventos de UI, nunca UTM em link interno.
  2. Esquecer set completo em link de campanha. Last-touch herda silenciosamente os ausentes.
  3. Pedir captura de campo novo só no front. Se o BFF não processar, é tracking morto. Sempre alinha back primeiro.
  4. Mudar política pra first-touch sem alinhar com BI. Quebra dashboards calibrados.