Validar callbacks de verificação do lado do servidor (SSV, na sigla em inglês)

Os callbacks de verificação no lado do servidor são solicitações de URL, com parâmetros de consulta expandidos pelo Google, que são enviados pelo Google a um sistema externo para notificar que um usuário deve ser recompensado por interagir com um anúncio intersticial premiado. Os callbacks de SSV (verificação do lado do servidor) premiados oferecem uma camada extra de proteção contra spoofing de callbacks no lado do cliente para recompensar os usuários.

Este guia mostra como verificar callbacks de SSV recompensados usando a biblioteca criptográfica de terceiros Tink Java Apps para garantir que os parâmetros de consulta no callback sejam valores legítimos. Embora a Tink seja usada para fins deste guia, você tem a opção de usar qualquer biblioteca de terceiros compatível com ECDSA. Também é possível testar o servidor com a ferramenta de teste na interface da AdMob.

Pré-requisitos

Usar o RewardedAdsVerifier da biblioteca Tink Java Apps

O repositório do GitHub dos apps Java do Tink inclui uma classe auxiliar RewardedAdsVerifier para reduzir o código necessário para verificar um callback de SSV recompensado. O uso dessa classe permite verificar um URL de callback com o código abaixo.

RewardedAdsVerifier verifier = new RewardedAdsVerifier.Builder()
    .fetchVerifyingPublicKeysWith(
        RewardedAdsVerifier.KEYS_DOWNLOADER_INSTANCE_PROD)
    .build();
String rewardUrl = ...;
verifier.verify(rewardUrl);

Se o método verify() for executado sem gerar uma exceção, o URL do callback será verificado. A seção Recompensar o usuário descreve as práticas recomendadas sobre quando os usuários devem ser recompensados. Para um detalhe das etapas realizadas por essa classe para verificar callbacks de SSV premiados, leia a seção Verificação manual de SSV premiada.

Parâmetros de callback de SSV

Os callbacks de verificação no lado do servidor contêm parâmetros de consulta que descrevem a interação com o anúncio premiado. Os nomes, descrições e valores de exemplo dos parâmetros estão listados abaixo. Os parâmetros são enviados em ordem alfabética.

Nome do parâmetro Descrição Valor de exemplo
ad_network Identificador da origem de anúncios que atendeu a solicitação. Os nomes das fontes de anúncios correspondentes aos valores de ID são listados na seção Identificadores de fonte de anúncios. 1953547073528090325
ad_unit ID do bloco de anúncios da AdMob usado para solicitar o anúncio premiado. 2747237135
custom_data String de dados personalizada fornecida por customRewardString.

Se nenhuma string de dados personalizada for fornecida pelo app, esse valor de parâmetro de consulta não vai estar presente no callback da SSV.

SAMPLE_CUSTOM_DATA_STRING
key_id Chave a ser usada para verificar a callback de SSV. Esse valor é associado a uma chave pública fornecida pelo servidor de chaves do AdMob. 1234567890
reward_amount Valor da recompensa especificado nas configurações do bloco de anúncios. 5
reward_item Item de recompensa especificado nas configurações do bloco de anúncios. moedas
assinatura Assinatura do callback de SSV gerado pela AdMob. MEUCIQCLJS_s4ia_sN06HqzeW7Wc3nhZi4RlW3qV0oO-6AIYdQIgGJEh-rzKreO-paNDbSCzWGMtmgJHYYW9k2_icM9LFMY
timestamp Carimbo de data/hora de quando o usuário recebeu a recompensa como Epoch time em ms. 1507770365237823
transaction_id Identificador exclusivo codificado em hexadecimal para cada evento de concessão de recompensa gerado pela AdMob. 18fa792de1bca816048293fc71035638
user_id Identificador do usuário fornecido por userIdentifier.

Se nenhum identificador de usuário for fornecido pelo app, esse parâmetro de consulta não estará presente no callback da SSV.

1234567

Identificadores da origem de anúncios

Nomes e IDs da origem de anúncios

Nome da origem do anúncio ID da origem de anúncios
Aarki (lances)5240798063227064260
Geração de anúncios (lances)1477265452970951479
AdColony15586990674969969776
AdColony (sem SDK) (Lances)4600416542059544716
AdColony (lances)6895345910719072481
AdFalcon3528208921554210682
Rede da AdMob5450213213286189855
Hierarquia da rede da AdMob1215381445328257950
ADResult10593873382626181482
AMoAd17253994435944008978
AppLovin1063618907739174004
AppLovin (lances)1328079684332308356
Chartboost2873236629771172317
Chocolate Platform (lances)6432849193975106527
CrossChannel (MdotM)9372067028804390441
Evento personalizado18351550913290782395
DT Exchange*
* Antes de 21 de setembro de 2022, essa rede se chamava "Fyber Marketplace".
2179455223494392917
EMX (lances)8497809869790333482
Fluct (lances)8419777862490735710
Flurry3376427960656545613
Fyber*
* Essa origem de anúncios é usada para relatórios históricos.
4839637394546996422
i-mobile5208827440166355534
Improve Digital (lances)159382223051638006
Index Exchange (lances)4100650709078789802
InMobi7681903010231960328
InMobi (lances)6325663098072678541
InMobi Exchange (lances)5264320421916134407
IronSource6925240245545091930
ironSource Ads (lances)1643326773739866623
Leadbolt2899150749497968595
LG U+AD18298738678491729107
LINE Ads Network3025503711505004547
maio7505118203095108657
maio (lances)1343336733822567166
Media.net (lances)2127936450554446159
Anúncios internos mediados6060308706800320801
Audience Network da Meta*
* Antes de 6 de junho de 2022, essa rede se chamava "Audience Network do Facebook".
10568273599589928883
Audience Network da Meta (lances)*
* Antes de 6 de junho de 2022, essa rede se chamava "Audience Network do Facebook (lances)".
11198165126854996598
Mintegral1357746574408896200
Mintegral (lances)6250601289653372374
MobFox8079529624516381459
MobFox (lances)3086513548163922365
MoPub (descontinuado)10872986198578383917
myTarget8450873672465271579
Nend9383070032774777750
Nexxen (lances)*

* Antes de 1º de maio de 2024, essa rede se chamava "UnrulyX".

2831998725945605450
ONE by AOL (Millennial Media)6101072188699264581
ONE by AOL (Nexage)3224789793037044399
OneTag Exchange (lances)4873891452523427499
OpenX (lances)4918705482605678398
Pangle4069896914521993236
Pangle (lances)3525379893916449117
PubMatic (lances)3841544486172445473
Campanha de reserva7068401028668408324
RhythmOne (lances)2831998725945605450
Rubicon (lances)3993193775968767067
SK planet734341340207269415
Sharethrough (lances)5247944089976324188
Smaato (lances)3362360112145450544
Equativ (lances)*

* Antes de 12 de janeiro de 2023, essa rede se chamava "Adserver inteligente".

5970199210771591442
Sonobi (lances)3270984106996027150
Tapjoy7295217276740746030
Tapjoy (lances)4692500501762622178
Tencent GDT7007906637038700218
TripleLift (lances)8332676245392738510
Unity Ads4970775877303683148
Unity Ads (lances)7069338991535737586
Verizon Media7360851262951344112
Verve Group (lances)5013176581647059185
Vpon1940957084538325905
Liftoff Monetize*

* Antes de 30 de janeiro de 2023, essa rede se chamava "Vungle".

1953547073528090325
Liftoff Monetize (lances)*

* Antes de 30 de janeiro de 2023, essa rede se chamava "Vungle (bidding)".

4692500501762622185
Yieldmo (lances)4193081836471107579
YieldOne (lances)3154533971590234104
Zucks5506531810221735863

Como recompensar o usuário

É importante equilibrar a experiência do usuário e a validação de recompensa ao decidir quando recompensar um usuário. Os callbacks do lado do servidor podem sofrer atrasos antes de alcançar sistemas externos. Portanto, a prática recomendada é usar o callback do lado do cliente para recompensar o usuário imediatamente, enquanto realiza a validação em todas as recompensas após o recebimento de callbacks do lado do servidor. Essa abordagem oferece uma boa experiência do usuário e garante a validade dos prêmios concedidos.

No entanto, para aplicativos em que a validade da recompensa é essencial (por exemplo, quando a recompensa afeta a economia do jogo do app) e os atrasos na concessão de recompensas são aceitáveis, esperar pelo callback verificado do lado do servidor pode ser a melhor abordagem.

Dados personalizados

Os apps que exigem dados extras em callbacks de verificação do lado do servidor precisam usar o recurso de dados personalizados dos anúncios premiados. Qualquer valor de string definido em um objeto de anúncio premiado é transmitido para o parâmetro de consulta custom_data do callback de SSV. Se nenhum valor de dados personalizado for definido, o valor do parâmetro de consulta custom_data não será presente no callback de SSV.

O exemplo a seguir define as opções de SSV depois que o anúncio premiado é carregado:

Swift

GADRewardedAd.load(withAdUnitID:"AD_UNIT_ID",
                       request: request,
                       completionHandler: { [self] ad, error in
      if let error != error {
      rewardedAd = ad
      let options = GADServerSideVerificationOptions()
      options.customRewardString = "SAMPLE_CUSTOM_DATA_STRING"
      rewardedAd.serverSideVerificationOptions = options
    }

Objective-C

GADRequest *request = [GADRequest request];
[GADRewardedAd loadWithAdUnitID:@"AD_UNIT_ID"
                        request:request
              completionHandler:^(GADRewardedAd *ad, NSError *error) {
                if (error) {
                  // Handle Error
                  return;
                }
                self.rewardedAd = ad;
                GADServerSideVerificationOptions *options =
                    [[GADServerSideVerificationOptions alloc] init];
                options.customRewardString = @"SAMPLE_CUSTOM_DATA_STRING";
                ad.serverSideVerificationOptions = options;
              }];

Verificação manual de SSV recompensada

As etapas realizadas pela classe RewardedAdsVerifier para verificar uma SSV premiada estão descritas abaixo. Embora os snippets de código incluídos estejam em Java e aproveitem a biblioteca de terceiros Tink, essas etapas podem ser implementadas no idioma de sua escolha, usando qualquer biblioteca de terceiros compatível com ECDSA.

Buscar chaves públicas

Para verificar um callback de SSV recompensado, você precisa de uma chave pública fornecida pelo AdMob.

Uma lista de chaves públicas a serem usadas para validar os callbacks de SSV recompensados pode ser buscada no servidor de chaves da AdMob. A lista de chaves públicas é fornecida como uma representação JSON com um formato semelhante a este:

{
 "keys": [
    {
      keyId: 1916455855,
      pem: "-----BEGIN PUBLIC KEY-----\nMF...YTPcw==\n-----END PUBLIC KEY-----"
      base64: "MFkwEwYHKoZIzj0CAQYI...ltS4nzc9yjmhgVQOlmSS6unqvN9t8sqajRTPcw=="
    },
    {
      keyId: 3901585526,
      pem: "-----BEGIN PUBLIC KEY-----\nMF...aDUsw==\n-----END PUBLIC KEY-----"
      base64: "MFYwEAYHKoZIzj0CAQYF...4akdWbWDCUrMMGIV27/3/e7UuKSEonjGvaDUsw=="
    },
  ],
}

Para recuperar as chaves públicas, conecte-se ao servidor de chaves da AdMob e faça o download das chaves. O código abaixo realiza essa tarefa e salva a representação JSON das chaves na variável data.

String url = ...;
NetHttpTransport httpTransport = new NetHttpTransport.Builder().build();
HttpRequest httpRequest =
    httpTransport.createRequestFactory().buildGetRequest(new GenericUrl(url));
HttpResponse httpResponse = httpRequest.execute();
if (httpResponse.getStatusCode() != HttpStatusCodes.STATUS_CODE_OK) {
  throw new IOException("Unexpected status code = " + httpResponse.getStatusCode());
}
String data;
InputStream contentStream = httpResponse.getContent();
try {
  InputStreamReader reader = new InputStreamReader(contentStream, UTF_8);
  data = readerToString(reader);
} finally {
  contentStream.close();
}

As chaves públicas são trocadas regularmente. Você vai receber um e-mail informando sobre a próxima rotação. Se você estiver armazenando em cache chaves públicas, atualize as chaves ao receber este e-mail.

Depois que as chaves públicas forem buscadas, elas precisarão ser analisadas. O método parsePublicKeysJson abaixo recebe uma string JSON, como o exemplo acima, como entrada e cria um mapeamento de valores key_id para chaves públicas, que são encapsuladas como objetos ECPublicKey da biblioteca Tink.

private static Map<Integer, ECPublicKey> parsePublicKeysJson(String publicKeysJson)
    throws GeneralSecurityException {
  Map<Integer, ECPublicKey> publicKeys = new HashMap<>();
  try {
    JSONArray keys = new JSONObject(publicKeysJson).getJSONArray("keys");
    for (int i = 0; i < keys.length(); i++) {
      JSONObject key = keys.getJSONObject(i);
      publicKeys.put(
          key.getInt("keyId"),
          EllipticCurves.getEcPublicKey(Base64.decode(key.getString("base64"))));
    }
  } catch (JSONException e) {
    throw new GeneralSecurityException("failed to extract trusted signing public keys", e);
  }
  if (publicKeys.isEmpty()) {
    throw new GeneralSecurityException("No trusted keys are available.");
  }
  return publicKeys;
}

Receber conteúdo para verificação

Os dois últimos parâmetros de consulta de callbacks de SSV premiados são sempre signature e key_id, nessa ordem. Os parâmetros de consulta restantes especificam o conteúdo a ser verificado. Vamos supor que você configurou a AdMob para enviar callbacks de recompensa para https://www.myserver.com/mypath. O snippet abaixo mostra um exemplo de callback de SSV recompensado com o conteúdo a ser verificado destacado.

https://www.myserver.com/path?ad_network=54...55&ad_unit=12345678&reward_amount=10&reward_item=coins
&timestamp=150777823&transaction_id=12...DEF&user_id=1234567&signature=ME...Z1c&key_id=1268887

O código abaixo demonstra como analisar o conteúdo a ser verificado de um URL de callback como uma matriz de bytes UTF-8.

public static final String SIGNATURE_PARAM_NAME = "signature=";
...
URI uri;
try {
  uri = new URI(rewardUrl);
} catch (URISyntaxException ex) {
  throw new GeneralSecurityException(ex);
}
String queryString = uri.getQuery();
int i = queryString.indexOf(SIGNATURE_PARAM_NAME);
if (i == -1) {
  throw new GeneralSecurityException("needs a signature query parameter");
}
byte[] queryParamContentData =
    queryString
        .substring(0, i - 1)
        // i - 1 instead of i because of & in the query string
        .getBytes(Charset.forName("UTF-8"));

Receber assinatura e key_id do URL de callback

Usando o valor queryString da etapa anterior, analise os parâmetros de consulta signature e key_id do URL de callback, conforme mostrado abaixo:

public static final String KEY_ID_PARAM_NAME = "key_id=";
...
String sigAndKeyId = queryString.substring(i);
i = sigAndKeyId.indexOf(KEY_ID_PARAM_NAME);
if (i == -1) {
  throw new GeneralSecurityException("needs a key_id query parameter");
}
String sig =
    sigAndKeyId.substring(
        SIGNATURE_PARAM_NAME.length(), i - 1 /* i - 1 instead of i because of & */);
int keyId = Integer.valueOf(sigAndKeyId.substring(i + KEY_ID_PARAM_NAME.length()));

Fazer a verificação

A etapa final é verificar o conteúdo do URL de callback com a chave pública apropriada. Use o mapeamento retornado pelo método parsePublicKeysJson e o parâmetro key_id do URL de callback para acessar a chave pública desse mapeamento. Em seguida, verifique a assinatura com essa chave pública. Essas etapas são demonstradas abaixo no método verify.

private void verify(final byte[] dataToVerify, int keyId, final byte[] signature)
    throws GeneralSecurityException {
  Map<Integer, ECPublicKey> publicKeys = parsePublicKeysJson();
  if (publicKeys.containsKey(keyId)) {
    foundKeyId = true;
    ECPublicKey publicKey = publicKeys.get(keyId);
    EcdsaVerifyJce verifier = new EcdsaVerifyJce(publicKey, HashType.SHA256, EcdsaEncoding.DER);
    verifier.verify(signature, dataToVerify);
  } else {
    throw new GeneralSecurityException("cannot find verifying key with key ID: " + keyId);
  }
}

Se o método for executado sem gerar uma exceção, o URL de callback será verificado.

Perguntas frequentes

Posso armazenar em cache a chave pública fornecida pelo servidor de chaves da AdMob?
Recomendamos armazenar em cache a chave pública fornecida pelo servidor de chaves da AdMob para reduzir o número de operações necessárias para validar callbacks de SSV. No entanto, as chaves públicas são rotacionadas regularmente e não podem ser armazenadas em cache por mais de 24 horas.
Com que frequência as chaves públicas fornecidas pelo servidor de chaves da AdMob são trocadas?
As chaves públicas fornecidas pelo servidor de chaves da AdMob são alternadas em uma programação variável. Para garantir que a verificação de callbacks de SSV continue funcionando corretamente, as chaves públicas não podem ser armazenadas em cache por mais de 24 horas.
O que acontece se não for possível acessar meu servidor?
O Google espera um código de resposta de status de sucesso HTTP 200 OK para callbacks de SSV. Se não for possível acessar seu servidor ou se ele não fornecer a resposta esperada, o Google vai tentar enviar callbacks de SSV até cinco vezes em intervalos de um segundo.
Como posso verificar se os callbacks de SSV estão vindo do Google?
Use a pesquisa DNS reversa para verificar se os callbacks de SSV são originados do Google.