SKAdNetwork 4 Disponible: Para conocer la última versión de SKAdNetwork, consulte la Guía de implementación de SKAdNetwork 4.
Guía de implementación de SKAdNetwork 3.0 S2S
Implemente el marco SKAdNetwork 3.0 de Apple para la atribución de iOS conforme a la privacidad mediante la integración de servidor a servidor para medir el rendimiento de la campaña de instalación de aplicaciones sin comprometer los identificadores de usuario.
Visión general
¿Qué es SKAdNetwork?
SKAdNetwork es el marco 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 aplicaciones en iOS sin necesidad de identificadores a nivel de usuario.
El marco procesa la atribución a través de los servidores de App Store, desconectando la información de atribución de los identificadores de usuario y los datos temporales antes de enviarla a las redes publicitarias.
Documentación de Apple: Referencia del marco SKAdNetwork
Cómo funciona SKAdNetwork
El proceso de atribución se realiza íntegramente a través de la infraestructura de Apple, lo que garantiza la privacidad del usuario al tiempo que permite medir el rendimiento de la campaña.
Flujo de atribución:
- Clic en el anuncio: El usuario hace clic en un anuncio que contiene la firma de SKAdNetwork con el ID de la red, del editor y de la 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 aplicación: El usuario instala e inicia la aplicación por primera vez.
- Registro: La aplicación se registra en el marco de SKAdNetwork para indicar que la instalación se ha realizado correctamente.
- Valor de conversión: La aplicación actualiza opcionalmente el valor de conversión (0-63) que representa la actividad posterior a la instalación.
- Ventana de temporizador: El dispositivo espera más de 24 horas tras el primer lanzamiento o la última actualización del valor de conversión.
- Postback: App Store envía la atribución a la red publicitaria con el ID de la campaña, el valor de conversión y la firma criptográfica.
Privacidad por diseño:
- El postback no contiene identificadores de dispositivo ni de usuario
- El retraso mínimo de 24 horas evita la correlación temporal
- La aplicación nunca sabe en qué anuncio ha hecho clic el usuario
- La red nunca sabe qué usuario específico instaló el anuncio
Capacidades y limitaciones
SKAdNetwork ofrece:
- Atribución de último clic: Funciona sin el consentimiento del usuario o ATT opt-in
- Desglose de campañas: Granularidad de fuente, campaña y editor
- Valores de conversión: 64 valores discretos (0-63) para la medición posterior a la instalación
- Prevención del fraude: Las firmas criptográficas validan la autenticidad de la atribución
Limitaciones actuales:
- Sin datos a nivel de usuario: No se puede realizar un seguimiento de los recorridos individuales de los usuarios
- No View-Through: Sólo atribución basada en clics
- Valores de conversión limitados: Un único valor de 6 bits (0-63) por instalación
- Granularidad limitada: Máximo 100 ID de campaña, sin desglose por grupo de anuncios o creatividad
- Sin cohortes largas: Marco temporal limitado para la medición de conversiones
- Exposición al fraude: El valor de conversión en sí no está firmado, los postbacks pueden duplicarse
Recursos de Singular
Singular proporciona recursos completos para la implementación y optimización de SKAdNetwork.
- SKAN: Estándar práctico para la implementación de SKAdNetwork
- SKAdNetwork 101: Qué significa para usted
- Código SKAdNetwork: Repositorio GitHub para redes, editores y anunciantes
- Cómo probar SKAdNetwork: Instrucciones paso a paso
- Singular anuncia la primera sustitución de IDFA en el mercado
- Medición avanzada con SKAdNetwork: Cómo desbloquear el ROAS
- SKAdNetwork 2.0 seguro: Establecimiento de la confianza sin fisuras
Solución Singular SKAdNetwork
La solución SKAdNetwork de Singular proporciona una gestión de la atribución de extremo a extremo, desde la implementación en el lado del cliente hasta el procesamiento postback y la optimización de la campaña.
Componentes de la solución
Características de la plataforma
Soporte completo de SKAdNetwork en todo el flujo de trabajo de atribución y análisis.
| Componente | Funcionalidad |
|---|---|
| Código cliente | Muestras de código nativo iOS para el registro del marco SKAdNetwork y la gestión del valor de conversión. |
| Procesamiento de postbacks | Validación y agregación de postbacks de todas las redes publicitarias con informes unificados |
| Protección contra el fraude | Validación de firmas criptográficas, deduplicación de ID de transacciones y verificación segura de parámetros |
| Gestión de conversiones | Configuración dinámica del panel de control para codificar las actividades posteriores a la instalación en valores de conversión |
| Informes | Traducción de ID de campaña y enriquecimiento con parámetros de marketing para un análisis granular |
| Postbacks de socios | Valores de conversión descodificados enviados como eventos e ingresos para la optimización de socios |
Arquitectura de integración S2S
Componentes de implementación
La integración de servidor a servidor de SKAdNetwork consta de dos áreas principales de implementación.
1.1. Implementación del lado del cliente (obligatoria):
- Registro del marco SKAdNetwork en el lanzamiento de la aplicación
- Gestión inteligente del valor de conversión basada en la actividad posterior a la instalación
- Esencial para la optimización de campañas utilizando la atribución SKAdNetwork
2. Actualización de integración S2S (Opcional):
- Enriquece los eventos y sesiones de Singular S2S con metadatos de SKAdNetwork
- Permite la validación de la implementación y la resolución de problemas
- Proporciona marcas de tiempo de valores de conversión para depuración
Implementación del lado del cliente
Implemente el registro del marco SKAdNetwork y la gestión del valor de conversión utilizando las muestras de código nativo iOS de Singular para una medición óptima de la campaña.
Responsabilidades de implementación
Funciones principales
El código del lado del cliente gestiona dos funciones críticas para la medición de SKAdNetwork.
- Registro SKAdNetwork: Registra la aplicación con el framework inmediatamente después del lanzamiento para permitir la atribución.
-
Gestión del valor de conversión:
- Se comunica de forma sincrónica con el punto final de Singular para recibir el siguiente valor de conversión.
- Informa de sesiones, eventos e ingresos a Singular.
- Recibe un valor de conversión codificado que representa la actividad posterior a la instalación configurada.
- Recoge metadatos de SKAdNetwork (marca de tiempo de la primera llamada, marca de tiempo de la última llamada, valor actual).
Interfaz SKAdNetwork
Definición de métodos
La interfaz incluye métodos para el registro de marcos, 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 aplicación en el marco SKAdNetwork, iniciando el temporizador de atribución de 24 horas.
Comportamiento del temporizador:
- El dispositivo envía una notificación de instalación 0-24 horas después de que expire el temporizador.
- La actualización del valor de conversión con un valor superior reinicia el temporizador para el nuevo intervalo de 24 horas.
- Referencia Apple: updateConversionValue Documentación
Métodos de valor de conversión
Los métodos calculan y actualizan los valores de conversión en función de la actividad posterior a la instalación y del modelo de conversión configurado.
Actividades admitidas:
- Sesiones: Crítico para la medición de la retención
- Eventos de conversión: Crítico para la medición de eventos post-instalación
- Eventos de ingresos: Críticos para la medición de ingresos
Implementación de la interfaz
Código de implementación completa
La implementación completa gestiona el registro, la gestión de conversiones y la comunicación Singular API.
// 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, crítico para el cálculo del valor de conversión.
Implementación crítica: Estas utilidades mantienen los metadatos necesarios para el cálculo preciso del valor de conversión. No modificar 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
Mejora opcional de la integración de servidor a servidor con los metadatos de SKAdNetwork para la validación de la implementación y la resolución de problemas.
Actualizaciones obligatorias
Respuesta del valor de conversión
Una vez activado el modelo de conversión, los puntos finales S2S devuelven el campo entero conversion_value que contiene el siguiente valor para la actualización del lado del cliente.
Tratamiento de la respuesta: Análisis de conversion_valuea partir de las respuestas de los puntos finales S2S y aplicación al marco SKAdNetwork mediante código del lado del cliente.
Mejoras opcionales
Parámetros de metadatos
Enriquezca las solicitudes de sesiones y eventos S2S con metadatos SKAdNetwork para validar la integración y solucionar problemas de implementación.
| Parámetro | Descripción |
|---|---|
skan_conversion_value
|
Último valor de conversión en el momento de la solicitud |
skan_first_call_timestamp
|
Fecha y hora 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
Extraer valores de metadatos SKAdNetwork utilizando 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
Transmitir los parámetros al lado del servidor y añadirlos a las solicitudes de la API S2S Singular. Documentación completa de los parámetros: Referencia de la API S2S
Flujo de integración
Flujo completo de SKAdNetwork para clientes S2S, desde la gestión de la conversión en el lado del cliente hasta el procesamiento posterior.
Etapas del flujo
Proceso de extremo a extremo
- Solicitud de valor de conversión: El código de la aplicación se comunica con el punto final de Singular de forma sincrónica para obtener el último valor de conversión basado en sesiones, eventos e ingresos.
- Actualización del marco : la aplicación actualiza el marco SKAdNetwork con el valor de conversión recibido.
- Enriquecimiento de metadatos: La aplicación enriquece los eventos y sesiones S2S con metadatos SKAdNetwork para su validación.
- Expiración del temporizador: Transcurridas más de 24 horas desde la última actualización, SKAdNetwork envía un postback a la red publicitaria.
- Reenvío de postbacks: La red reenvía el postback a Singular (configuración segura o normal)
-
Procesamiento de postbacks: Singular procesa el postback:
- Validación de la firma criptográfica
- Decodificación del valor de conversión utilizando el modelo configurado
- Enriquecimiento con parámetros de marketing basados en integraciones de socios
- Envío de los datos descodificados a BI y a los socios mediante postbacks
Separación de datos: Los datos de SKAdNetwork (instalaciones y eventos descodificados) accesibles a través de informes, API, tablas ETL y postbacks separados para evitar que se mezclen con los conjuntos de datos existentes durante las pruebas y la validación.
Implementación del ciclo de vida de la aplicación
Integre los métodos de SKAdNetwork en los puntos adecuados del ciclo de vida de la aplicación para una cobertura completa de la atribución.
Ejemplo de implementación
Colocación del método
// 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);
}
}];
Pruebas de implementación
Valide la implementación de SKAdNetwork utilizando el punto final de prueba de Singular antes de la implementación en producción.
Entorno de prueba
Configuración del endpoint de prueba
Sustituya el endpoint de gestión de conversión de producción por el endpoint de prueba para simular el flujo de la aplicación.
URL del endpoint de prueba:
https://skadnetwork-testing.singular.net/api/v1/conversion_value
- No se requiere clave API para el endpoint de prueba
- Devuelve los valores de conversión 0-2 secuencialmente
- Devuelve una cadena vacía tras la tercera llamada
Procedimiento de prueba
Pasos de validación
-
Actualizar punto final: Sustituir la constante
SINGULAR_API_URLpor la URL del endpoint de prueba - Inicializar valor de conversión: Asegurar que el valor de conversión por defecto se inicializa a 0
- Generar eventos: Activar 3 eventos diferentes dentro de la aplicación
-
Verificar actualizaciones: Confirmar
updateConversionValueAsyncllamada después de cada evento - Registrar valores: Registrar los valores de conversión devueltos para verificar la progresión correcta (0 → 1 → 2)
- Confirmar finalización: Después de la tercera llamada, verificar la respuesta vacía y la retención del valor final
Comportamiento esperado:
- Primer evento: Recibe el valor de conversión 0
- Segundo evento: Recibe valor de conversión 1
- Tercer evento: Recibe valor de conversión 2
- Cuarto+ eventos: Respuesta vacía, el valor 2 persiste
Registro de cambios del código
Seguimiento de las actualizaciones de muestras de código y 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 |
|