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 interno | Aliases aceitos na URL | Vai pro BFF como |
|---|---|---|
utm_source | utm_source | utm_source |
utm_campaign | utm_campaign | utm_campaign |
utm_content | utm_content | utm_content |
utm_medium | utm_medium | utm_medium |
src | utm_src, src | src (apenas em signup, não em deposit) |
app_source | app_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 só 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:
| Brand | TTL configurado | Onde |
|---|---|---|
| Default base | 7 dias (168h) | app/config/features/features.ts |
| Vera | 7 dias (explícito) | overrides/vera-bet-br/.../features.ts (linha trackingCookieTtlHours: 168) |
| 7k | 7 dias (explícito) | overrides/7k-bet-br/.../features.ts |
| Cassino | 7 dias (explícito) | overrides/cassino-bet-br/.../features.ts |
| Betpontobet | 10 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.
| Valor | Comportamento | Impacto |
|---|---|---|
true (default em todas as brands hoje) | UTMs permanecem na URL | URL compartilhável carrega atribuição; SEO afetado por params variados; canonical mantém UTMs |
false | Server faz 302 pra mesmo path sem UTMs; cookie já foi escrito no redirect | URL "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):
| Key | Por quê |
|---|---|
user_phone | PII — TikTok ad macros vazam isso quando mal-configuradas (user_phone=__USER_PHONE__) |
user_email | Idem |
user_name | Idem |
pixel | Ruído — não é tracking, é ID de pixel da plataforma |
currency | Ruí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
- Limpa cookies do site
- Acesse
http://localhost:5173/?utm_source=teste&utm_campaign=abc&utm_oferta=novo&ref=AFIL1 - DevTools → Application → Cookies → confirme
cookie_trackingpopulado - Clica em outro link com
?utm_campaign=cyber-monday→ confirma queutm_source=testefoi preservado eutm_campaignfoi sobrescrito - Submete signup → Network →
POST /api/auth/register→ Form Data deve terutm_source,utm_campaign,utm_oferta,affiliation_code,app_source - Submete deposit → Network →
POST /api/payments/deposit→ body deve ter os UTMs semsrc/affiliation_code/subid/app_source
Detalhes em Operação → Testar Campanha.
Anti-patterns
- Setar UTM em link interno (ex: header →
/?utm_source=header_btn). Sobrescreve a atribuição real e infla "header_btn" como canal. Usedata-*ou eventos de UI, nunca UTM em link interno. - Esquecer set completo em link de campanha. Last-touch herda silenciosamente os ausentes.
- Pedir captura de campo novo só no front. Se o BFF não processar, é tracking morto. Sempre alinha back primeiro.
- Mudar política pra first-touch sem alinhar com BI. Quebra dashboards calibrados.