3

Is there a way to verify if the Rewarded Video Ad was watched by client (OnAdRewarded) on a server? Is there any integration I can use with Google Cloud Functions?

I thought it's possible to verify a token sent by client to the server with admob admin SDK but it seems that it's not possible and we can only verify the ad on the client.

Dave
  • 2,684
  • 2
  • 21
  • 38

5 Answers5

5

It is now possible with Server-Side Verification (SSV) Callbacks.

Server-side verification callbacks are URL requests, with query parameters expanded by Google, that are sent by Google to an external system to notify it that a user should be rewarded for interacting with a rewarded video ad. Rewarded video SSV (server-side verification) callbacks provide an extra layer of protection against spoofing of client-side callbacks to reward users.

Dave
  • 2,684
  • 2
  • 21
  • 38
2

Not sure this is relevant to Firebase but here are some details in case anyone is using Node / JS. You can use Node's inbuilt crypto library. First fetch the available Google AdMob verifier keys from https://gstatic.com/admob/reward/verifier-keys.json.

You'll then need to loop through the returned JSON keys array and grab the pem public key file string corresponding to the req.query.key_id parameter of your incoming req.url string.

Then the "message" we wish to verify signatures with is the incoming req.url substring inbetween the parameter ? symbol and &signature... strings.

Now we can verify easily:

   const verifier = crypto.createVerify("sha256");
   
   verifier.update(message);
   
   if(verifier.verify(pem, req.query.signature, "base64"))
      console.log("Ad was successfully verified.");
   else
      console.log("Ad could not be verified - quick call the cops !");

One caveat to beware of is you may need to unescape(...) your req.url string before you use it since some characters may have been escaped. I was stuck on that for a good hour or two. You can do this using e.g. Node's built in querystring library.

  • I am Node JS newbie, can you share code or example? thanks – NullPointer Jun 22 '21 at 03:20
  • 1
    All the info is in the answer, with example for verifying. Compare and contrast it with Google's Java equivalent: https://developers.google.com/admob/android/ssv you'll learn a lot that way. Note that using Google's Tink JS library would be much easier, (it's explained in that link) although it's in Alpha at the mo so that's why I rolled my own. But that's also explained in that link too ! For starters you could enter the gstatic HTTPS link into a browser for example. –  Jun 22 '21 at 06:50
  • 1
    Also note my references to `req.*` (request) above relate to the Express library form of creating your Node / JS endpoints. Explore https://expressjs.com/ to learn how to setup a Node / JS / Express server. Although you don't need to use Express. Check out Mosh's really good tutorials on Node / JS / etc: https://www.youtube.com/watch?v=pKd0Rpw7O48 –  Jun 22 '21 at 07:57
  • unescape(...) your req.url => save me time. Some query string not match with original from google admob if we don't unescape() – babie Jun 30 '21 at 19:19
1

Not at the moment. The feature has been in closed beta for a while from what I've gathered recently. The last mention I could find was in the linked discussion where someone, from Google presumably, says the feature will be rolled out to the public soon. The post is from Jan 22.

https://groups.google.com/forum/#!topic/google-admob-ads-sdk/weXTAGZfYQ8

pita
  • 108
  • 5
1

I know its a bit late but here is a piece of code that helped me. It is in javascript for Node users. https://github.com/hypeben/admob-rewarded-ads-ssv

const queryString = require('query-string');
const crypto = require('crypto');
const axios = require('axios');
const GOOGLE_AD_KEY_URL = 'https://gstatic.com/admob/reward/verifier-keys.json';

/**
 * Fetches the google public keys for the admob providers.
 * These keys changes time to time.
 */
 const getGoogleKeysMap = async () => {
 let googleKeyRes = await axios.get(GOOGLE_AD_KEY_URL);
 let {keys} = googleKeyRes.data;
 if (!keys) {
 throw new Error('No keys found from google keys');
 }
 /** For each of the keys array save it base 64 in decoded form in the key map */
 let keyMap = {};
 keys.forEach(k => {
 keyMap[`${k.keyId}`] = crypto.createPublicKey(k.pem);
 console.log(keyMap[`${k.keyId}`]);
 });
 return keyMap;
 };

/**
 * Verifies the callback url query params string,
 * Resolves the promise if verification was successful, else fails.
 * Wanna 'debug' then pass the second parameter as true.
  * @param {String} queryUrl
  * @param {Boolean} debug 
 */
async function verify(queryUrl, debug) {

try {

if (typeof queryUrl !== "string") throw new TypeError("URL needs to be string!");

/**
 * Request coming as callback from admob must contain the 'signature' and the  'user_id'.
 * For more info https://developers.google.com/admob/android/rewarded-video-ssv
 */
const {signature, key_id} = queryString.parse(queryUrl);
if (!signature) {
  throw new Error('No signature value exist in the URL param');
}

if(debug) {
  console.debug('Signature and KeyId ---');
  console.debug(signature, key_id);
//  console.log('Signature and KeyId ---');
//  console.log(signature, key_id);
}

let queryParamsString = queryUrl;
if (queryParamsString.indexOf('?') > -1) {
  queryParamsString = queryUrl.split('?')[1];
}

if(debug) {
  console.debug('Query param string ---');
 // console.log('Query param string ---');
  console.debug(queryParamsString);
 // console.log(queryParamsString);
}

/**
 * As per admob, 
 * The last two query parameters of rewarded video SSV callbacks are always signature and key_id, in that order.
 * The remaining query parameters specify the content to be verified. 
 */
let contentToVerify = queryParamsString.substring(0, queryParamsString.indexOf('signature') -1);

if(debug) {
  console.debug('Content to verify ---');
 // console.log(contentToVerify);
 // console.log('Content to verify ---');
  console.debug(contentToVerify);
}

let keyMap = await getGoogleKeysMap();

if(keyMap[`${key_id}`]) {
  let publicKey = keyMap[`${key_id}`];
  const verifier = crypto.createVerify('RSA-SHA256');
  verifier.update(contentToVerify);
  let result = verifier.verify(publicKey, signature, 'base64');
  if (result) {
    console.debug('Result ---');
     console.debug(result);
    return true;
  } else {
    console.debug('Failure ---');
    console.debug(result);
    throw new Error('Invalid Signature Supplied');
  }
} else {
  console.debug('Key id provided doesn\'t exist ---');
  throw new Error('Key id provided doesn\'t exist in the google public keys');
}


  } catch (error) {

 }

}

module.exports.verify = verify;
postUp
  • 11
  • 1
0

If you guys looking for a simple way on Golang for the Admob SSV.

Just use this hiyali/go-lib-ssv, hope to save your life :)

Salam Hiyali
  • 379
  • 1
  • 7