Pular para o conteúdo principal

App Mode (TWA + ?app=true)

A plataforma roda em 3 contextos distintos: web, PWA e App Android (TWA). O modo app tem comportamento de tracking radicalmente diferente do web — esta página explica por quê.

Os 3 contextos

ContextoComo detectarapp_sourceX-ORIGIN-ACCESS
Web DesktopUser-Agent não-mobile, sem param de app mode"web"2
Web MobileUser-Agent mobile, sem param de app mode"web"4
PWA StandalonematchMedia('(display-mode: standalone)')"pwa"2 ou 4
TWA App Android?app=true ou ?is_twa=true na URL inicial + bridge window.twa"app"1 ou 5

Os dois params que ligam app mode

APP_MODE_QUERY_PARAMS = ["app", "is_twa"] (app/hooks/useAppMode.ts) — qualquer um dos dois com valor "true" liga o app mode:

ParamPor que existe
?app=trueConvenção atual do projeto.
?is_twa=trueLegado dos APKs 7k/cassino/vera publicados antes do rename. Esses APKs continuam em produção injetando is_twa em cada landing. Enquanto não reconhecíamos o param, isAppMode ficava false pra esses usuários — e como o useAppsFlyer se auto-gateia em isAppMode, os eventos AppsFlyer S2S simplesmente não disparavam pra eles.

O flag é persistido em sessionStorage["cookie_app_mode"], então sobrevive às navegações SPA depois da landing inicial.

TWA — Trusted Web Activity

TWA é um APK Android que renderiza o site num WebView Chrome controlado. Comporta-se como app nativo (push, install, full screen, ícone na home), mas o conteúdo é o mesmo HTML/JS/CSS do site.

Identificação no front

URL inicial do TWA carrega ?app=true:

https://7k.bet.br/?app=true

Front lê esse param uma vez no boot e persiste. Subsequentes navegações herdam.

Bridge window.twa

APK injeta uma bridge JS:

window.twa = {
getPostMessageService: () => Promise<{
postMessage: (message: string) => void
}>
}

Front usa essa bridge pra:

  • Enviar eventos S2S pro AppsFlyer (via post message → APK encaminha)
  • Buscar device IDs (appsflyer_id, advertising_id, app_version)

Comportamento de tracking em modo app

Pixels de marketing DESATIVADOS

// app/hooks/useAppMode.ts
shouldLoadTrackers: !isAppMode

:::info A cláusula !isBot não existe mais A fórmula era !isAppMode && !isBot. O ramo de bots saiu junto com o branch de auditoria sintética na revisão do scheduler (2026-06): bots e Lighthouse hoje carregam os mesmos scripts que um usuário real — decisão deliberada, pra não inflar score. App mode é o único gate. :::

Quando isAppMode === true, o useAnalytics não inicializa:

  • GTM
  • Meta Pixel
  • Kwai Pixel
  • Taboola
  • Microsoft Clarity
  • Webtrends Optimize
  • Google DV360 Floodlight

Atenção — o gate não cobre tudo. Hotjar, Pendo e o selo RA sobem por outro componente (AnalyticsLoaders), que não consulta app mode. Eles seguem inertes por config (enabled: false na maioria das brands), não por causa do modo app.

Razão

App tem requisitos diferentes:

  • Atribuição de install é via AppsFlyer (bridge nativa / S2S), não via pixel client
  • WebView no APK tem comportamento sutilmente diferente de browser
  • Pixels client-side em app inflariam métricas de "web users" com tráfego mobile

O que continua funcionando em app

AppsFlyer — o tracker de marketing ativo em app mode. Duas rotas: bridge nativa window.twa (dominante em produção) e fallback HTTP. Ver Plataformas → AppsFlyer.

cookie_tracking — UTMs ainda capturados (deeplink do APK pode trazer UTMs).

cookie_referrer — referrer ainda capturado (do APK).

Cookie de remarketing (<rmkCookie>, ex: rmk7k) — UUID first-party gerado igual web.

Headers X-ORIGIN-* — todos viajam normalmente.

Smartico — gamification continua via SDK (não passa pelo gate de app mode).

Detecção PWA

PWA é diferente de TWA:

  • PWA: site instalado via "Add to Home Screen" do browser (Chrome, Safari). Continua sendo browser, com permissões mais robustas.
  • TWA: APK próprio na Play Store. Browser é só engine de renderização.

Detection PWA via:

window.matchMedia('(display-mode: standalone)').matches

PWA não desativa pixels client-side (permanece em modo web normal). Apenas marca app_source: "pwa" no cookie_tracking.

Eventos especiais em modo app

APK pode lançar deeplink com UTMs:

https://7k.bet.br/?app=true&utm_source=appsflyer&utm_campaign=push_winback

Front captura UTMs normalmente + identifica como app + roteia AppsFlyer S2S em vez de pixel.

App version gating

O limite não é hardcoded — vem da config de AppsFlyer da brand:

// app/utils/metrics/appsflyer-bridge.ts (e useAppsFlyer.ts)
const minVersion = appsFlyerConfig.postMessageMinVersion;
if (!minVersion || !versionGte(appVersion, minVersion)) return;
  • appVersion vem de sessionStorage["cookie_app_version"] (populado pelo param app_version da URL que o APK injeta).
  • postMessageMinVersion é per-brand. Hoje só 7k-bet-br define: "3.0.0".
  • Sem postMessageMinVersion, ou com APK abaixo do mínimo, a bridge nativa não é usada — o envio cai no caminho HTTP.

Configuração TWA por brand

Um único brand ainda versiona assetlinks.json neste repo:

Brandpackage_name no assetlinks.jsonPath
7k-bet-brbr.bet.k.twaoverrides/7k-bet-br/public/.well-known/assetlinks.json
Demais brands do base(não versionam assetlinks aqui)

:::note Cassino e Vera saíram do base Os APKs de cassino.bet.br e vera.bet.br continuam existindo, mas as brands foram removidas do front-web-base em 2026-07 — os assetlinks.json delas não são mais servidos por este repo, fora do escopo desta doc. (São justamente esses APKs legados que injetam ?is_twa=true.) :::

Lista viva: ls overrides/*/public/.well-known/assetlinks.json no front-web-base.

assetlinks.json é o Digital Asset Links do Google — prova que o domínio autoriza o APK a operar em modo TWA. Sem ele:

  • TWA perde modo url-bar-hidden
  • Smart Lock para na 1ª autenticação
  • Smartico push subscription quebra

Nunca alterar um caractere desse JSON. package_name + sha256_cert_fingerprints são chaves criptográficas atreladas ao APK no Play Store. Typo quebra TWA em prod até próximo deploy.

Como testar app mode

No browser (sem APK)

  1. Acesse http://localhost:<DEV_PORT>/?app=true (ou ?is_twa=true) — a porta default é 5173, mas pode estar sobrescrita por DEV_PORT no .env.local do workspace
  2. Console:
    // Simula bridge TWA
    window.twa = {
    getPostMessageService: async () => ({
    postMessage: (msg) => console.log("TWA bridge msg:", msg)
    })
    }
  3. Submeta deposit → Console deve logar mensagem TWA
  4. Network → Pixels (Meta, Kwai, etc) NÃO devem disparar

No APK real

Conecta dispositivo Android via USB → Chrome DevTools → chrome://inspect → seleciona WebView do APK.

Anti-patterns

  1. Disparar pixel client em modo app. Quebra atribuição mobile (apps inflate web metrics).
  2. Confiar em _fbp cookie em modo app. Não existe (Meta Pixel não carrega).
  3. Esquecer ?app=true/?is_twa=true no deeplink que o APK manda. Front roda como web, dispara pixels e o AppsFlyer não dispara.
  4. Modificar assetlinks.json sem coordenar com time mobile. Quebra TWA em prod.
  5. Confundir PWA com TWA. PWA = browser instalado, pixels normais. TWA = APK próprio, pixels de marketing desativados.
  6. Assumir que bot/Lighthouse não carrega tracker. Carrega — a cláusula !isBot foi removida em 2026-06. O único gate é app mode.