Valida las devoluciones de llamada de verificación del servidor (SSV)

Las devoluciones de llamada de verificación del servidor son solicitudes de URL con parámetros de búsqueda que expande Google y que Google envía a un sistema externo para notificarle que se debe recompensar a un usuario por interactuar con un anuncio intersticial recompensado o intersticial. Las devoluciones de llamada de SSV (verificación del servidor) entregadas como recompensa proporcionan una capa adicional de protección contra la falsificación de identidad de las devoluciones de llamada del cliente para recompensar a los usuarios.

En esta guía, se muestra cómo verificar las devoluciones de llamada de SSV recompensados mediante el uso de la biblioteca criptográfica de terceros Tink Java Apps para garantizar que los parámetros de consulta de la devolución de llamada sean valores legítimos. Aunque Tink se usa para los fines de esta guía, tienes la opción de usar cualquier biblioteca de terceros que sea compatible con ECDSA. También puedes probar tu servidor con la herramienta de pruebas en la IU de AdMob.

Consulta este ejemplo completo con Java spring-boot.

Requisitos previos

Usa RewardedAdsVerifier de la biblioteca de apps Tink Java

El repositorio de GitHub Tink Java Apps incluye una clase auxiliar RewardedAdsVerifier a fin de reducir el código necesario para verificar una devolución de llamada de SSV recompensada. El uso de esta clase te permite verificar una URL de devolución de llamada con el siguiente código.

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

Si el método verify() se ejecuta sin generar una excepción, la URL de devolución de llamada se verificó correctamente. En la sección Cómo recompensar al usuario se detallan las prácticas recomendadas sobre cuándo se debe recompensar a los usuarios. Para obtener un desglose de los pasos que realiza esta clase a fin de verificar las devoluciones de llamada de SSV recompensado, puedes leer la sección Verificación manual de SSV recompensado.

Parámetros de devolución de llamada de SSV

Las devoluciones de llamada de verificación del servidor contienen parámetros de consulta que describen la interacción con el anuncio recompensado. Los nombres, las descripciones y los valores de ejemplo de los parámetros se enumeran a continuación. Los parámetros se envían en orden alfabético.

Nombre del parámetro Descripción Valor de ejemplo
red_de_anuncios Es el identificador de la fuente del anuncio que le envió este anuncio. Los nombres de la fuente del anuncio que corresponden a los valores de ID se enumeran en la sección Identificadores de la fuente del anuncio. 1953547073528090325
bloque de anuncios Es el ID de la unidad de anuncios de AdMob que se usó para solicitar el anuncio recompensado. 2747237135
datos_personalizados String de datos personalizada proporcionada por setCustomData .

Si la app no proporciona una string de datos personalizados, este valor del parámetro de consulta no estará presente en la devolución de llamada de SSV.

SAMPLE_CUSTOM_DATA_STRING
id_clave Clave que se usará para verificar la devolución de llamada de SSV. Este valor se asigna a una clave pública que proporciona el servidor de claves de AdMob. 1234567890
importe_de_recompensa Importe de la recompensa según se especifica en la configuración del bloque de anuncios. 5
elemento de recompensa Elemento de recompensa según se especifica en la configuración del bloque de anuncios. monedas
firma Firma para la devolución de llamada de SSV generada por AdMob. MEUCIQCLJS_s4ia_sN06HqzeW7Wc3nhZi4RlW3qV0oO-6AIYdQIgGJEh-rzKreO-paNDbSCzWGMtmgJHYYW9k2_icM9LFMY
timestamp Marca de tiempo del momento en que el usuario se recompensó como tiempo de época en ms. 1507770365237823
transaction_id Identificador único con codificación hexadecimal para cada evento de concesión de recompensa que genera AdMob. 18fa792de1bca816048293fc71035638
user_id Identificador de usuario que proporcionasetUserId.

Si la app no proporciona un identificador de usuario, este parámetro de consulta no estará presente en la devolución de llamada de SSV.

1234567

Identificadores de fuentes del anuncio

ID y nombres de fuentes del anuncio

Ad source name Ad source ID
Aarki (bidding)5240798063227064260
Ad Generation (bidding)1477265452970951479
AdColony15586990674969969776
AdColony (non-SDK) (bidding)4600416542059544716
AdColony (bidding)6895345910719072481
AdFalcon3528208921554210682
AdMob Network5450213213286189855
ADResult10593873382626181482
AMoAd17253994435944008978
Applovin1063618907739174004
Applovin (bidding)1328079684332308356
Chartboost2873236629771172317
Chocolate Platform (bidding)6432849193975106527
CrossChannel (MdotM)9372067028804390441
Custom Event18351550913290782395
DT Exchange*
* Prior to September 21, 2022, this network was called "Fyber Marketplace".
2179455223494392917
Fluct (bidding)8419777862490735710
Flurry3376427960656545613
Fyber*
* This ad source is used for historical reporting.
4839637394546996422
i-mobile5208827440166355534
Improve Digital (bidding)159382223051638006
Index Exchange (bidding)4100650709078789802
InMobi7681903010231960328
InMobi (bidding)6325663098072678541
IronSource6925240245545091930
Leadbolt2899150749497968595
LG U+AD18298738678491729107
maio7505118203095108657
maio (bidding)1343336733822567166
Media.net (bidding)2127936450554446159
Mediated house ads6060308706800320801
Meta Audience Network*
* Prior to June 6, 2022, this network was called "Facebook Audience Network".
10568273599589928883
Meta Audience Network (bidding)*
* Prior to June 6, 2022, this network was called "Facebook Audience Network (bidding)".
11198165126854996598
MobFox8079529624516381459
MoPub (deprecated)10872986198578383917
myTarget8450873672465271579
Nend9383070032774777750
ONE by AOL (Millennial Media)6101072188699264581
ONE by AOL (Nexage)3224789793037044399
OpenX (bidding)4918705482605678398
Pangle (bidding)3525379893916449117
PubMatic (bidding)3841544486172445473
Reservation campaign7068401028668408324
RhythmOne (bidding)2831998725945605450
Rubicon (bidding)3993193775968767067
SK planet734341340207269415
Sharethrough (bidding)5247944089976324188
Smaato (bidding)3362360112145450544
Equativ (bidding)*

* Prior to January 12, 2023, this network was called "Smart Adserver".

5970199210771591442
Sonobi (bidding)3270984106996027150
Tapjoy7295217276740746030
Tapjoy (bidding)4692500501762622178
Tencent GDT7007906637038700218
TripleLift (bidding)8332676245392738510
Unity Ads4970775877303683148
UnrulyX (bidding)2831998725945605450
Verizon Media7360851262951344112
Vpon1940957084538325905
Liftoff Monetize*

* Prior to January 30, 2023, this network was called "Vungle".

1953547073528090325
Liftoff Monetize (bidding)*

* Prior to January 30, 2023, this network was called "Vungle (bidding)".

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

Recompensa al usuario

Es importante equilibrar la experiencia del usuario y la validación de recompensa cuando se decide cuándo recompensar a un usuario. Las devoluciones de llamada del servidor pueden experimentar demoras antes de alcanzar sistemas externos. Por lo tanto, la práctica recomendada es usar la devolución de llamada del cliente para recompensar de inmediato al usuario, mientras se realiza la validación en todas las recompensas cuando se reciben devoluciones de llamada del servidor. Este enfoque proporciona una buena experiencia del usuario y garantiza la validez de las recompensas otorgadas.

Sin embargo, para las aplicaciones en las que la validez de la recompensa es fundamental (por ejemplo, la recompensa afecta la economía de tu app dentro del juego) y las demoras en la concesión de recompensas son aceptables, la espera de la devolución de llamada verificada del servidor puede ser el mejor enfoque.

Datos personalizados

Las apps que requieren datos adicionales en las devoluciones de llamada de verificación del lado del servidor deben usar la función de datos personalizados de los anuncios recompensados. Cualquier valor de string establecido en un objeto de anuncio recompensado se pasa al parámetro de búsqueda custom_data de la devolución de llamada SSV. Si no se establece ningún valor de datos personalizados, el valor del parámetro de consulta custom_data no estará presente en la devolución de llamada de SSV.

En la siguiente muestra de código, se indica cómo configurar las opciones de SSV después de cargar el anuncio recompensado:

Java

RewardedAd.load(MainActivity.this, "ca-app-pub-3940256099942544/5354046379",
    new AdRequest.Builder().build(),  new RewardedAdLoadCallback() {
  @Override
  public void onAdLoaded(RewardedAd ad) {
    Log.d(TAG, "Ad was loaded.");
    rewardedAd = ad;
    ServerSideVerificationOptions options = new ServerSideVerificationOptions
        .Builder()
        .setCustomData("SAMPLE_CUSTOM_DATA_STRING")
        .build();
    rewardedAd.setServerSideVerificationOptions(options);
  }
  @Override
  public void onAdFailedToLoad(LoadAdError loadAdError) {
    Log.d(TAG, loadAdError.toString());
    rewardedAd = null;
  }
});

Kotlin

RewardedAd.load(this, "ca-app-pub-3940256099942544/5354046379",
    AdRequest.Builder().build(), object : RewardedAdLoadCallback() {
  override fun onAdLoaded(ad: RewardedAd) {
    Log.d(TAG, "Ad was loaded.")
    rewardedInterstitialAd = ad
    val options = ServerSideVerificationOptions.Builder()
        .setCustomData("SAMPLE_CUSTOM_DATA_STRING")
        .build()
    rewardedAd.setServerSideVerificationOptions(options)
  }

  override fun onAdFailedToLoad(adError: LoadAdError) {
    Log.d(TAG, adError?.toString())
    rewardedAd = null
  }
})

Si deseas configurar la string de recompensa personalizada, debes hacerlo antes de mostrar el anuncio.

Verificación manual de la SSV recompensada

A continuación, se describen los pasos que realiza la clase RewardedAdsVerifier para verificar una SSV recompensada. Aunque los fragmentos de código incluidos están en Java y aprovechan la biblioteca de terceros Tink, puedes implementar estos pasos en el lenguaje que elijas mediante cualquier biblioteca de terceros que admita ECDSA.

Recupera claves públicas

Para verificar una devolución de llamada de SSV recompensada, necesitas una clave pública que proporcione AdMob.

Puedes obtener una lista de las claves públicas que se usan para validar las devoluciones de llamada de SSV recompensados desde el servidor de claves de AdMob. La lista de claves públicas se proporciona como una representación JSON con un formato similar al siguiente:

{
 "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 las claves públicas, conéctate al servidor de claves de AdMob y descarga las claves. Con el siguiente código, se realiza esta tarea y se guarda la representación JSON de las claves en la variable 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();
}

Ten en cuenta que las claves públicas se rotan con regularidad. Recibirás un correo electrónico para informarte sobre una próxima rotación. Si almacenas en caché las claves públicas, debes actualizar las claves cuando recibas este correo electrónico.

Una vez que se hayan obtenido las claves públicas, se deben analizar. El método parsePublicKeysJson que se muestra a continuación toma una string JSON, como el ejemplo anterior, como entrada y crea una asignación de valores key_id a claves públicas, que se encapsulan como objetos ECPublicKey de la 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;
}

Obtén contenido para verificar

Los dos últimos parámetros de consulta de las devoluciones de llamada de SSV recompensadas son siempre signature y key_id,, en ese orden. Los parámetros de consulta restantes especifican el contenido que se verificará. Supongamos que configuraste AdMob para enviar devoluciones de llamada de recompensas a https://www.myserver.com/mypath. En el siguiente fragmento, se muestra un ejemplo de devolución de llamada de SSV recompensado con el contenido por verificar 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

En el siguiente código, se muestra cómo analizar el contenido que se verificará desde una URL de devolución de llamada como un arreglo 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"));

Obtener la firma y el key_id de la URL de devolución de llamada

Con el valor queryString del paso anterior, analiza los parámetros de consulta signature y key_id de la URL de devolución de llamada como se muestra a continuación:

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()));

Realiza la verificación

El paso final es verificar el contenido de la URL de devolución de llamada con la clave pública adecuada. Toma la asignación que muestra el método parsePublicKeysJson y usa el parámetro key_id de la URL de devolución de llamada para obtener la clave pública de esa asignación. Luego, verifica la firma con esa clave pública. Estos pasos se muestran a continuación en el 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);
  }
}

Si el método se ejecuta sin arrojar una excepción, la URL de devolución de llamada se verificó correctamente.

Preguntas frecuentes

¿Puedo almacenar en caché la clave pública que proporciona el servidor de claves de AdMob?
Te recomendamos almacenar en caché la clave pública que proporciona el servidor de claves de AdMob a fin de reducir la cantidad de operaciones necesarias para validar las devoluciones de llamada de SSV. Sin embargo, ten en cuenta que las claves públicas se rotan con regularidad y no deben almacenarse en caché por más de 24 horas.
¿Con qué frecuencia se rotan las claves públicas proporcionadas por el servidor de claves de AdMob?
Las claves públicas que proporciona el servidor de claves de AdMob se rotan según un programa variable. Para garantizar que la verificación de las devoluciones de llamada de SSV continúe funcionando según lo previsto, las claves públicas no se deben almacenar en caché durante más de 24 horas.
¿Qué sucede si no puedo establecer conexión con mi servidor?
Google espera un código de respuesta de estado de éxito HTTP 200 OK para las devoluciones de llamada SSV. Si no se puede acceder al servidor o no proporciona la respuesta esperada, Google volverá a intentar enviar devoluciones de llamada SSV hasta cinco veces en intervalos de un segundo.
¿Cómo puedo verificar que las devoluciones de llamada SSV provienen de Google?
Usa la búsqueda de DNS inversa para verificar que las devoluciones de llamada SSV se originen en Google.