Guia de implementação SkadNetwork 3.0 S2S

Para implementar a versão mais recente da SKAdNetwork, consulte [BETA] SKAdNetwork 4.0 S2S Implementation.

 

Visão geral da SKAdNetwork

A SKAdNetwork é uma estrutura fornecida pela Apple para permitir a mensuração de campanhas publicitárias de instalação de aplicativos com privacidade no iOS(saiba mais). A estrutura ajuda a mensurar as taxas de conversão de campanhas de instalação de aplicativos sem comprometer os identificadores dos usuários.

Como funciona a SKAdNetwork?

Na SKAdNetwork, o processo de atribuição é conduzido pela App Store por meio dos servidores da Apple. As informações de atribuição são então desconectadas dos identificadores de usuários e das informações temporais e enviadas para a rede.

Screen_Shot_2020-09-16_at_18.57.56.png

Quando um anúncio é clicado e a loja é aberta, o aplicativo de publicação e a rede fornecem algumas informações básicas, como rede, editor e ID da campanha. Se o aplicativo do anunciante tiver sido iniciado e registrado na SKAdNetwork, o dispositivo enviará uma notificação de conversão bem-sucedida para a rede. Ele informará os valores anexados juntamente com um valor de conversão que pode ser informado pelo aplicativo anunciado.

Essa notificação será enviada pelo menos 24 horas após o primeiro lançamento e será desprovida de qualquer informação de identificação do dispositivo ou do usuário.

Além disso, a App Store conduz o processo para que o aplicativo anunciado não tenha conhecimento do anúncio e do editor originais. Dessa forma, a rede é notificada sobre uma instalação sem saber nada sobre o usuário que a instalou.

O que esperar ao usar a SKAdNetwork?

A SKAdNetwork tem algumas vantagens importantes. Ela fornece a você todas as informações a seguir:

  • Atribuição do último clique que funciona sem consentimento
  • Detalhamento de fontes, campanhas e editores
  • Valores de conversão pós-instalação (até 64 valores discretos)
  • Assinaturas criptográficas para validar instalações

No entanto, em sua forma atual, a SKAdNetwork é básica e requer implementação e coordenação cuidadosas entre várias entidades para garantir seu funcionamento.

Aqui estão algumas das limitações atuais da SKAdNetwork:

  • Não há dados no nível do usuário
  • Não há atribuição de visualização
  • Faixa limitada de valores de conversão:
    • Um único evento de conversão por instalação/reinstalação
    • Até 64 valores de conversão (6 bits)
  • Granularidade limitada:
    • Até 100 valores de campanha
    • Nenhuma representação para o grupo de anúncios e o nível criativo
  • Sem LTV / coortes longas
  • Exposição a fraudes:
    • O valor da conversão em si não é assinado (pode ser manipulado)
    • Os postbacks podem ser duplicados.

Para superar alguns desses problemas, a Singular lançou um padrão público para a implementação da SKAdNetwork, bem como várias postagens de blog que podem ajudá-lo a navegar pela SKAdNEtwork:

Implementação da SKAdNetwork S2S

A solução SKAdNetwork Singular consiste nos seguintes componentes:

  • Código do lado do cliente para implementar a SKAdNetwork
  • Validação e agregação de postback de todas as redes
  • Proteção contra fraudes:
    • Validação de assinatura e redução de ID de transação.
    • Configuração segura com redes para verificar parâmetros que não são assinados (valor de conversão e geodados).
  • Gerenciamento do valor de conversão: oferece a capacidade de configurar dinamicamente no painel da Singular qual atividade pós-instalação deve ser codificada no valor de conversão da SKAdNetwork.
  • Relatórios: Traduzir o ID limitado da campanha SKAdNetwork e enriquecer os dados com mais parâmetros e granularidades de marketing.
  • Postbacks de parceiros: Fornece o envio de postbacks de SKAdNetwork com um valor de conversão decodificado em eventos e receita, o que é fundamental para sua otimização.

Para clientes que usam uma integração Singular de servidor para servidor, a implementação da SKAdNetwork consiste em duas partes principais:

  1. Implementação da SKAdNetwork no lado do cliente: Essa parte é fundamental para registrar seus aplicativos na SKAdNetwork e gerenciar o valor de conversão da SKAdNetwork de forma inteligente. Isso significa que, ao implementá-la, você poderá otimizar suas campanhas com base nas atribuições de SKAdNetwork e nas atividades pós-instalação associadas.
  2. Atualização da integração S2S (opcional): Essa parte é importante para validar e solucionar problemas da implementação no lado do cliente. Ao enriquecer eventos e sessões enviados para o Singular hoje por meio da sessão S2S do Singular e endpoints de eventos com dados da SKAdNetwork (valores de conversão e carimbos de data/hora de atualização), o Singular pode validar se a implementação foi feita corretamente no lado do seu aplicativo.

Implementação do lado do cliente da SKAdNetwork

A Singular fornece exemplos de código que suportam o registro para SKAdNetwork e o gerenciamento do valor de conversão. Esses exemplos de código são responsáveis pelas seguintes partes:

  1. Suporte e registro da SKAdNetwork
  2. Gerenciamento do valor de conversão:
    • O código se comunica de forma síncrona com um endpoint da Singular para receber o próximo valor de conversão com base no modelo de conversão configurado. Ele relata eventos/sessões/receita e, em resposta, obtém o próximo valor de conversão, que é um número codificado que representa a atividade pós-instalação que foi configurada para medição no painel do Singular.
    • O código também coleta metadados da SKAdnetwork que são usados para validação e cálculo do valor da próxima conversão:
      • O registro de data e hora da primeira chamada para a estrutura SKAdNetwork subjacente
      • O registro de data e hora da última chamada para a estrutura SKAdNetwork subjacente
      • O último valor de conversão atualizado

Atualização da integração S2S

Obrigatório

Quando você tiver um modelo de conversão ativo, nossos pontos de extremidade S2S começarão a retornar um novo campo int chamado "conversion_value", que conterá o próximo valor a ser atualizado no código do lado do cliente.

Opcional

Para validar a integração e solucionar possíveis problemas de implementação, recomendamos usar nossos exemplos de código para estender sua integração S2S atual. Os pontos de extremidade de sessão e evento S2S da Singular já suportam a obtenção de parâmetros SKAdNetwork adicionais, como:

  • Valor de conversão mais recente
  • Último registro de data e hora de uma chamada para a estrutura SKAdNetwork subjacente
  • Primeiro registro de data e hora de uma chamada para a estrutura SKAdNetwork subjacente

Fluxo de integração

Screen_Shot_2020-09-16_at_18.59.13.png

O diagrama acima ilustra o fluxo da SKAdNetwork para um cliente S2S:

  1. Primeiro, o código no aplicativo se comunica com um servidor Singular (por meio de um endpoint dedicado para SKAdNetwork) para obter o valor de conversão mais recente de forma síncrona com base em eventos/sessões/receitas que ocorrem no aplicativo e atualizar a estrutura de SKAdNetwork com esse valor.
  2. Em segundo lugar, o aplicativo enriquece os eventos e sessões existentes com dados da SKAdNetwork, que serão usados posteriormente para validações.
  3. Quando o aplicativo terminar de atualizar a estrutura de SKAdNetwork com novos valores de conversão e o cronômetro de SKAdNetwork expirar, o postback de SKAdNetwork será enviado para a rede.
  4. A rede o encaminhará para a Singular (por meio da configuração segura ou da configuração normal).
  5. A Singular processará o postback da seguinte forma
    • Validando sua assinatura
    • Decodificação do valor de conversão com base no modelo de conversão configurado
    • Enriquecer o postback com mais parâmetros de marketing voltados para o exterior com base na integração com parceiros e no ID de campanha interna da SKAdNetwork
    • Envio dos postbacks decodificados ao BI e aos parceiros

Uma observação importante é que as informações da SKAdNetwork, incluindo instalações e eventos decodificados, poderão ser acessadas por meio de um conjunto diferente de relatórios/APIs/tabelas ETL e postbacks para evitar misturá-las com seus conjuntos de dados existentes. Isso é especialmente importante durante as próximas semanas para permitir que você meça e teste a SKAdNetwork lado a lado com sua campanha existente.

Amostras de código iOS nativo de SKAdNetwork

Interface de SKAdNetwork

Essa interface inclui os seguintes componentes de SKAdNetwork:

Registro na SKAdNetwork:

  • Esse método é responsável pelo registro do seu aplicativo na SKAdNetwork. O método subjacente da API da Apple gera uma notificação se o dispositivo tiver dados de atribuição para esse aplicativo e inicia um cronômetro de 24 horas.
  • O dispositivo envia a notificação de instalação para o endpoint de postback da rede de anúncios dentro de 0 a 24 horas após a expiração do cronômetro.
  • Observe que, se você atualizar o valor de conversão com um valor maior do que o anterior, ele redefinirá o primeiro cronômetro para um novo intervalo de 24 horas(leia mais aqui).

Gerenciamento e atualizações do valor de conversão:

  • O valor de conversão é calculado com base na atividade pós-instalação de um dispositivo que é capturado pelos métodos abaixo e em um modelo de conversão selecionado que pode ser configurado dinamicamente pelo usuário.
  • Os métodos nesta seção são responsáveis por extrair o próximo valor de conversão do endpoint da Singular de acordo com o modelo de conversão selecionado e a atividade pós-instalação relatada (consulte a documentação acima).
  • Os métodos abaixo atualizam o valor de conversão com base nas seguintes atividades pós-instalação:
      • Sessão: essencial para a medição da retenção de um usuário com SKAdNetwork
      • Conversion Event (Evento de conversão): essencial para a medição do evento de conversão pós-instalação com SKAdNetwork
      • Evento de receita: Crítico para a medição de receita com SKAdNetwork
//  SKANSnippet.h
  
  #import <Foundation/Foundation.h>
  
  @interface
  SKANSnippet : NSObject
  
  // Registre-se para atribuição SkAdNetwork. Você deve chamar esse método o mais rápido possível assim que o aplicativo for iniciado.
  + (void)registerAppForAdNetworkAttribution;
  
  /* Para rastrear retenção e coortes, você precisa chamar esse método após cada sessão. Ele relata os detalhes da sessão e atualiza o valor de conversão devido a esta sessão, se necessário. O valor de conversão será atualizado somente se o novo valor for maior que o valor anterior. O retorno de chamada passado para o método é opcional, você pode usá-lo para executar o código assim que o valor de conversão for atualizado. */
  + (void)updateConversionValueAsync:(void(^)(int, NSError*))handler;
  
  /* Para rastrear eventos de conversão com SKAdNetwork você precisa chamar esse método após cada evento. Ele relata os detalhes do evento e atualiza o valor da conversão devido a esse evento, se necessário. O retorno de chamada passado para o método é opcional, você pode usá-lo para executar o código assim que o valor de conversão for atualizado.*/
  + (void)updateConversionValueAsync:(NSString*)eventName
withCompletionHandler:(void(^)(int, NSError*))handler; /* Para acompanhar a receita com SKAdNetwork você precisa chamar esse método antes de cada evento de receita. Ele atualizará a receita total, portanto, quando você chamar 'updateConversionValueAsync', o novo valor de conversão será determinado de acordo com o valor total da receita.
Observação:
1. Chame esse método antes de chamar 'updateConversionValueAsync' para garantir que a receita seja atualizada.
2. No caso de tentar novamente um evento, evite chamar este método para que a mesma receita não seja contabilizada duas vezes.*/ + (void)updateRevenue:(double)amount andCurrency:(NSString*)currency; // Obtém o valor de conversão atual (nulo se nenhum) + (NSNumber *)getConversionValue; @end

Implementação da interface SKAdNetwork

//  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"
  
  // Chaves para armazenamento UserDefaults
  #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
  
  @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 = @"";
          
          components.queryItems = @[
              [NSURLQueryItem queryItemWithName:@"a" value:@""],
              [NSURLQueryItem queryItemWithName:@"i" value:bundleIdentifier],
              [NSURLQueryItem queryItemWithName:@"app_v" value:@""],
              [NSURLQueryItem queryItemWithName:@"n" value:eventName],
              [NSURLQueryItem queryItemWithName:@"p" value:@"iOS"],
              [NSURLQueryItem queryItemWithName:@"idfv" value:@""],
              [NSURLQueryItem queryItemWithName:@"idfa" value:@""],
              [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; }

Metadados e métodos de utilitários da SKAdNetwork

Esta seção é responsável pela implementação de utilitários que estão sendo usados na implementação acima. Observe que esses utilitários são essenciais para manter e armazenar os metadados necessários para calcular os próximos valores de conversão.

Os seguintes utilitários são implementados:

Salvar valores em UserDefaults (e recuperá-los):

  • Registro de data e hora da primeira chamada para a API SKAdNetwork subjacente
  • Registro de data e hora da última chamada para a API SKAdNetwork subjacente
  • Receita total por moeda

Conversão de valores entre diferentes representações:

  • Dicionário para string JSON
  • JSON para dicioná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 - Implementação (opcional)

Atualize sua integração S2S com os seguintes metadados da SKAdNetwork (esses metadados devem ser encaminhados em todas as sessões e eventos relatados à Singular para validação da implementação da SKAdNetwork):

  • skan_conversion_value - O valor de conversão mais recente
  • skan_first_call_timestamp - Registro de data e hora Unix da primeira chamada à API SkAdNetwork subjacente
  • skan_last_call_timestamp - Carimbo de data/hora Unix da última chamada à API SkAdNetwork subjacente

O trecho de código a seguir mostra como extrair esses valores:

NSDictionary *values = @{
      @"skan_conversion_value":
        [[SKANSnippet getConversionValue] stringValue]],
      @"skan_first_call_timestamp": 
[NSString stringWithFormat:@"%ld", [SKANSnippet getFirstSkanCallTimestamp]], @"skan_last_call_timestamp":
[NSString stringWithFormat:@"%ld", [SKANSnippet getLastSkanCallTimestamp]] };

Agora, depois de enviar esses parâmetros para o seu servidor, você pode encaminhá-los por meio de nossos endpoints de API de servidor para servidor. Para saber mais, pesquise esses parâmetros em nossa referência de API server-to-server.

Exemplo de fluxo do ciclo de vida do aplicativo

// No lançamento do aplicativo
  [SKANSnippet registerAppForAdNetworkAttribution];
  
  // Após cada sessão ser tratada
  [SKANSnippet updateConversionValueAsync:handler];
  
  // Depois de lidar com eventos sem fins lucrativos
  [SKANSnippet updateConversionValueAsync:@"event_name" 
withCompletionHandler:handler]; // Depois de lidar com eventos de receita [SKANSnippet updateRevenue:15.53 andCurrency:@"KRW"]; [SKANSnippet updateConversionValueAsync:@"revenue_event_name" withCompletionHandler:handler];

Registro de alterações de código

  • 1 de outubro de 2020
    • Correção [IMPORTANTE]:o mecanismo de bloqueio em `getConversionValueFromServer`não foi inicializado corretamente
    • Aprimoramento: retorno do motivo de falha da resposta do nosso servidor
  • 23 de setembro de 2020
    • Correção [IMPORTANTE]:`setConversionValue` agora chama `updateConversionValue`
    • Aprimorado: tratamento de erros ao recuperar o valor de conversão mais recente do servidor
  • 15 de setembro de 2020
    • Alterado [IMPORTANTE]: Se o valor de conversão não for definido, o valor padrão retornado por `getConversionValue` será 0 em vez de nulo
    • Aprimorado: Relatório e tratamento de receita
    • Aprimorado: Recuperação do próximo valor de conversão de forma assíncrona

Teste de sua implementação

Gerenciamento de conversões

Para simular o fluxo do seu aplicativo e testar sua implementação, substitua o endpoint de produção do gerenciamento de conversões(SINGULAR_API_URL) pelo seguinte endpoint de teste -

    • https://skadnetwork-testing.singular.net/api/v1/conversion_value
    • Nenhuma chave de API é necessária

Um fluxo de teste sugerido:

  • Gere 3 eventos diferentes em seu aplicativo.
      • Se tudo estiver implementado corretamente
        • Após cada evento, updateConversionValueAsync deve ser chamado (certifique-se de que o valor de conversão padrão seja inicializado em 0).
        • O ponto de extremidade de teste recebe o valor de conversão atual do aplicativo e retorna o próximo valor.
    • Registre o valor de conversão retornado para verificar se tudo está funcionando conforme o esperado.
      • O ponto de extremidade de teste é implementado para retornar valores entre 0 e 2.
      • Portanto, após a terceira chamada, ele retornará uma cadeia de caracteres vazia, e o último valor será usado como o valor final de conversão da SKAdNetwork.