Legado — SKAdNetwork 3. Este guia cobre a implementação legada do SKAdNetwork 3.0 e é mantido apenas para integrações existentes. Para todas as implementações novas e atuais, use o Guia de Implementação do SKAdNetwork 4 .
Guia de Implementação S2S do SKAdNetwork 3.0 (Legado)
Implemente o framework SKAdNetwork 3.0 da Apple para atribuição iOS em conformidade com a privacidade usando integração server-to-server para medir o desempenho de campanhas de instalação de apps sem comprometer os identificadores dos usuários.
Visão Geral
O que é o SKAdNetwork?
O SKAdNetwork é o framework de atribuição da Apple que preserva a privacidade e permite a medição das taxas de conversão de campanhas de anúncios de instalação de apps no iOS sem exigir identificadores em nível de usuário.
O framework processa a atribuição por meio dos servidores da App Store, desvinculando as informações de atribuição dos identificadores de usuário e dos dados temporais antes de enviá-las às redes de anúncios.
Documentação da Apple: SKAdNetwork Framework Reference
Como o SKAdNetwork Funciona
O processo de atribuição ocorre inteiramente por meio da infraestrutura da Apple, garantindo a privacidade do usuário e ao mesmo tempo permitindo a medição do desempenho de campanhas.
Fluxo de Atribuição:
- Clique no Anúncio: O usuário clica em um anúncio que contém a assinatura do SKAdNetwork com o ID de rede, publisher e campanha
- Abertura da App Store: O dispositivo armazena os dados de atribuição e abre a App Store para a instalação
- Inicialização do App: O usuário instala e inicia o app pela primeira vez
- Registro: O app se registra no framework do SKAdNetwork para sinalizar uma instalação bem-sucedida
- Conversion Value: O app opcionalmente atualiza o conversion value (0-63) representando a atividade pós-instalação
- Janela do Timer: O dispositivo aguarda mais de 24 horas após a primeira inicialização ou a última atualização do conversion value
- Postback: A App Store envia o postback de atribuição à rede de anúncios com o ID da campanha, o conversion value e a assinatura criptográfica
Privacidade por Design:
- O postback não contém identificadores de dispositivo ou de usuário
- O atraso mínimo de 24 horas impede a correlação temporal
- O app nunca sabe em qual anúncio o usuário clicou
- A rede nunca sabe qual usuário específico fez a instalação
Capacidades e Limitações
O SKAdNetwork Oferece:
- Atribuição Last-Click: Funciona sem o consentimento do usuário ou opt-in de ATT
- Detalhamento de Campanha: Granularidade por origem, campanha e publisher
- Conversion Values: 64 valores discretos (0-63) para medição pós-instalação
- Prevenção de Fraude: Assinaturas criptográficas validam a autenticidade da atribuição
Limitações Atuais:
- Sem Dados em Nível de Usuário: Não é possível rastrear jornadas individuais de usuários
- Sem View-Through: Apenas atribuição baseada em clique
- Conversion Values Limitados: Um único valor de 6 bits (0-63) por instalação
- Granularidade Limitada: Máximo de 100 IDs de campanha, sem detalhamento por grupo de anúncios ou criativo
- Sem Coortes Longas: Prazo limitado para a medição de conversão
- Exposição a Fraude: O conversion value em si não é assinado, e os postbacks podem ser duplicados
Recursos da Singular
A Singular oferece recursos abrangentes para a implementação e otimização do SKAdNetwork.
- SKAN: Practical Standard for SKAdNetwork Implementation
- SKAdNetwork 101: What It Means for You
- SKAdNetwork Code: GitHub Repository for Networks, Publishers, Advertisers
- How to Test SKAdNetwork: Step-by-Step Instructions
- Singular Announces First-to-Market IDFA Replacement Support
- Advanced Measurement Using SKAdNetwork: Unlocking ROAS
- Secure SKAdNetwork 2.0: Seamless Trust Establishment Setup
Solução SKAdNetwork da Singular
A solução SKAdNetwork da Singular oferece gerenciamento de atribuição de ponta a ponta, desde a implementação no lado do cliente até o processamento de postbacks e a otimização de campanhas.
Componentes da Solução
Recursos da Plataforma
Suporte abrangente ao SKAdNetwork em todo o fluxo de trabalho de atribuição e analytics.
| Componente | Funcionalidade |
|---|---|
| Código do Lado do Cliente | Exemplos de código iOS nativo para o registro no framework do SKAdNetwork e o gerenciamento de conversion values |
| Processamento de Postbacks | Validação e agregação de postbacks de todas as redes de anúncios com relatórios unificados |
| Proteção Contra Fraude | Validação de assinatura criptográfica, deduplicação de transaction ID e verificação segura de parâmetros |
| Gerenciamento de Conversão | Configuração dinâmica do dashboard para codificar atividades pós-instalação em conversion values |
| Relatórios | Tradução do ID da campanha e enriquecimento com parâmetros de marketing para uma análise granular |
| Postbacks de Parceiros | Conversion values decodificados enviados como eventos e receita para a otimização de parceiros |
Arquitetura de Integração S2S
Componentes de Implementação
A integração server-to-server do SKAdNetwork consiste em duas principais áreas de implementação.
1. Implementação no Lado do Cliente (Obrigatória):
- Registro no framework do SKAdNetwork na inicialização do app
- Gerenciamento inteligente do conversion value com base na atividade pós-instalação
- Essencial para a otimização de campanhas usando a atribuição do SKAdNetwork
2. Atualização da Integração S2S (Opcional):
- Enriquece os eventos e sessões S2S da Singular com metadados do SKAdNetwork
- Permite a validação da implementação e a solução de problemas
- Fornece timestamps de conversion value para depuração
Implementação no Lado do Cliente
Implemente o registro no framework do SKAdNetwork e o gerenciamento de conversion values usando os exemplos de código iOS nativo da Singular para uma medição de campanha ideal.
Responsabilidades de Implementação
Funcionalidade Principal
O código do lado do cliente lida com duas funções críticas para a medição do SKAdNetwork.
- Registro no SKAdNetwork: Registra o app no framework imediatamente após a inicialização para habilitar a atribuição
-
Gerenciamento do Conversion Value:
- Comunica-se de forma síncrona com o endpoint da Singular para receber o próximo conversion value
- Reporta sessões, eventos e receita à Singular
- Recebe um conversion value codificado que representa a atividade pós-instalação configurada
- Coleta metadados do SKAdNetwork (timestamp da primeira chamada, timestamp da última chamada, valor atual)
Interface do SKAdNetwork
Definições de Métodos
A interface inclui métodos para registro no framework, rastreamento de sessão, rastreamento de eventos e medição de receita.
// SKANSnippet.h
#import <Foundation/Foundation.h>
@interface SKANSnippet : NSObject
// Register for SKAdNetwork attribution.
// Call this method as soon as possible once app is launched
+ (void)registerAppForAdNetworkAttribution;
// Track retention and cohorts by calling after each session.
// Reports session details and updates conversion value if needed.
// Conversion value updates only if new value greater than previous.
// Optional callback runs once conversion value updated.
+ (void)updateConversionValueAsync:(void(^)(int, NSError*))handler;
// Track conversion events by calling after each event.
// Reports event details and updates conversion value if needed.
// Optional callback runs once conversion value updated.
+ (void)updateConversionValueAsync:(NSString*)eventName
withCompletionHandler:(void(^)(int, NSError*))handler;
// Track revenue by calling before each revenue event.
// Updates total revenue for next conversion value calculation.
// Note:
// 1. Call before 'updateConversionValueAsync' to ensure revenue included
// 2. Avoid calling on event retries to prevent double-counting
+ (void)updateRevenue:(double)amount andCurrency:(NSString*)currency;
// Gets current conversion value (nil if none set)
+ (NSNumber *)getConversionValue;
@end
Método de Registro
registerAppForAdNetworkAttribution
registra o app no framework do SKAdNetwork,
iniciando o timer de atribuição de 24 horas.
Comportamento do Timer:
- O dispositivo envia a notificação de instalação de 0 a 24 horas após a expiração do timer
- Atualizar o conversion value com um valor mais alto reinicia o timer para um novo intervalo de 24 horas
- Referência da Apple: updateConversionValue Documentation
Métodos de Conversion Value
Os métodos calculam e atualizam os conversion values com base na atividade pós-instalação e no modelo de conversão configurado.
Atividades Suportadas:
- Sessões: Críticas para a medição de retenção
- Eventos de Conversão: Críticos para a medição de eventos pós-instalação
- Eventos de Receita: Críticos para a medição de receita
Implementação da Interface
Código de Implementação Completo
A implementação completa lida com o registro, o gerenciamento de conversão e a comunicação com a API da Singular.
// SKANSnippet.m
#import "SKANSnippet.h"
#import <StoreKit/SKAdNetwork.h>
#define SESSION_EVENT_NAME @"__SESSION__"
#define SINGULAR_API_URL @"https://sdk-api-v1.singular.net/api/v1/conversion_value"
// Keys for UserDefaults storage
#define CONVERSION_VALUE_KEY @"skan_conversion_value"
#define FIRST_SKAN_CALL_TIMESTAMP @"skan_first_call_to_skadnetwork_timestamp"
#define LAST_SKAN_CALL_TIMESTAMP @"skan_last_call_to_skadnetwork_timestamp"
#define TOTAL_REVENUE_BY_CURRENCY_KEY @"skan_total_revenue_by_currency"
#define SECONDS_PER_DAY 86400
static NSLock *lockObject;
@implementation SKANSnippet
+ (void)registerAppForAdNetworkAttribution {
if ([SKANSnippet getFirstSkanCallTimestamp] != 0) {
return;
}
if (@available(iOS 11.3, *)) {
[SKAdNetwork registerAppForAdNetworkAttribution];
[SKANSnippet setFirstSkanCallTimestamp];
[SKANSnippet setLastSkanCallTimestamp];
}
}
+ (void)updateConversionValueAsync:(void(^)(int, NSError*))handler {
[SKANSnippet updateConversionValueAsync:SESSION_EVENT_NAME withCompletionHandler:handler];
}
+ (void)updateConversionValueAsync:(NSString*)eventName withCompletionHandler:(void(^)(int, NSError*))handler {
if (@available(iOS 14, *)) {
if ([SKANSnippet isSkanUpdateWindowOver]) {
return;
}
[SKANSnippet getConversionValueFromServer:eventName withCompletionHandler:handler];
}
}
+ (void)updateRevenue:(double)amount andCurrency:(NSString*)currency {
if (@available(iOS 14, *)) {
// Update total revenues
if (amount != 0 && currency) {
NSMutableDictionary* revenues = [[SKANSnippet getTotalRevenue] mutableCopy];
NSNumber* currentRevenue = @(0);
if ([revenues objectForKey:currency]) {
currentRevenue = [revenues objectForKey:currency];
}
currentRevenue = @([currentRevenue floatValue] + amount);
[revenues setObject:currentRevenue forKey:currency];
[SKANSnippet setTotalRevenue:revenues];
}
}
}
+ (NSNumber *)getConversionValue {
NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
if (![userDefaults objectForKey:CONVERSION_VALUE_KEY]) {
return nil;
}
return @([userDefaults integerForKey:CONVERSION_VALUE_KEY]);
}
+ (void)getConversionValueFromServer:(NSString*)eventName withCompletionHandler:(void(^)(int, NSError*))handler {
if (!lockObject) {
lockObject = [NSLock new];
}
// Making the lock async so it will not freeze the calling thread
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
[lockObject lock];
NSURLComponents *components = [NSURLComponents componentsWithString:SINGULAR_API_URL];
NSString* bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
components.queryItems = @[
[NSURLQueryItem queryItemWithName:@"a" value:@"YOUR_SDK_KEY"],
[NSURLQueryItem queryItemWithName:@"i" value:bundleIdentifier],
[NSURLQueryItem queryItemWithName:@"app_v" value:@"YOUR_APP_VERSION"],
[NSURLQueryItem queryItemWithName:@"n" value:eventName],
[NSURLQueryItem queryItemWithName:@"p" value:@"iOS"],
[NSURLQueryItem queryItemWithName:@"idfv" value:@"YOUR_IDFV"],
[NSURLQueryItem queryItemWithName:@"idfa" value:@"YOUR_IDFA"],
[NSURLQueryItem queryItemWithName:@"conversion_value" value:[[SKANSnippet getConversionValue] stringValue]],
[NSURLQueryItem queryItemWithName:@"total_revenue_by_currency" value:[SKANSnippet dictionaryToJsonString:[SKANSnippet getTotalRevenue]]],
[NSURLQueryItem queryItemWithName:@"first_call_to_skadnetwork_timestamp" value:[NSString stringWithFormat:@"%ld", [SKANSnippet getFirstSkanCallTimestamp]]],
[NSURLQueryItem queryItemWithName:@"last_call_to_skadnetwork_timestamp" value:[NSString stringWithFormat:@"%ld", [SKANSnippet getLastSkanCallTimestamp]]],
];
[[[NSURLSession sharedSession] dataTaskWithURL:components.URL
completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (error) {
[lockObject unlock];
if (handler) {
handler(-1, error);
}
return;
}
NSDictionary* parsedResponse = [SKANSnippet jsonDataToDictionary:data];
if (!parsedResponse) {
[lockObject unlock];
if (handler) {
handler(-1, [NSError errorWithDomain:bundleIdentifier
code:0
userInfo:@{NSLocalizedDescriptionKey:@"Failed parsing server response"}]);
}
return;
}
NSNumber *conversionValue = [parsedResponse objectForKey:@"conversion_value"];
if (!conversionValue) {
[lockObject unlock];
NSString *status = [parsedResponse objectForKey:@"status"];
if (!status || ![status isEqualToString:@"ok"]) {
if (handler) {
NSString *reason = [parsedResponse objectForKey:@"reason"];
if (!reason) {
reason = @"Got error from server";
}
handler(-1, [NSError errorWithDomain:bundleIdentifier
code:0
userInfo:@{NSLocalizedDescriptionKey:reason}]);
}
}
return;
}
NSNumber* currentValue = [SKANSnippet getConversionValue];
if ([conversionValue intValue] <= [currentValue intValue]) {
[lockObject unlock];
return;
}
[SKANSnippet setConversionValue:[conversionValue intValue]];
[SKANSnippet setLastSkanCallTimestamp];
if (![SKANSnippet getFirstSkanCallTimestamp]) {
[SKANSnippet setFirstSkanCallTimestamp];
}
[lockObject unlock];
if (handler) {
handler([conversionValue intValue], error);
}
}] resume];
});
}
+ (BOOL)isSkanUpdateWindowOver {
NSInteger timeDiff = [SKANSnippet getCurrentUnixTimestamp] - [SKANSnippet getLastSkanCallTimestamp];
return SECONDS_PER_DAY <= timeDiff;
}
@end
Métodos Utilitários
Gerenciamento de Metadados
Os métodos utilitários gerenciam o armazenamento de metadados do SKAdNetwork no UserDefaults, essencial para o cálculo do conversion value.
Implementação Crítica: Estes utilitários mantêm os metadados necessários para o cálculo preciso do conversion value. Não os modifique a menos que seja necessário.
+ (NSInteger)getFirstSkanCallTimestamp {
NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
return [userDefaults integerForKey:FIRST_SKAN_CALL_TIMESTAMP];
}
+ (NSInteger)getLastSkanCallTimestamp {
NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
return [userDefaults integerForKey:LAST_SKAN_CALL_TIMESTAMP];
}
+ (NSDictionary*)getTotalRevenue {
NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
if (![userDefaults objectForKey:TOTAL_REVENUE_BY_CURRENCY_KEY]) {
return [NSDictionary new];
}
return [userDefaults objectForKey:TOTAL_REVENUE_BY_CURRENCY_KEY];
}
+ (NSInteger)getCurrentUnixTimestamp {
return [[NSDate date]timeIntervalSince1970];
}
+ (void)setConversionValue:(int)value {
if (@available(iOS 14.0, *)) {
if (value <= [[SKANSnippet getConversionValue] intValue]) {
return;
}
[SKAdNetwork updateConversionValue:value];
NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
[userDefaults setInteger:value forKey:CONVERSION_VALUE_KEY];
[userDefaults synchronize];
}
}
+ (void)setFirstSkanCallTimestamp {
NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
[userDefaults setInteger:[SKANSnippet getCurrentUnixTimestamp]
forKey:FIRST_SKAN_CALL_TIMESTAMP];
[userDefaults synchronize];
}
+ (void)setLastSkanCallTimestamp {
NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
[userDefaults setInteger:[SKANSnippet getCurrentUnixTimestamp]
forKey:LAST_SKAN_CALL_TIMESTAMP];
[userDefaults synchronize];
}
+ (void)setTotalRevenue:(NSDictionary *)values {
NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
[userDefaults setObject:values forKey:TOTAL_REVENUE_BY_CURRENCY_KEY];
[userDefaults synchronize];
}
+ (NSString*)dictionaryToJSONString:(NSDictionary*)dictionary {
if (!dictionary || [dictionary count] == 0){
return @"{}";
}
NSError *error;
NSData *JSONData = [NSJSONSerialization dataWithJSONObject:dictionary
options:0
error:&error];
if (error || !JSONData) {
return @"{}";
}
return [[NSString alloc] initWithData:JSONData
encoding:NSUTF8StringEncoding];
}
+ (NSDictionary*)JSONDataToDictionary:(NSData*)JSONData {
if (!JSONData) {
return nil;
}
NSError * error;
NSDictionary * parsedData = [NSJSONSerialization
JSONObjectWithData:JSONData
options:kNilOptions error:&error];
if (error || !parsedData) {
return nil;
}
return parsedData;
}
@end
Atualização da Integração S2S
Opcionalmente, aprimore a integração server-to-server com metadados do SKAdNetwork para validação da implementação e solução de problemas.
Atualizações Obrigatórias
Resposta de Conversion Value
Após o modelo de conversão ser ativado, os endpoints S2S retornam o campo inteiro
conversion_value
que contém o próximo valor para a
atualização no lado do cliente.
Tratamento da Resposta:
Faça o parse do
conversion_value
das respostas do endpoint S2S e aplique-o ao framework do SKAdNetwork por meio do
código do lado do cliente.
Aprimoramentos Opcionais
Parâmetros de Metadados
Enriqueça as requisições de sessão e de evento S2S com metadados do SKAdNetwork para validar a integração e solucionar problemas de implementação.
| Parâmetro | Descrição |
|---|---|
skan_conversion_value
|
Conversion value mais recente no momento da requisição |
skan_first_call_timestamp
|
Timestamp Unix da primeira chamada à API do SKAdNetwork |
skan_last_call_timestamp
|
Timestamp Unix da chamada mais recente à API do SKAdNetwork |
Código de Extração de Metadados
Extraia os valores de metadados do SKAdNetwork usando os métodos utilitários para a transmissão S2S.
NSDictionary *skanMetadata = @{
@"skan_conversion_value":
[[SKANSnippet getConversionValue] stringValue],
@"skan_first_call_timestamp":
[NSString stringWithFormat:@"%ld", [SKANSnippet getFirstSkanCallTimestamp]],
@"skan_last_call_timestamp":
[NSString stringWithFormat:@"%ld", [SKANSnippet getLastSkanCallTimestamp]]
};
// Forward skanMetadata to your server for S2S API enrichment
Encaminhe os parâmetros para o lado do servidor e anexe-os às requisições da API S2S da Singular. Documentação completa dos parâmetros: S2S API Reference
Fluxo de Integração
Fluxo completo do SKAdNetwork para clientes S2S, desde o gerenciamento de conversion value no lado do cliente até o processamento de postbacks.
Etapas do Fluxo
Processo de Ponta a Ponta
- Requisição de Conversion Value: O código do app se comunica de forma síncrona com o endpoint da Singular para obter o conversion value mais recente com base em sessões, eventos e receita
- Atualização do Framework: O app atualiza o framework do SKAdNetwork com o conversion value recebido
- Enriquecimento de Metadados: O app enriquece os eventos e sessões S2S com metadados do SKAdNetwork para validação
- Expiração do Timer: Após mais de 24 horas da última atualização, o SKAdNetwork envia o postback à rede de anúncios
- Encaminhamento do Postback: A rede encaminha o postback à Singular (setup seguro ou regular)
-
Processamento do Postback:
A Singular processa o postback
ao:
- Validar a assinatura criptográfica
- Decodificar o conversion value usando o modelo configurado
- Enriquecer com parâmetros de marketing com base nas integrações de parceiros
- Enviar os dados decodificados para BI e parceiros por meio de postbacks
Separação de Dados: Os dados do SKAdNetwork (instalações e eventos decodificados) ficam acessíveis por meio de relatórios, APIs, tabelas ETL e postbacks separados, para evitar a mistura com os conjuntos de dados existentes durante os testes e a validação.
Implementação no Ciclo de Vida do App
Integre os métodos do SKAdNetwork nos pontos apropriados do ciclo de vida do app para uma cobertura de atribuição completa.
Exemplo de Implementação
Posicionamento dos Métodos
// On app launch (in applicationDidFinishLaunching)
[SKANSnippet registerAppForAdNetworkAttribution];
// After each session handled
[SKANSnippet updateConversionValueAsync:^(int value, NSError *error) {
if (error) {
NSLog(@"Conversion value update failed: %@", error);
} else {
NSLog(@"Conversion value updated to: %d", value);
}
}];
// After handling non-revenue events
[SKANSnippet updateConversionValueAsync:@"event_name"
withCompletionHandler:^(int value, NSError *error) {
if (error) {
NSLog(@"Event conversion value update failed: %@", error);
} else {
NSLog(@"Event conversion value updated to: %d", value);
}
}];
// After handling revenue events
[SKANSnippet updateRevenue:15.53 andCurrency:@"USD"];
[SKANSnippet updateConversionValueAsync:@"revenue_event_name"
withCompletionHandler:^(int value, NSError *error) {
if (error) {
NSLog(@"Revenue conversion value update failed: %@", error);
} else {
NSLog(@"Revenue conversion value updated to: %d", value);
}
}];
Teste da Implementação
Valide a implementação do SKAdNetwork usando o endpoint de teste da Singular antes da implantação em produção.
Ambiente de Teste
Configuração do Endpoint de Teste
Substitua o endpoint de gerenciamento de conversão de produção pelo endpoint de teste para simular o fluxo do app.
URL do Endpoint de Teste:
https://skadnetwork-testing.singular.net/api/v1/conversion_value
- Nenhuma API key necessária para o endpoint de teste
- Retorna os conversion values de 0 a 2 sequencialmente
- Retorna uma string vazia após a terceira chamada
Procedimento de Teste
Etapas de Validação
-
Atualizar o Endpoint:
Substitua a constante
SINGULAR_API_URLpela URL do endpoint de teste - Inicializar o Conversion Value: Garanta que o conversion value padrão seja inicializado como 0
- Gerar Eventos: Dispare 3 eventos diferentes dentro do app
-
Verificar as Atualizações:
Confirme que
updateConversionValueAsyncfoi chamado após cada evento - Registrar os Valores: Registre nos logs os conversion values retornados para verificar a progressão correta (0 → 1 → 2)
- Confirmar a Conclusão: Após a terceira chamada, verifique a resposta vazia e a retenção do valor final
Comportamento Esperado:
- Primeiro evento: Recebe o conversion value 0
- Segundo evento: Recebe o conversion value 1
- Terceiro evento: Recebe o conversion value 2
- Quarto evento em diante: Resposta vazia, o valor 2 persiste
Changelog do Código
Acompanhe as atualizações dos exemplos de código e as correções críticas aplicadas ao longo do tempo.
Histórico de Versões
| Data | Alterações |
|---|---|
| 1 de outubro de 2020 |
|
| 23 de setembro de 2020 |
|
| 15 de setembro de 2020 |
|