Legado — SKAdNetwork 3. Esta guía cubre la implementación legada de SKAdNetwork 3.0 y se conserva únicamente para integraciones existentes. Para todas las implementaciones nuevas y actuales, utilice la Guía de implementación de SKAdNetwork 4 .
Guía de implementación S2S de SKAdNetwork 3.0 (Legado)
Implemente el framework SKAdNetwork 3.0 de Apple para la atribución en iOS que respeta la privacidad, utilizando una integración server-to-server para medir el rendimiento de las campañas de instalación de apps sin comprometer los identificadores de usuario.
Descripción general
¿Qué es SKAdNetwork?
SKAdNetwork es el framework de atribución de Apple que preserva la privacidad y permite medir las tasas de conversión de las campañas publicitarias de instalación de apps en iOS sin requerir identificadores a nivel de usuario.
El framework procesa la atribución a través de los servidores de la App Store, desvinculando la información de atribución de los identificadores de usuario y de los datos temporales antes de enviarla a las redes publicitarias.
Documentación de Apple: SKAdNetwork Framework Reference
Cómo funciona SKAdNetwork
El proceso de atribución ocurre en su totalidad a través de la infraestructura de Apple, garantizando la privacidad del usuario y permitiendo a la vez medir el rendimiento de las campañas.
Flujo de atribución:
- Clic en el anuncio: El usuario hace clic en un anuncio que contiene la firma de SKAdNetwork con la red, el publisher y el ID de campaña
- Apertura de la App Store: El dispositivo almacena los datos de atribución y abre la App Store para la instalación
- Lanzamiento de la app: El usuario instala e inicia la app por primera vez
- Registro: La app se registra con el framework SKAdNetwork para señalar una instalación exitosa
- Conversion Value: La app actualiza opcionalmente el conversion value (0-63) que representa la actividad posterior a la instalación
- Ventana del temporizador: El dispositivo espera más de 24 horas después del primer lanzamiento o de la última actualización del conversion value
- Postback: La App Store envía el postback de atribución a la red publicitaria con el ID de campaña, el conversion value y una firma criptográfica
Privacidad por diseño:
- El postback no contiene identificadores de dispositivo ni de usuario
- El retraso mínimo de 24 horas impide la correlación temporal
- La app nunca sabe en qué anuncio hizo clic el usuario
- La red nunca sabe qué usuario específico realizó la instalación
Capacidades y limitaciones
SKAdNetwork ofrece:
- Atribución de último clic: Funciona sin el consentimiento del usuario ni la aceptación de ATT
- Desglose de campañas: Granularidad por fuente, campaña y publisher
- Conversion values: 64 valores discretos (0-63) para la medición posterior a la instalación
- Prevención de fraude: Las firmas criptográficas validan la autenticidad de la atribución
Limitaciones actuales:
- Sin datos a nivel de usuario: No puede rastrear los recorridos individuales de los usuarios
- Sin view-through: Solo atribución basada en clics
- Conversion values limitados: Un único valor de 6 bits (0-63) por instalación
- Granularidad limitada: Máximo 100 IDs de campaña, sin desglose por grupo de anuncios ni por creativo
- Sin cohortes largas: Marco temporal limitado para la medición de conversiones
- Exposición al fraude: El conversion value en sí no está firmado, los postbacks pueden duplicarse
Recursos de Singular
Singular ofrece recursos completos para la implementación y la optimización de 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
Solución de SKAdNetwork de Singular
La solución de SKAdNetwork de Singular ofrece una gestión de atribución de extremo a extremo, desde la implementación del lado del cliente hasta el procesamiento de postbacks y la optimización de campañas.
Componentes de la solución
Funciones de la plataforma
Soporte completo de SKAdNetwork en todo el flujo de trabajo de atribución y analítica.
| Componente | Funcionalidad |
|---|---|
| Código del lado del cliente | Ejemplos de código iOS nativo para el registro con el framework SKAdNetwork y la gestión de conversion values |
| Procesamiento de postbacks | Validación y agregación de postbacks de todas las redes publicitarias con reportes unificados |
| Protección contra fraude | Validación de firma criptográfica, deduplicación de transaction ID y verificación segura de parámetros |
| Gestión de conversiones | Configuración dinámica en el dashboard para codificar las actividades posteriores a la instalación en conversion values |
| Reportes | Traducción del ID de campaña y enriquecimiento con parámetros de marketing para un análisis granular |
| Postbacks a partners | Conversion values decodificados enviados como eventos e ingresos para la optimización del partner |
Arquitectura de la integración S2S
Componentes de la implementación
La integración server-to-server de SKAdNetwork consta de dos áreas principales de implementación.
1. Implementación del lado del cliente (obligatoria):
- Registro con el framework SKAdNetwork al iniciar la app
- Gestión inteligente de conversion values basada en la actividad posterior a la instalación
- Esencial para la optimización de campañas mediante la atribución de SKAdNetwork
2. Actualización de la integración S2S (opcional):
- Enriquece los eventos y las sesiones S2S de Singular con metadatos de SKAdNetwork
- Permite la validación de la implementación y la resolución de problemas
- Proporciona marcas de tiempo del conversion value para la depuración
Implementación del lado del cliente
Implemente el registro con el framework SKAdNetwork y la gestión de conversion values utilizando los ejemplos de código iOS nativo de Singular para una medición óptima de campañas.
Responsabilidades de la implementación
Funcionalidad principal
El código del lado del cliente gestiona dos funciones críticas para la medición de SKAdNetwork.
- Registro con SKAdNetwork: Registra la app con el framework inmediatamente después del lanzamiento para habilitar la atribución
-
Gestión de conversion values:
- Se comunica de forma síncrona con el endpoint de Singular para recibir el próximo conversion value
- Reporta sesiones, eventos e ingresos a Singular
- Recibe un conversion value codificado que representa la actividad posterior a la instalación configurada
- Recopila metadatos de SKAdNetwork (marca de tiempo de la primera llamada, marca de tiempo de la última llamada, valor actual)
Interfaz de SKAdNetwork
Definiciones de métodos
La interfaz incluye métodos para el registro con el framework, el seguimiento de sesiones, el seguimiento de eventos y la medición de ingresos.
// 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 la app con el framework SKAdNetwork,
iniciando el temporizador de atribución de 24 horas.
Comportamiento del temporizador:
- El dispositivo envía la notificación de instalación entre 0 y 24 horas después de que expira el temporizador
- Actualizar el conversion value con un valor mayor reinicia el temporizador para un nuevo intervalo de 24 horas
- Referencia de Apple: updateConversionValue Documentation
Métodos de conversion value
Los métodos calculan y actualizan los conversion values según la actividad posterior a la instalación y el modelo de conversión configurado.
Actividades admitidas:
- Sesiones: Críticas para la medición de retención
- Eventos de conversión: Críticos para la medición de eventos posteriores a la instalación
- Eventos de ingresos: Críticos para la medición de ingresos
Implementación de la interfaz
Código completo de implementación
La implementación completa gestiona el registro, la gestión de conversiones y la comunicación con la API de 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 de utilidad
Gestión de metadatos
Los métodos de utilidad gestionan el almacenamiento de metadatos de SKAdNetwork en UserDefaults, lo cual es crítico para el cálculo del conversion value.
Implementación crítica: Estas utilidades mantienen los metadatos necesarios para un cálculo preciso del conversion value. No las modifique a menos que sea necesario.
+ (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
Actualización de la integración S2S
Opcionalmente, mejore la integración server-to-server con metadatos de SKAdNetwork para la validación de la implementación y la resolución de problemas.
Actualizaciones obligatorias
Respuesta del conversion value
Una vez activado el modelo de conversión, los endpoints S2S devuelven el campo
conversion_value
de tipo entero que contiene el próximo valor para la
actualización del lado del cliente.
Manejo de la respuesta:
Analice
conversion_value
de las respuestas del endpoint S2S y aplíquelo al framework SKAdNetwork mediante
el código del lado del cliente.
Mejoras opcionales
Parámetros de metadatos
Enriquezca las solicitudes de sesión y de evento S2S con metadatos de SKAdNetwork para validar la integración y resolver problemas de implementación.
| Parámetro | Descripción |
|---|---|
skan_conversion_value
|
Último conversion value en el momento de la solicitud |
skan_first_call_timestamp
|
Marca de tiempo Unix de la primera llamada a la API de SKAdNetwork |
skan_last_call_timestamp
|
Marca de tiempo Unix de la última llamada a la API de SKAdNetwork |
Código de extracción de metadatos
Extraiga los valores de metadatos de SKAdNetwork utilizando los métodos de utilidad para la transmisión 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
Reenvíe los parámetros al lado del servidor y añádalos a las solicitudes de la API S2S de Singular. Documentación completa de parámetros: S2S API Reference
Flujo de la integración
Flujo completo de SKAdNetwork para clientes S2S, desde la gestión de conversiones del lado del cliente hasta el procesamiento de postbacks.
Etapas del flujo
Proceso de extremo a extremo
- Solicitud de conversion value: El código de la app se comunica de forma síncrona con el endpoint de Singular para obtener el último conversion value según las sesiones, los eventos y los ingresos
- Actualización del framework: La app actualiza el framework SKAdNetwork con el conversion value recibido
- Enriquecimiento de metadatos: La app enriquece los eventos y las sesiones S2S con metadatos de SKAdNetwork para la validación
- Expiración del temporizador: Después de más de 24 horas desde la última actualización, SKAdNetwork envía el postback a la red publicitaria
- Reenvío del postback: La red reenvía el postback a Singular (configuración segura o regular)
-
Procesamiento del postback:
Singular procesa el postback
mediante:
- La validación de la firma criptográfica
- La decodificación del conversion value utilizando el modelo configurado
- El enriquecimiento con parámetros de marketing según las integraciones de partners
- El envío de los datos decodificados a BI y a partners mediante postbacks
Separación de datos: Los datos de SKAdNetwork (instalaciones y eventos decodificados) son accesibles a través de reportes, APIs, tablas ETL y postbacks separados para evitar mezclarlos con los conjuntos de datos existentes durante las pruebas y la validación.
Implementación en el ciclo de vida de la app
Integre los métodos de SKAdNetwork en los puntos apropiados del ciclo de vida de la app para una cobertura de atribución completa.
Ejemplo de implementación
Colocación de los 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);
}
}];
Prueba de la implementación
Valide la implementación de SKAdNetwork utilizando el endpoint de prueba de Singular antes del despliegue en producción.
Entorno de prueba
Configuración del endpoint de prueba
Reemplace el endpoint de gestión de conversiones de producción por el endpoint de prueba para simular el flujo de la app.
URL del endpoint de prueba:
https://skadnetwork-testing.singular.net/api/v1/conversion_value
- No se requiere API key para el endpoint de prueba
- Devuelve los conversion values de 0 a 2 de forma secuencial
- Devuelve una cadena vacía después de la tercera llamada
Procedimiento de prueba
Pasos de validación
-
Actualizar el endpoint:
Reemplace la constante
SINGULAR_API_URLpor la URL del endpoint de prueba - Inicializar el conversion value: Asegúrese de que el conversion value predeterminado se inicialice en 0
- Generar eventos: Active 3 eventos diferentes dentro de la app
-
Verificar las actualizaciones:
Confirme que
updateConversionValueAsyncse llame después de cada evento - Registrar los valores: Registre los conversion values devueltos para verificar la progresión correcta (0 → 1 → 2)
- Confirmar la finalización: Después de la tercera llamada, verifique la respuesta vacía y la retención del valor final
Comportamiento esperado:
- Primer evento: recibe el conversion value 0
- Segundo evento: recibe el conversion value 1
- Tercer evento: recibe el conversion value 2
- Cuarto evento y posteriores: respuesta vacía, el valor 2 persiste
Registro de cambios del código
Realice un seguimiento de las actualizaciones de los ejemplos de código y de las correcciones críticas aplicadas a lo largo del tiempo.
Historial de versiones
| Fecha | Cambios |
|---|---|
| 1 de octubre de 2020 |
|
| 23 de septiembre de 2020 |
|
| 15 de septiembre de 2020 |
|