SKAdNetwork 4 disponível: Para obter a versão mais recente da SKAdNetwork, consulte o Guia de implementação da SKAdNetwork 4.
Guia de implementação de SKAdNetwork 3.0 S2S
Implemente a estrutura SKAdNetwork 3.0 da Apple para atribuição iOS compatível com a privacidade, utilizando a integração servidor a servidor para medir o desempenho da campanha de instalação de aplicações sem comprometer os identificadores de utilizadores.
Visão geral
O que é a SKAdNetwork?
SKAdNetwork é a estrutura de atribuição da Apple que preserva a privacidade e permite medir as taxas de conversão de campanhas publicitárias de instalação de aplicativos no iOS sem exigir identificadores no nível do usuário.
A estrutura processa a atribuição através dos servidores da App Store, desligando as informações de atribuição dos identificadores do utilizador e dos dados temporais antes de as enviar para as redes de anúncios.
Documentação da Apple: Referência da estrutura SKAdNetwork
Como funciona a SKAdNetwork
O processo de atribuição ocorre inteiramente através da infraestrutura da Apple, garantindo a privacidade do utilizador e permitindo a medição do desempenho da campanha.
Fluxo de atribuição:
- Clique no anúncio: O utilizador clica no anúncio que contém a assinatura SKAdNetwork com o ID da rede, do editor e da campanha
- Abertura da App Store: O dispositivo armazena os dados de atribuição e abre a App Store para instalação
- Lançamento do aplicativo: O utilizador instala e inicia a aplicação pela primeira vez
- Registo: A aplicação regista-se na estrutura SKAdNetwork para sinalizar uma instalação bem sucedida
- Valor de conversão: Opcionalmente, a aplicação actualiza o valor de conversão (0-63) que representa a atividade pós-instalação
- Janela do temporizador: O dispositivo aguarda mais de 24 horas após o primeiro lançamento ou a última atualização do valor de conversão
- Postback: A App Store envia um postback de atribuição para a rede de anúncios com o ID da campanha, o valor de conversão e a assinatura criptográfica
Privacidade desde a conceção:
- O postback não contém identificadores de dispositivos ou utilizadores
- O atraso mínimo de 24 horas impede a correlação temporal
- A aplicação nunca sabe em que anúncio o utilizador clicou
- A rede nunca sabe que utilizador específico instalou
Recursos e limitações
SKAdNetwork oferece:
- Atribuição de último clique: Funciona sem o consentimento do utilizador ou o opt-in da ATT
- Detalhamento da campanha: Granularidade de origem, campanha e editor
- Valores de conversão: 64 valores discretos (0-63) para medição pós-instalação
- Prevenção de fraudes: Assinaturas criptográficas validam a autenticidade da atribuição
Limitações actuais:
- Sem dados ao nível do utilizador: Não é possível acompanhar os percursos individuais dos utilizadores
- Sem visualização: Apenas atribuição baseada em cliques
- Valores de conversão limitados: Valor único de 6 bits (0-63) por instalação
- Granularidade limitada: Máximo de 100 IDs de campanha, sem discriminação de grupo de anúncios ou criativo
- Sem coortes longas: Período de tempo limitado para medição de conversão
- Exposição a fraudes: O valor da conversão em si não é assinado, os postbacks podem ser duplicados
Recursos da Singular
A Singular fornece recursos abrangentes para a implementação e otimização da SKAdNetwork.
- SKAN: Padrão prático para implementação de SKAdNetwork
- SKAdNetwork 101: O que isso significa para você
- Código SKAdNetwork: Repositório GitHub para redes, editores e anunciantes
- Como testar a SKAdNetwork: Instruções passo a passo
- Singular anuncia o primeiro suporte para substituição de IDFA no mercado
- Medição avançada usando SKAdNetwork: Desbloqueando ROAS
- SKAdNetwork 2.0 segura: Configuração perfeita do estabelecimento de confiança
Solução Singular SKAdNetwork
A solução SKAdNetwork da Singular fornece gerenciamento de atribuição de ponta a ponta, desde a implementação no lado do cliente até o processamento de postback e otimização da campanha.
Componentes da solução
Caraterísticas da plataforma
Suporte abrangente de SKAdNetwork em todo o fluxo de trabalho de atribuição e análise.
| Componente | Funcionalidade |
|---|---|
| Código do lado do cliente | Amostras de código iOS nativo para registro de estrutura SKAdNetwork e gerenciamento de valor de conversão |
| Processamento de postbacks | Validação e agregação de postbacks de todas as redes de anúncios com relatórios unificados |
| Proteção contra fraudes | Validação de assinatura criptográfica, deduplicação de ID de transação e verificação segura de parâmetros |
| Gestão de conversões | Configuração dinâmica do painel de controlo para codificar actividades pós-instalação em valores de conversão |
| Relatórios | Tradução de ID de campanha e enriquecimento com parâmetros de marketing para análise granular |
| Postbacks de parceiros | Valores de conversão descodificados enviados como eventos e receitas para otimização do parceiro |
Arquitetura de integração S2S
Componentes de implementação
A integração server-to-server da SKAdNetwork consiste em duas áreas principais de implementação.
1. Implementação no lado do cliente (obrigatório):
- Registo da estrutura SKAdNetwork no lançamento da aplicação
- Gestão inteligente do valor de conversão com base na atividade pós-instalação
- Essencial para a otimização de campanhas utilizando a atribuição de SKAdNetwork
2. Atualização da integração S2S (opcional):
- Enriquece os eventos e sessões do Singular S2S com metadados da SKAdNetwork
- Permite a validação da implementação e a resolução de problemas
- Fornece carimbos de data e hora do valor de conversão para depuração
Implementação no lado do cliente
Implemente o registro da estrutura SKAdNetwork e o gerenciamento do valor de conversão usando os exemplos de código iOS nativos do Singular para otimizar a medição da campanha.
Responsabilidades de implementação
Funcionalidade principal
O código do lado do cliente lida com duas funções críticas para a medição de SKAdNetwork.
- Registro de SKAdNetwork: Registra o aplicativo com a estrutura imediatamente após o lançamento para permitir a atribuição
-
Gerenciamento do valor de conversão:
- Comunica-se de forma síncrona com o endpoint Singular para receber o próximo valor de conversão
- Relata sessões, eventos e receita para a Singular
- Recebe o valor de conversão codificado que representa a atividade pós-instalação configurada
- Recolhe metadados SKAdNetwork (timestamp da primeira chamada, timestamp da última chamada, valor atual)
Interface SKAdNetwork
Definições de métodos
A interface inclui métodos para registo de framework, acompanhamento de sessões, acompanhamento de eventos e medição de receitas.
// 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 registo
registerAppForAdNetworkAttribution regista a aplicação com a estrutura SKAdNetwork, iniciando o temporizador de atribuição de 24 horas.
Comportamento do temporizador:
- O dispositivo envia uma notificação de instalação 0-24 horas após a expiração do temporizador
- A atualização do valor de conversão com um valor mais elevado reinicia o temporizador para um novo intervalo de 24 horas
- Referência da Apple: updateConversionValue Documentação
Métodos de valor de conversão
Os métodos calculam e actualizam os valores de conversão com base na atividade pós-instalação e no modelo de conversão configurado.
Actividades suportadas:
- Sessões: Crítico para a medição de retenção
- Eventos de conversão: Crítico para a medição de eventos pós-instalação
- Eventos de receita: Crítico para a medição de receitas
Implementação de interface
Código de implementação completo
A implementação completa lida com o registo, gestão de conversões e comunicação com a API 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
Gestão de metadados
Os métodos utilitários gerem o armazenamento de metadados da SKAdNetwork em UserDefaults, essencial para o cálculo do valor de conversão.
Implementação crítica: Estes utilitários mantêm os metadados necessários para o cálculo exato do valor de conversão. Não modificar, exceto se 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, melhora a integração servidor a servidor com metadados SKAdNetwork para validação da implementação e resolução de problemas.
Actualizações obrigatórias
Resposta do valor de conversão
Uma vez ativado o modelo de conversão, os endpoints S2S devolvem conversion_value campo inteiro contendo o próximo valor para atualização do lado do cliente.
Tratamento da resposta: Analisar conversion_valuea partir das respostas do ponto final S2S e aplicar à estrutura SKAdNetwork através de código do lado do cliente.
Melhorias opcionais
Parâmetros de metadados
Enriquecer os pedidos de sessões e eventos S2S com metadados SKAdNetwork para validar a integração e resolver problemas de implementação.
| Parâmetro | Descrição |
|---|---|
skan_conversion_value
|
Valor de conversão mais recente no momento do pedido |
skan_first_call_timestamp
|
Carimbo de data/hora Unix da primeira chamada à API SKAdNetwork |
skan_last_call_timestamp
|
Registo de data e hora Unix da última chamada à API SKAdNetwork |
Código de extração de metadados
Extrai valores de metadados SKAdNetwork utilizando métodos utilitários para 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
Reencaminhar parâmetros para o lado do servidor e anexar aos pedidos da API S2S Singular. Documentação completa dos parâmetros: Referência da API S2S
Fluxo de integração
Fluxo completo de SKAdNetwork para clientes S2S, desde a gestão da conversão no lado do cliente até ao processamento de postback.
Fases do fluxo
Processo de ponta a ponta
- Solicitação de valor de conversão: O código do aplicativo se comunica com o endpoint Singular de forma síncrona para obter o valor de conversão mais recente com base em sessões, eventos e receita
- Atualização do framework: o aplicativo atualiza o framework SKAdNetwork com o valor de conversão recebido
- Enriquecimento de metadados: A aplicação enriquece os eventos e sessões S2S com metadados SKAdNetwork para validação
- Expiração do temporizador: Após mais de 24 horas da última atualização, SKAdNetwork envia postback para a rede de anúncios
- Encaminhamento de postback: A rede encaminha o postback para a Singular (configuração segura ou regular)
-
Processamento do postback: A Singular processa o postback por
- Validação da assinatura criptográfica
- Decodificação do valor de conversão usando o modelo configurado
- Enriquecimento com parâmetros de marketing baseados em integrações de parceiros
- Envio de dados descodificados para o BI e parceiros através de postbacks
Separação de dados: Dados de SKAdNetwork (instalações e eventos descodificados) acessíveis através de relatórios separados, APIs, tabelas ETL e postbacks para evitar a mistura com conjuntos de dados existentes durante os testes e a validação.
Implementação do ciclo de vida da aplicação
Integre os métodos de SKAdNetwork nos pontos apropriados do ciclo de vida do aplicativo para obter uma cobertura completa da atribuição.
Exemplo de implementação
Colocação de 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 de implementação
Valide a implementação de SKAdNetwork usando o endpoint de teste da Singular antes da implementação na 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 aplicativo.
URL do ponto de extremidade de teste:
https://skadnetwork-testing.singular.net/api/v1/conversion_value
- Nenhuma chave de API necessária para o ponto de extremidade de teste
- Retorna valores de conversão 0-2 sequencialmente
- Devolve uma cadeia de caracteres vazia após a terceira chamada
Procedimento de teste
Etapas de validação
-
Atualizar o ponto final: Substituir a constante
SINGULAR_API_URLpelo URL do ponto de extremidade de teste - Inicializar valor de conversão: Garantir que o valor de conversão padrão seja inicializado em 0
- Gerar eventos: Acionar 3 eventos diferentes no aplicativo
-
Verificar atualizações: Confirmar
updateConversionValueAsyncchamado após cada evento - Registar valores: Registar os valores de conversão devolvidos para verificar a progressão correta (0 → 1 → 2)
- Confirmar a conclusão: Após a terceira chamada, verificar a resposta vazia e a retenção do valor final
Comportamento esperado:
- Primeiro evento: Recebe o valor de conversão 0
- Segundo evento: Recebe o valor de conversão 1
- Terceiro evento: Recebe o valor de conversão 2
- Quarto + eventos: Resposta vazia, o valor 2 persiste
Registro de alterações de código
Acompanhe as atualizações de exemplos de código e 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 |
|