Deep Links e Deferred Deep Links: Como Taguear a Jornada do Usuário do Web para o App

Do clique na web móvel à primeira abertura no app: Universal Links, Play Install Referrer, UIPasteControl no iOS, Smart Scripts com localStorage e unificação no BigQuery.

Contexto

O usuário clica em um anúncio pago no Instagram ou no Google, navega pela versão mobile do seu e-commerce, encontra uma oferta exclusiva e clica no banner "Baixe o app e ganhe 15% de desconto". Ele é levado à App Store ou Google Play, instala o aplicativo e abre.

O que ele encontra? A tela inicial genérica de boas-vindas, pedindo cadastro ou exibindo banners institucionais. O produto que ele estava olhando sumiu. O cupom evaporou.

Para piorar: no seu painel de atribuição e no GA4, a compra subsequente é classificada como tráfego direto ou orgânico. O time de mídia jura que a campanha de R$ 50 mil converteu; engenharia diz que o SDK está inicializando normalmente; produto reclama que a retenção D1 despencou.

Não se trata de bug no aplicativo. É uma ruptura estrutural entre o protocolo HTTP do navegador e as sandboxes dos sistemas operacionais móveis. Quando o usuário não possui o app instalado e passa pela loja, a sessão web é destruída.

Recuperar esse contexto no primeiro arranque — entregando a tela certa para o usuário e o crédito de conversão para a campanha — é o trabalho do deferred deep linking. Com o fim definitivo dos Firebase Dynamic Links em agosto de 2025, o bloqueio explícito de fingerprinting pela Apple e a blindagem de IP pelo iCloud Private Relay, truques antigos deixaram de funcionar. Este guia detalha a engenharia necessária para implementar essa jornada de ponta a ponta sem perda de dados.

Anatomia do Deep Link: Direto vs. Diferido (Deferred)

Antes de tratar a perda de dados, é preciso separar os três protocolos de abertura e a mecânica do deferimento:

Mecanismo Formato Validação de Domínio Se o app não estiver instalado Preserva contexto pós-instalação?
Custom URI Scheme myapp://produto/123 Nenhuma (sujeito a colisões e sequestro) Falha com erro silencioso de URL inválida Não
Universal Links (iOS) https://empresa.com/... Criptográfica via apple-app-site-association Fallback para URL web no Safari Não
App Links (Android) https://empresa.com/... Criptográfica via assetlinks.json Fallback para URL web no Chrome Não
Deferred Deep Link Rota HTTPS orquestrada por MMP/serviço Criptográfica + roteamento em borda Redirecionamento à loja com retenção de payload Sim (restaura no 1º arranque)

Universal Links e App Links funcionam exclusivamente quando o binário nativo já está compilado e presente no dispositivo do usuário. Quando o app não existe, o sistema operacional abre a rota web de contingência.

Se o objetivo de marketing for conduzir o usuário à loja para baixar o app, o redirecionamento tradicional quebra o pipeline. O deferred deep linking adia (defer) a entrega dos parâmetros analíticos e da intenção de navegação até que a instalação termine e a aplicação execute pela primeira vez.

Taxonomia e Preservação de UTMs e Parâmetros

Um erro frequente de tagueamento é esperar que o SDK de atribuição "adivinhe" as origens sem um contrato formal entre a URL da landing page e a chamada da loja.

Em uma jornada multicanal, três classes de parâmetros devem atravessar a ponte:

  1. Atribuição de Mídia: utm_source (ou pid no MMP), utm_campaign, utm_medium, utm_content e utm_term.
  2. Identificadores de Clique: gclid (Google Ads), fbclid (Meta) e ttclid (TikTok).
  3. Roteamento Interno e Contexto: deep_link_value (o identificador de rota ou SKU, ex: produto_4489) e metadados adicionais (ex: cupom=PROMO15).

O gerador de links deve aplicar regras de precedência rígidas (waterfall). Se a URL web contiver parâmetros nativos do MMP e parâmetros padrão da UTM, a biblioteca precisa priorizar a taxonomia da plataforma sem descartar as variáveis universais legíveis para o Data Warehouse:

https://empresa.onelink.me/AbCd?pid=google_ads&c=blackfriday_app&af_sub1=gclid_valor&deep_link_value=sku_4489&deep_link_sub1=PROMO15

Atenção: Parâmetros determinísticos como gclid e fbclid nunca devem ser jogados soltos na raiz se você usa um MMP; mapeie-os explicitamente em campos customizados (como af_sub1 a af_sub5 no AppsFlyer ou sub-parâmetros no Branch) para garantir que eles apareçam nas tabelas brutas (raw data) e viabilizem retroalimentação via API de conversões.

Android: Pipeline Determinístico via Play Install Referrer API

No ecossistema Android, o deferred deep linking possui suporte nativo do sistema operacional e é 100% determinístico.

Quando o link dinâmico detecta que o dispositivo é Android e não possui o app, ele redireciona para a Google Play Store anexando a query string &referrer=:

https://play.google.com/store/apps/details?id=com.empresa.app&referrer=utm_source%3Dgoogle%26utm_campaign%3Dpromo_app%26gclid%3DAbCd123%26deep_link_value%3Dproduto_4489

Durante o download, a Google Play Store grava essa string codificada em um armazenamento local protegido. No primeiro arranque do app, sua aplicação consulta o serviço local do Google Play via IPC (Inter-Process Communication) através da biblioteca com.android.installreferrer:installreferrer.

Exemplo de implementação em Kotlin:

import android.content.Context
import com.android.installreferrer.api.InstallReferrerClient
import com.android.installreferrer.api.InstallReferrerStateListener

fun extrairContextoInstalacao(context: Context, onDadosProntos: (String) -> Unit) {
    val referrerClient = InstallReferrerClient.newBuilder(context).build()
    
    referrerClient.startConnection(object : InstallReferrerStateListener {
        override fun onInstallReferrerSetupFinished(responseCode: Int) {
            if (responseCode == InstallReferrerClient.InstallReferrerResponse.OK) {
                val response = referrerClient.installReferrer
                val rawReferrer = response.installReferrer // query string decodificável
                
                // Repassa parâmetros de navegação para o roteador interno e MMP
                onDadosProntos(rawReferrer)
                referrerClient.endConnection()
            }
        }

        override fun onInstallReferrerServiceDisconnected() {
            // Rotina de reconexão se necessário
        }
    })
}

O registro na Play Store fica disponível por 90 dias e não depende de internet na primeira inicialização para ser lido, pois reside nos serviços locais do dispositivo.

iOS: O Fim do Fingerprinting e a Solução via UIPasteControl

No iOS, a App Store adota uma política de isolamento rigoroso: ela não repassa nenhuma query string de URL para o binário do app.

Historicamente, o mercado contornava isso via fingerprinting probabilístico: no clique na web, o servidor registrava IP, User-Agent, idioma e resolução de tela. No primeiro arranque do app, comparava esses dados.

Esse modelo tornou-se inviável por dois fatores:

  1. App Tracking Transparency (ATT): A Apple proibiu categoricamente a correlação de dados do dispositivo entre apps e sites de terceiros sem autorização prévia (tracking iOS pós-ATT).
  2. iCloud Private Relay: Ativado por padrão para assinantes iCloud+, mascara completamente os endereços IP dos usuários no Safari e em WebViews embutidas, destruindo qualquer assertividade estocástica.

A alternativa determinística compatível com as regras de privacidade utiliza a área de transferência local do dispositivo (pasteboard on-device).

No clique na web móvel, uma tela intermediária grava a URL de contexto na área de transferência antes de abrir a App Store. No entanto, desde o iOS 16, a leitura do clipboard por código dispara um modal invasivo: "App deseja colar do Safari. Permitir?".

Para eliminar esse atrito mantendo conformidade com o ATT, deve-se adotar o componente nativo UIPasteControl (iOS 16+) no onboarding ou na primeira tela de transição. Ao tocar no botão nativo, o sistema entende a intenção explícita do usuário e concede acesso sem exibir o alerta de permissão:

import UIKit
import BranchSDK

class OnboardingRoutingViewController: UIViewController, UIPasteConfigurationSupporting {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        if #available(iOS 16.0, *) {
            let config = UIPasteControl.Configuration()
            config.baseBackgroundColor = .systemIndigo
            config.baseForegroundColor = .white
            config.cornerStyle = .capsule
            
            let pasteButton = UIPasteControl(configuration: config)
            pasteButton.target = self
            pasteButton.frame = CGRect(x: 32, y: 300, width: 260, height: 50)
            view.addSubview(pasteButton)
        }
    }
    
    // Método delegado invocado automaticamente pelo UIPasteControl
    func paste(itemProviders: [NSItemProvider]) {
        Branch.getInstance().passPaste(itemProviders)
    }
}

Com o passPaste(), o SDK de atribuição extrai o payload criptografado da área de transferência local e resolve o deep link diferido de forma determinística, sem depender de IP.

Na Web: Smart Script, LocalStorage e Navegação Multi-Páginas

Um ponto de falha silenciosa ocorre antes mesmo do usuário chegar na loja.

Considere a jornada real:

  1. O usuário clica em um anúncio com utm_source=meta&utm_campaign=lancamento&fbclid=123.
  2. Acessa a Landing Page.
  3. Clica no menu, navega para a categoria "Calçados", depois abre a página do produto específico.
  4. Na página do produto, três níveis depois, ele clica no Smart Banner: "Abra no App".

Se o seu site só lê parâmetros da URL corrente (window.location.search), os parâmetros de campanha da primeira página já desapareceram há muito tempo. O clique no banner sairá sem UTMs e sem fbclid.

Para blindar o pipeline, o script web deve persistir o payload de aquisição no localStorage na entrada e montar dinamicamente o link do Smart Banner:

(function initWebToAppTracking() {
    const params = new URLSearchParams(window.location.search);
    
    // 1. Persistir dados da primeira aterrissagem
    if (params.has('utm_source') || params.has('gclid') || params.has('pid')) {
        const attributionPayload = {
            source: params.get('utm_source') || params.get('pid'),
            campaign: params.get('utm_campaign') || params.get('c'),
            gclid: params.get('gclid') || '',
            fbclid: params.get('fbclid') || '',
            capturedAt: Date.now()
        };
        localStorage.setItem('lead_attribution_payload', JSON.stringify(attributionPayload));
    }
    
    // 2. Resgatar payload histórico caso a página atual não possua parâmetros
    const saved = JSON.parse(localStorage.getItem('lead_attribution_payload') || '{}');
    const currentSku = document.querySelector('meta[name="product-sku"]')?.content || 'home';
    
    // 3. Montar a URL dinâmica do Smart Banner / Botão CTA
    const baseLink = "https://empresa.onelink.me/AbCd";
    const finalUrl = new URL(baseLink);
    
    finalUrl.searchParams.set('pid', saved.source || 'mobile_web');
    finalUrl.searchParams.set('c', saved.campaign || 'organic');
    finalUrl.searchParams.set('deep_link_value', currentSku);
    if (saved.gclid) finalUrl.searchParams.set('af_sub1', saved.gclid);
    if (saved.fbclid) finalUrl.searchParams.set('af_sub2', saved.fbclid);
    
    // 4. Injetar nos botões de incentivo ao download
    document.querySelectorAll('.btn-app-download, .smart-banner-link').forEach(el => {
        el.href = finalUrl.toString();
    });
})();

Dessa forma, não importa quantas páginas o usuário visite: o produto atual vai no deep_link_value e a atribuição da mídia inicial é preservada intacta.

Unificação no Data Warehouse: GA4, Client ID e BigQuery

Mesmo com o MMP atribuindo a instalação, as equipes de Business Intelligence e Engenharia de Dados costumam enfrentar um descompasso no GA4 exportado para o BigQuery: a duplicação de usuários.

No schema do GA4:

  • Na Web, o user_pseudo_id é o Client ID do cookie _ga (ex: 123456789.987654321).
  • No App, o user_pseudo_id é o Firebase App Instance ID, gerado aleatoriamente na instalação pelo SDK do Firebase.

Se um usuário navega no site mobile, clica no banner, instala o app e conclui uma compra, o BigQuery enxerga dois usuários distintos. A transação in-app fica órfã da sessão de navegação original.

A Solução: Injeção de Identidade Determinística

  1. Na web móvel, capture o Client ID do GA4 via gtag('get', ...) e anexe como sub-parâmetro no deferred deep link: deep_link_sub2=CLIENT_ID_WEB.
  2. No app, ao resolver o deferred deep link no primeiro arranque, capture esse valor e envie como propriedade de usuário ou parâmetro de evento no Firebase:
    // Exemplo Flutter
    await FirebaseAnalytics.instance.setUserProperty(
      name: 'linked_web_client_id',
      value: payloadFromDeferredLink['deep_link_sub2'],
    );
    
  3. No BigQuery, conecte as duas pontas via SQL:
WITH web_sessions AS (
    SELECT
        user_pseudo_id AS web_client_id,
        traffic_source.source AS source,
        traffic_source.name AS campaign,
        TIMESTAMP_MICROS(event_timestamp) AS session_time
    FROM
        `seu-projeto.analytics_123456.events_*`
    WHERE
        platform = 'WEB'
        AND event_name = 'session_start'
        AND _TABLE_SUFFIX >= '20260901'
),
app_conversions AS (
    SELECT
        user_pseudo_id AS app_instance_id,
        TIMESTAMP_MICROS(event_timestamp) AS purchase_time,
        event_value_in_usd,
        (SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'linked_web_client_id') AS linked_web_id
    FROM
        `seu-projeto.analytics_123456.events_*`
    WHERE
        platform IN ('ANDROID', 'IOS')
        AND event_name = 'in_app_purchase'
        AND _TABLE_SUFFIX >= '20260901'
)
SELECT
    a.app_instance_id,
    w.source,
    w.campaign,
    a.event_value_in_usd,
    w.session_time AS web_touchpoint,
    a.purchase_time AS app_conversion_time
FROM
    app_conversions a
INNER JOIN
    web_sessions w ON a.linked_web_id = w.web_client_id
WHERE
    a.purchase_time >= w.session_time;

Essa modelagem fecha o ciclo: a compra no aplicativo é unificada deterministicamente à sessão inicial do navegador móvel, mesmo que o usuário nunca tenha feito login no site.

O Novo Cenário: In-App Browsers e AdAttributionKit (AAK)

Dois fatores de infraestrutura exigem atenção contínua em 2026:

1. In-App Browsers (Instagram, TikTok, Facebook)

Navegadores embutidos em redes sociais bloqueiam a execução de Universal Links para reter o usuário dentro da plataforma. Se o link for clicado dentro do Instagram:

  • No Android: Utilize um fallback com esquema sintético intent:// no HTML. O Android força o encerramento da WebView do Instagram e invoca o aplicativo nativo ou a Play Store:
    <a href="intent://produto/4489#Intent;scheme=https;package=com.empresa.app;S.browser_fallback_url=https%3A%2F%2Fempresa.com%2Fproduto%2F4489;end;">
        Continuar no Aplicativo
    </a>
    
  • No iOS: Como o WebKit embutido impede a fuga para esquemas arbitrários, exiba uma página de transição (deepview) instruindo o usuário a tocar no menu ... e selecionar "Abrir no navegador do sistema".

2. Apple AdAttributionKit (AAK) e Web AdAttributionKit (WAAK)

Lançado para suceder o SKAdNetwork e atender às normas do DMA europeu (suporte a marketplaces de aplicativos de terceiros):

  • O AAK traz suporte nativo a re-engajamento via Universal Links com o parâmetro AdAttributionKitReengagementOpen, permitindo postbacks de reativação sem fingerprinting.
  • O Web AdAttributionKit (WAAK) no Safari padroniza a mensuração de conversões originadas em cliques em sites que resultam em instalações de app via tokens de preservação de privacidade, garantindo conformidade regulatória sem depender de cookies de terceiros.

Checklist de Implementação

  1. Validação de Domínio: Arquivos apple-app-site-association (sem extensão, servido via CDN em /.well-known/) e assetlinks.json devidamente validados com fingerprint SHA-256 da keystore de produção.
  2. Android Play Install Referrer: Dependência com.android.installreferrer integrada no app; rotina IPC capturando a query string na primeira execução antes de chamar o router.
  3. iOS On-Device: Implementação do componente nativo UIPasteControl no primeiro fluxo do app, evitando requisições probabilísticas bloqueadas pelo ATT e mascaradas pelo Private Relay.
  4. Camada Web: Script capturando parâmetros de UTM, gclid e fbclid no primeiro acesso e persistindo em localStorage para montagem dinâmica de Smart Banners.
  5. Taxonomia de Roteamento: Definição estável do parâmetro deep_link_value tratado pelo roteador nativo (React Navigation, GoRouter ou nativo), desacoplado de IDs transitórios de sessão (tagueamento RN e Flutter).
  6. Costura de Identidades: Injeção do GA4 Client ID Web no deep link para consolidação de receita cross-environment no BigQuery.
  7. Tratamento de IAB: Rotas de fuga configuradas com intent:// (Android) e deepviews informativas (iOS) para tráfego proveniente de redes sociais.

Próximo passo

Garantir que um usuário saia de uma campanha web e termine na tela exata do produto no app recém-instalado não é tarefa de uma única tag no Google Tag Manager — é um contrato de engenharia entre frontend web, mobile e pipeline de dados.

Se a sua empresa já investe em mídia para aquisição de app ou tráfego mobile, mas as instalações continuam caindo em tela inicial genérica e as métricas de conversão não batem entre GA4 e MMP:

Nós auditamos sua arquitetura de tracking, configuramos a passagem de parâmetros via Play Install Referrer e UIPasteControl, e estruturamos os scripts de web-to-app para você parar de desperdiçar orçamento de aquisição.