Como Implementar Tracking de Conversões em Apps iOS Pós-ATT

IDFA com opt-in, AdAttributionKit pós-negação, conversion value, Privacy Manifests e S2S — o mapa híbrido para campanhas Apple sem fingerprinting.

Contexto

ROAS no iOS caiu depois do ATT. Meta e Google mostram menos conversões. O MMP entrega postbacks com 24–48 horas de atraso. Produto abre o Firebase e vê funil in-app normal. Engenharia diz que “o SDK está no ar”.

Não existe botão que devolve IDFA para quem recusou rastreamento. A precisão de campanha volta por arquitetura dual: atribuição determinística só com opt-in; para o resto, postbacks agregados do sistema (SKAdNetwork / AdAttributionKit) mais eventos server-side com dado first-party autenticado.

Este guia é implementação — Swift, Info.plist, validação — para dev e mídia fecharem o mesmo contrato de conversão. O papel do MMP no stack está em Firebase vs AppsFlyer; aqui o foco é o que o app iOS precisa fazer pós-ATT.

O que o ATT realmente corta

Para a Apple, tracking é ligar dados do seu app a dados de outra empresa (outro app, site ou offline) para anúncio direcionado ou medição de mídia. Compartilhar com data broker entra na mesma definição.

Sem consentimento — ou com “Permitir que apps solicitem rastreamento” desligado no iOS — o IDFA vira 00000000-0000-0000-0000-000000000000. A partir daí:

  • Proibido usar fingerprinting ou identificador alternativo para contornar ATT.
  • Proibido inferir identidade cross-app para atribuição de ads.

O pre-prompt (tela antes do alerta nativo) segue as HIG:

  • Botão neutro: “Continuar” ou “Avançar” — não “Permitir”.
  • Um botão claro que leva ao diálogo do sistema; sem atalho de cancelamento que desvia a decisão.
  • Sem recompensa (desconto, feature extra) em troca de opt-in.
  • Sem controles duplos: escolha no ATT não pode ser anulada em tela secundária.

Gate de consentimento antes de inicializar SDK de ads/MMP continua obrigatório também na LGPD — ver telemetria no app. ATT e banner jurídico não são a mesma coisa, mas o fluxo precisa estar alinhado.

Arquitetura híbrida

flowchart TD
    launch[AppLaunch] --> attCheck{ATT_autorizado}
    attCheck -->|sim| idfaPath[IDFA_MMP_deterministico]
    attCheck -->|nao| aakPath[AdAttributionKit_postbacks]
    idfaPath --> networks[Redes_e_MMP]
    aakPath --> networks
    inAppEvent[Evento_in_app] --> s2s[Servidor_first_party]
    s2s --> capi[CAPI_Meta_Google_etc]
    s2s --> product[Firebase_produto]

Autorizado: o app lê IDFA e o MMP faz atribuição determinística (install, eventos in-app com rede/campanha). É o caminho mais próximo do “pré-ATT” — mas só para a fatia que aceitou.

Negado: atribuição de install passa por postbacks assinados do AdAttributionKit (evolução do SKAdNetwork). Eventos de conversão in-app que as redes precisam para otimização vão por servidor first-party → Conversion API (Meta, Google, etc.) usando identificadores do seu login (e-mail ou telefone hasheados SHA-256 no servidor), nunca IDFA. Sem usuário autenticado, você fica com sinal agregado e delayed — não com atribuição user-level.

Hash de e-mail não é anonimato; é pseudonimização. O desenho compliant combina postback agregado da Apple com S2S mínimo — detalhe em anonimização e privacidade.

Telemetria de produto (Firebase, funil, retenção) segue first-party dentro do app, independente do ATT — desde que não vire proxy de tracking cross-company.

SKAdNetwork → AdAttributionKit

O SKAdNetwork 4.0 mede campanhas app-to-app com janelas fixas da plataforma. Redes registradas usam identificador terminado em .skadnetwork.

O AdAttributionKit (iOS 17.4+, expandido no iOS 18) é a evolução operacional:

  • Web-to-app e app-to-app no mesmo framework.
  • Re-engagement no iOS 18: clique em app publicadora abre sua app via Universal Link com AdAttributionKitReengagementOpen; você registra conversão com conversionTypes: [.reengagement].
  • Assinaturas de anúncio em JWS (JSON Web Signature), mais compactas que SKAN legado.
  • Janelas de atribuição configuráveis no Info.plist (AdAttributionKitConfigurations): click-through 1–30 dias, view-through 1–7 dias, cooldown anti-duplicata.
  • Redes novas usam identificador em minúsculas terminado em .adattributionkit.

Se você ainda só integra SKAN 4.0, planeje migração: campanhas web-to-app e reativação dependem do AAK.

Conversion value e janelas

Postback agregado não manda “usuário X comprou”. Manda conversion value que você mapeia para eventos de negócio. Esse mapa é contrato entre produto, mídia e eng — no espírito de um tracking plan, só que com 6 bits (0–63) no fine value.

Crowd Anonymity: a Apple só libera granularidade se o volume da campanha garante anonimato estatístico (tiers 0–3):

  • Tier 0 (volume muito baixo): postback sem conversion value — só sinal mínimo.
  • Tiers 1–2: libera coarse value (low, medium, high).
  • Tier 3: libera fine value (0–63), publisher item ID e source identifier de quatro dígitos.

Três janelas por install:

  1. Dias 0–2: fine (0–63) ou coarse.
  2. Dias 3–7: só coarse.
  3. Dias 8–35: só coarse.

Cada janela gera postback próprio. Em produção, o envio ocorre 24–48 horas após o fim da janela — delay intencional para impedir correlação temporal click → install → compra.

Use lockPostback: true ao chamar Postback.updateConversionValue quando precisar fechar a janela cedo (ex.: campanha de lançamento com budget curto) em vez de esperar o ciclo natural.

Exemplo de mapa (ajuste ao seu funil):

  • 0 — install apenas (default).
  • 10 — cadastro / conta criada.
  • 20 — trial iniciado.
  • 40 — add to cart ou paywall visto.
  • 63 — purchase / assinatura paga.

Documente isso antes de subir build. Mídia otimiza em cima do que você codificou — não do que “acham” que o app manda. Esse mapa também alimenta AEO/VO nas campanhas de install — ver eventos in-app para campanhas.

Privacy Manifests e SDKs

Desde iOS 17, app e cada SDK precisam de PrivacyInfo.xcprivacy no bundle. O Xcode consolida tudo no Privacy Nutrition Label da App Store.

Pontos que bloqueiam submissão:

  • Required Reason APIs (UserDefaults, system boot time, file timestamps, disk space, etc.) exigem código de razão aprovado pela Apple em NSPrivacyAccessedAPITypes. API restrita sem declaração → rejeição automática no Connect.
  • NSPrivacyTracking e NSPrivacyTrackingDomains devem refletir o que o app/SDK realmente faz — divergência entre manifesto e comportamento é risco de review.
  • SDKs de MMP e ads devem vir como XCFramework assinado com manifesto próprio; audite dependências transitivas.

Não repita aqui o mapa legal completo de bases LGPD — isso está em LGPD no app. No iOS pós-ATT, manifesto errado mata release antes de qualquer discussão de ROAS.

Implementação em Swift

1. Solicitar ATT e ler IDFA

Dispare só quando .notDetermined. Se já negou ou autorizou, não re-peça o alerta — respeite o estado.

import AppTrackingTransparency
import AdSupport

final class PrivacyConsentManager {
    static let shared = PrivacyConsentManager()

    func requestTrackingAuthorization(completion: @escaping (ATTrackingManager.AuthorizationStatus) -> Void) {
        let status = ATTrackingManager.trackingAuthorizationStatus
        guard status == .notDetermined else {
            completion(status)
            return
        }
        ATTrackingManager.requestTrackingAuthorization { newStatus in
            DispatchQueue.main.async {
                if newStatus == .authorized {
                    _ = ASIdentifierManager.shared().advertisingIdentifier
                    // Inicialize MMP / ads IDFA-dependentes aqui
                }
                completion(newStatus)
            }
        }
    }
}

Timing: depois de contexto (onboarding, valor do app), não no cold start antes de qualquer tela.

2. Atualizar conversion value (AdAttributionKit)

No iOS 18+, use Postback.updateConversionValue para install e re-engagement:

import AdAttributionKit

final class ConversionTrackingManager {
    static let shared = ConversionTrackingManager()

    func recordPurchase() async {
        let update = PostbackUpdate(
            fineConversionValue: 63,
            lockPostback: false,
            conversionTypes: [.install]
        )
        try? await Postback.updateConversionValue(update)
    }

    func finalizeWindow() async {
        let update = PostbackUpdate(
            fineConversionValue: 63,
            lockPostback: true,
            conversionTypes: [.install]
        )
        try? await Postback.updateConversionValue(update)
    }
}

Chame nos pontos do funil mapeados no contrato (cadastro, trial, purchase). Para re-engagement, use conversionTypes: [.reengagement] ao processar Universal Link com AdAttributionKitReengagementOpen.

3. Info.plist

Chaves essenciais:

  • NSAdvertisingAttributionReportEndpoint — URL HTTPS do seu servidor para receber cópia dos postbacks vencedores.
  • AdAttributionKitConfigurations — janelas de click/view e AttributionCooldown (ex.: install-cooldown-hours: 24).

O endpoint deve responder HTTP 200 e expor .well-known/adattributionkit/report-postback para validação de assinaturas JWS da Apple.

Validar antes de produção

Conecte dispositivo físico, ative Settings > Developer > AdAttributionKit / SKAdNetwork Testing. Postbacks de teste chegam em 5–10 minutos — não confunda com latência real de campanha (24–48 h).

Checklist de QA:

  • ATT dispara uma vez, no momento certo; IDFA não vaza em log/analytics sem autorização.
  • Conversion values batem com o documento acordado com mídia.
  • Endpoint de postback retorna 200 e persiste payload assinado.
  • Fluxo S2S não envia IDFA para usuários que negaram ATT.
  • MMP recebe e decodifica postbacks SKAN/AAK (configuração no painel do vendor).
  • Privacy Manifests de app + SDKs auditados antes do upload.

Checklist

  1. Pre-prompt HIG-compliant; ATT após contexto, não no splash.
  2. Mapa fine value (0–63) assinado por produto + mídia + eng.
  3. Target iOS com AdAttributionKit; re-engagement via Universal Link se campanha de reativação.
  4. NSAdvertisingAttributionReportEndpoint + rota .well-known funcionando.
  5. Audit PrivacyInfo.xcprivacy (app e dependências).
  6. S2S/CAPI só com first-party autenticado quando ATT negado.
  7. MMP configurado para SKAN/AAK; KPI de install/ROAS com fonte única (MMP), funil in-app no Firebase — não misturar como se fossem o mesmo número.
  8. Dev test concluído; stakeholders avisados sobre delay de postback em produção.

Próximo passo

Implementação “com SDK no ar” sem mapa de conversion value, endpoint de postback e fluxo S2S condicionado ao ATT é o motivo pelo qual campanhas iOS parecem cegas mesmo com budget alto.

Se o app já tem MMP + Firebase e os painéis brigam, o trabalho não é outro dashboard — é fechar o contrato de valores, alinhar latência agregada à expectativa de mídia e garantir que nada no código tenta substituir IDFA negado.

Se quiser, partimos do seu stack (target iOS, MMP, eventos de conversão) e fechamos: mapa de fine values, ordem do prompt ATT e o que vai para S2S vs postback.