Обратные вызовы серверной проверки (Server-side verification callbacks) — это запросы URL-адресов с параметрами запроса, расширяемыми Google, которые отправляются Google во внешнюю систему, чтобы уведомить её о том, что пользователь должен получить вознаграждение за взаимодействие с рекламным объявлением с вознаграждением или межстраничной рекламой с вознаграждением. Обратные вызовы серверной проверки с вознаграждением (Rewarded SSV) обеспечивают дополнительный уровень защиты от подделки обратных вызовов на стороне клиента для вознаграждения пользователей.
В этом руководстве показано, как проверить обратные вызовы SSV с вознаграждением, используя стороннюю криптографическую библиотеку Tink Java Apps, чтобы убедиться, что параметры запроса в обратном вызове являются допустимыми значениями. Хотя в этом руководстве используется Tink, вы можете использовать любую стороннюю библиотеку, поддерживающую ECDSA . Вы также можете протестировать свой сервер с помощью инструмента тестирования в пользовательском интерфейсе AdMob.
Предварительные требования
- Включите серверную проверку с вознаграждением для вашего рекламного блока.
Используйте RewardedAdsVerifier из библиотеки Tink Java Apps.
В репозитории Tink Java Apps на GitHub есть вспомогательный класс RewardedAdsVerifier , который сокращает объем кода, необходимого для проверки коллбэка SSV с вознаграждением. Использование этого класса позволяет проверить URL-адрес коллбэка с помощью следующего кода.
RewardedAdsVerifier verifier = new RewardedAdsVerifier.Builder()
.fetchVerifyingPublicKeysWith(
RewardedAdsVerifier.KEYS_DOWNLOADER_INSTANCE_PROD)
.build();
String rewardUrl = ...;
verifier.verify(rewardUrl);
Если метод verify() выполняется без генерации исключения, значит, URL-адрес обратного вызова был успешно проверен. В разделе «Вознаграждение пользователя» подробно описаны лучшие практики в отношении того, когда следует вознаграждать пользователей. Подробное описание шагов, выполняемых этим классом для проверки вознагражденных обратных вызовов SSV, можно найти в разделе «Ручная проверка вознагражденных SSV» .
Параметры обратного вызова SSV
Обратные вызовы проверки на стороне сервера содержат параметры запроса, описывающие взаимодействие с рекламным объявлением, за которое начисляется вознаграждение. Названия параметров, их описания и примеры значений приведены ниже. Параметры передаются в алфавитном порядке.
| Имя параметра | Описание | Пример значения |
|---|---|---|
| рекламная сеть | Идентификатор источника объявления, который выполнил показ данного объявления. Названия источников объявления, соответствующие значениям ID, перечислены в разделе « Идентификаторы источников объявления» . | 1953547073528090325 |
| рекламный блок | Идентификатор рекламного блока AdMob, использованный для запроса рекламного объявления с вознаграждением. | 2747237135 |
| key_id | Ключ, используемый для проверки обратного вызова SSV. Это значение соответствует открытому ключу, предоставленному сервером ключей AdMob . | 1234567890 |
| сумма вознаграждения | Размер вознаграждения указан в настройках рекламного блока. | 5 |
| награда_предмет | Вознаграждение предоставляется в соответствии с настройками рекламного блока. | монеты |
| подпись | Подпись для функции обратного вызова SSV, сгенерированная AdMob. | MEUCIQCLJS_s4ia_sN06HqzeW7Wc3nhZi4RlW3qV0oO-6AIYdQIgGJEh-rzKreO-paNDbSCzWGMtmgJHYYW9k2_icM9LFMY |
| метка времени | Отметка времени, когда пользователь получил вознаграждение, в формате Epoch time в миллисекундах. | 1507770365237823 |
| transaction_id | Уникальный шестнадцатеричный идентификатор для каждого события предоставления вознаграждения, генерируемого AdMob. | 18fa792de1bca816048293fc71035638 |
| ID пользователя | Идентификатор пользователя, предоставленный функцией SetUserId .Если приложение не предоставляет идентификатор пользователя, этот параметр запроса не будет присутствовать в функции обратного вызова SSV. | 1234567 |
Идентификаторы источника рекламы
Названия и идентификаторы источников рекламы
| Название источника рекламы | Идентификатор источника рекламы |
|---|---|
| Генерация рекламы (ставки) | 1477265452970951479 |
| Сеть AdMob | 5450213213286189855 |
| Водопад сети AdMob | 1215381445328257950 |
| AppLovin | 1063618907739174004 |
| AppLovin (торги) | 1328079684332308356 |
| Предложение (торги) | 3670825090829827805 |
| BidMachine (система торгов) | 7943972370566394673 |
| Chartboost | 2873236629771172317 |
| Шоколадная платформа (торги) | 6432849193975106527 |
| Пользовательское событие | 18351550913290782395 |
| Обмен DT* * До 21 сентября 2022 года эта сеть называлась "Fyber Marketplace". | 2179455223494392917 |
| DT Exchange (торги) | 8189833498765234879 |
| Экватив (торги)* * До 12 января 2023 года эта сеть называлась "Smart Adserver". | 5970199210771591442 |
| Колебание (торги) | 8419777862490735710 |
| i-mobile | 5208827440166355534 |
| Улучшение цифровых технологий (торгов) | 159382223051638006 |
| Биржа индексов (торги) | 4100650709078789802 |
| InMobi | 7681903010231960328 |
| InMobi (SDK) (торги) | 8468954295581492586 |
| Биржа InMobi (торги) | 5264320421916134407 |
| ironSource Реклама | 6925240245545091930 |
| ironSource Реклама (торги) | 1643326773739866623 |
| Взлет Монетизировать* * До 30 января 2023 года эта сеть называлась "Vungle". | 1953547073528090325 |
| Монетизация Liftoff (торги)* * До 30 января 2023 года эта сеть называлась "Vungle (торги)". | 4692500501762622185 |
| LY Ads Network | 3025503711505004547 |
| Рекламная сеть LY (торги) | 2615812619460460513 |
| Magnite DV+ (торги) | 3993193775968767067 |
| майо | 7505118203095108657 |
| Media.net (торги) | 2127936450554446159 |
| Реклама домов, размещенная через посредников. | 6060308706800320801 |
| Мета-аудитория* * До 6 июня 2022 года эта сеть называлась "Facebook Audience Network". | 10568273599589928883 |
| Meta Audience Network (торги)* * До 6 июня 2022 года эта сеть называлась "Facebook Audience Network (bidding)". | 11198165126854996598 |
| Минтеграл | 1357746574408896200 |
| Минтеграл (торги) | 6250601289653372374 |
| Mobfox (торги) | 3086513548163922365 |
| MobileFuse (торги) | 7303547408604090310 |
| Moloco Ads SDK (система назначения ставок) | 8267622065755668722 |
| myTarget | 8450873672465271579 |
| Nativo (торги) | 3240503836211327896 |
| Нексен (торги)* * До 1 мая 2024 года эта сеть называлась "UnrulyX". | 2831998725945605450 |
| OneTag Exchange (торги) | 4873891452523427499 |
| OpenX (торги) | 4918705482605678398 |
| Пэнгл | 4069896914521993236 |
| Pangle KR SDK (торги) | 12171279046073404914 |
| Pangle ROW SDK (торги) | 3525379893916449117 |
| Pangle US SDK (торги) | 15999446638585856012 |
| PubMatic (торги) | 3841544486172445473 |
| PubMatic OpenWrap SDK | 7702975372504485373 |
| PubMatic OpenWrap SDK (система торгов) | 1234567890123456789 |
| Кампания по бронированию | 7068401028668408324 |
| Повышение (ставки) | 6816468518946650043 |
| Совместное использование (торги) | 5247944089976324188 |
| Смаато (торги) | 3362360112145450544 |
| Соноби (торги) | 3270984106996027150 |
| TripleLift (торги) | 8332676245392738510 |
| Реклама Unity | 4970775877303683148 |
| Реклама в Unity (торги) | 7069338991535737586 |
| Verve Group (участник тендера) | 5013176581647059185 |
| Впон | 1940957084538325905 |
| Yieldmo (торги) | 4193081836471107579 |
| YieldOne (торги) | 3154533971590234104 |
| Цукс | 5506531810221735863 |
Вознаграждение пользователя
Принимая решение о вознаграждении пользователя, важно соблюдать баланс между удобством использования и проверкой достоверности вознаграждения. Серверные коллбэки могут испытывать задержки перед передачей данных во внешние системы. Поэтому рекомендуется использовать клиентский коллбэк для немедленного вознаграждения пользователя, одновременно выполняя проверку всех вознаграждений после получения серверных коллбэков. Такой подход обеспечивает удобство использования и гарантирует достоверность предоставленных вознаграждений.
Однако для приложений, где достоверность вознаграждения имеет решающее значение (например, вознаграждение влияет на внутриигровую экономику вашего приложения) и задержки в начислении вознаграждений допустимы, ожидание подтвержденного обратного вызова на стороне сервера может быть наилучшим подходом.
Пользовательские данные
Приложениям, требующим дополнительных данных в коллбэках проверки на стороне сервера, следует использовать функцию пользовательских данных в объявлениях с вознаграждением. Любое строковое значение, заданное для объекта объявления с вознаграждением, передается в параметр запроса custom_data коллбэка SSV. Если значение пользовательских данных не задано, значение параметра запроса custom_data не будет присутствовать в коллбэке SSV.
Приведенный ниже пример кода демонстрирует, как установить параметры SSV после загрузки рекламного объявления с вознаграждением:
private void LoadRewardedAd(string adUnitId)
{
// Send the request to load the ad.
AdRequest adRequest = new AdRequest();
RewardedAd.Load(adUnitId, adRequest, (RewardedAd rewardedAd, LoadAdError error) =>
{
// If the operation failed with a reason.
if (error != null)
{
Debug.LogError("Rewarded ad failed to load an ad with error : " + error);
return;
}
var options = new ServerSideVerificationOptions
.Builder()
.SetCustomData("SAMPLE_CUSTOM_DATA_STRING")
.Build()
rewardedAd.SetServerSideVerificationOptions(options);
});
}
Если вы хотите задать пользовательскую строку вознаграждения, это необходимо сделать до показа рекламы.
Ручная проверка вознагражденных SSV
Ниже описаны шаги, выполняемые классом RewardedAdsVerifier для проверки SSV с вознаграждением. Хотя приведенные фрагменты кода написаны на Java и используют стороннюю библиотеку Tink, вы можете реализовать эти шаги на любом языке программирования по вашему выбору, используя любую стороннюю библиотеку, поддерживающую ECDSA .
Получить открытые ключи
Для проверки полученного вознаграждения за обратный вызов SSV вам потребуется открытый ключ, предоставленный AdMob.
Список открытых ключей, используемых для проверки обратных вызовов SSV с вознаграждением, можно получить на сервере ключей AdMob . Список открытых ключей предоставляется в формате JSON, аналогичном следующему:
{
"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=="
},
],
}
Чтобы получить открытые ключи, подключитесь к серверу ключей AdMob и загрузите ключи. Следующий код выполняет эту задачу и сохраняет JSON-представление ключей в переменную 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();
}
Обратите внимание, что открытые ключи регулярно обновляются. Вы получите электронное письмо с уведомлением о предстоящем обновлении. Если вы кэшируете открытые ключи, вам следует обновить их после получения этого письма.
После получения открытых ключей их необходимо разобрать. Метод parsePublicKeysJson описанный ниже, принимает в качестве входных данных строку JSON, подобную приведенному выше примеру, и создает сопоставление значений key_id с открытыми ключами, которые инкапсулированы в объекты ECPublicKey из библиотеки 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;
}
Получите контент для проверки.
Последние два параметра запроса в коллбэках SSV с вознаграждением всегда являются signature и key_id, именно в таком порядке. Остальные параметры запроса указывают содержимое, подлежащее проверке. Предположим, вы настроили AdMob для отправки коллбэков с вознаграждением на https://www.myserver.com/mypath . Приведенный ниже фрагмент кода показывает пример коллбэка SSV с вознаграждением, где выделено содержимое, подлежащее проверке.
https://www.myserver.com/path?ad_network=54...55&ad_unit=12345678&reward_amount=10&reward_item=coins ×tamp=150777823&transaction_id=12...DEF&user_id=1234567&signature=ME...Z1c&key_id=1268887
Приведённый ниже код демонстрирует, как преобразовать содержимое, подлежащее проверке, из URL-адреса обратного вызова в массив байтов в кодировке 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"));
Получение подписи и key_id из URL-адреса обратного вызова.
Используя значение queryString из предыдущего шага, проанализируйте параметры запроса signature и key_id из URL-адреса обратного вызова, как показано ниже:
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()));
Провести проверку
Последний шаг — проверка содержимого URL-адреса обратного вызова с помощью соответствующего открытого ключа. Возьмите сопоставление, возвращаемое методом parsePublicKeysJson , и используйте параметр key_id из URL-адреса обратного вызова, чтобы получить открытый ключ из этого сопоставления. Затем проверьте подпись с помощью этого открытого ключа. Эти шаги показаны ниже в методе 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);
}
}
Если метод выполняется без генерации исключения, значит, URL-адрес обратного вызова был успешно проверен.
Часто задаваемые вопросы
- Можно ли кэшировать открытый ключ, предоставленный сервером ключей AdMob?
- Мы рекомендуем кэшировать открытый ключ, предоставленный сервером ключей AdMob, чтобы уменьшить количество операций, необходимых для проверки обратных вызовов SSV. Однако обратите внимание, что открытые ключи регулярно обновляются и не должны кэшироваться дольше 24 часов.
- Как часто меняются открытые ключи, предоставляемые сервером ключей AdMob?
- Открытые ключи, предоставляемые сервером ключей AdMob, обновляются по переменному расписанию. Для обеспечения корректной работы проверки обратных вызовов SSV открытые ключи не следует кэшировать дольше 24 часов.
- Что произойдет, если мой сервер окажется недоступен?
- Google ожидает код ответа
HTTP 200 OK(успешное выполнение) для обратных вызовов SSV. Если ваш сервер недоступен или не предоставляет ожидаемый ответ, Google попытается отправить обратные вызовы SSV до пяти раз с интервалом в одну секунду. - Как я могу убедиться, что обратные вызовы SSV поступают от Google?
- Используйте обратный DNS-запрос, чтобы убедиться, что обратные вызовы SSV исходят от Google.
Обратные вызовы серверной проверки (Server-side verification callbacks) — это запросы URL-адресов с параметрами запроса, расширяемыми Google, которые отправляются Google во внешнюю систему, чтобы уведомить её о том, что пользователь должен получить вознаграждение за взаимодействие с рекламным объявлением с вознаграждением или межстраничной рекламой с вознаграждением. Обратные вызовы серверной проверки с вознаграждением (Rewarded SSV) обеспечивают дополнительный уровень защиты от подделки обратных вызовов на стороне клиента для вознаграждения пользователей.
В этом руководстве показано, как проверить обратные вызовы SSV с вознаграждением, используя стороннюю криптографическую библиотеку Tink Java Apps, чтобы убедиться, что параметры запроса в обратном вызове являются допустимыми значениями. Хотя в этом руководстве используется Tink, вы можете использовать любую стороннюю библиотеку, поддерживающую ECDSA . Вы также можете протестировать свой сервер с помощью инструмента тестирования в пользовательском интерфейсе AdMob.
Предварительные требования
- Включите серверную проверку с вознаграждением для вашего рекламного блока.
Используйте RewardedAdsVerifier из библиотеки Tink Java Apps.
В репозитории Tink Java Apps на GitHub есть вспомогательный класс RewardedAdsVerifier , который сокращает объем кода, необходимого для проверки коллбэка SSV с вознаграждением. Использование этого класса позволяет проверить URL-адрес коллбэка с помощью следующего кода.
RewardedAdsVerifier verifier = new RewardedAdsVerifier.Builder()
.fetchVerifyingPublicKeysWith(
RewardedAdsVerifier.KEYS_DOWNLOADER_INSTANCE_PROD)
.build();
String rewardUrl = ...;
verifier.verify(rewardUrl);
Если метод verify() выполняется без генерации исключения, значит, URL-адрес обратного вызова был успешно проверен. В разделе «Вознаграждение пользователя» подробно описаны лучшие практики в отношении того, когда следует вознаграждать пользователей. Подробное описание шагов, выполняемых этим классом для проверки вознагражденных обратных вызовов SSV, можно найти в разделе «Ручная проверка вознагражденных SSV» .
Параметры обратного вызова SSV
Обратные вызовы проверки на стороне сервера содержат параметры запроса, описывающие взаимодействие с рекламным объявлением, за которое начисляется вознаграждение. Названия параметров, их описания и примеры значений приведены ниже. Параметры передаются в алфавитном порядке.
| Имя параметра | Описание | Пример значения |
|---|---|---|
| рекламная сеть | Идентификатор источника объявления, который выполнил показ данного объявления. Названия источников объявления, соответствующие значениям ID, перечислены в разделе « Идентификаторы источников объявления» . | 1953547073528090325 |
| рекламный блок | Идентификатор рекламного блока AdMob, использованный для запроса рекламного объявления с вознаграждением. | 2747237135 |
| key_id | Ключ, используемый для проверки обратного вызова SSV. Это значение соответствует открытому ключу, предоставленному сервером ключей AdMob . | 1234567890 |
| сумма вознаграждения | Размер вознаграждения указан в настройках рекламного блока. | 5 |
| награда_предмет | Вознаграждение предоставляется в соответствии с настройками рекламного блока. | монеты |
| подпись | Подпись для функции обратного вызова SSV, сгенерированная AdMob. | MEUCIQCLJS_s4ia_sN06HqzeW7Wc3nhZi4RlW3qV0oO-6AIYdQIgGJEh-rzKreO-paNDbSCzWGMtmgJHYYW9k2_icM9LFMY |
| метка времени | Отметка времени, когда пользователь получил вознаграждение, в формате Epoch time в миллисекундах. | 1507770365237823 |
| transaction_id | Уникальный шестнадцатеричный идентификатор для каждого события предоставления вознаграждения, генерируемого AdMob. | 18fa792de1bca816048293fc71035638 |
| ID пользователя | Идентификатор пользователя, предоставленный функцией SetUserId .Если приложение не предоставляет идентификатор пользователя, этот параметр запроса не будет присутствовать в функции обратного вызова SSV. | 1234567 |
Идентификаторы источника рекламы
Названия и идентификаторы источников рекламы
| Название источника рекламы | Идентификатор источника рекламы |
|---|---|
| Генерация рекламы (ставки) | 1477265452970951479 |
| Сеть AdMob | 5450213213286189855 |
| Водопад сети AdMob | 1215381445328257950 |
| AppLovin | 1063618907739174004 |
| AppLovin (торги) | 1328079684332308356 |
| Предложение (торги) | 3670825090829827805 |
| BidMachine (система торгов) | 7943972370566394673 |
| Chartboost | 2873236629771172317 |
| Шоколадная платформа (торги) | 6432849193975106527 |
| Пользовательское событие | 18351550913290782395 |
| Обмен DT* * До 21 сентября 2022 года эта сеть называлась "Fyber Marketplace". | 2179455223494392917 |
| DT Exchange (торги) | 8189833498765234879 |
| Экватив (торги)* * До 12 января 2023 года эта сеть называлась "Smart Adserver". | 5970199210771591442 |
| Колебание (торги) | 8419777862490735710 |
| i-mobile | 5208827440166355534 |
| Улучшение цифровых технологий (торгов) | 159382223051638006 |
| Биржа индексов (торги) | 4100650709078789802 |
| InMobi | 7681903010231960328 |
| InMobi (SDK) (торги) | 8468954295581492586 |
| Биржа InMobi (торги) | 5264320421916134407 |
| ironSource Реклама | 6925240245545091930 |
| ironSource Реклама (торги) | 1643326773739866623 |
| Взлет Монетизировать* * До 30 января 2023 года эта сеть называлась "Vungle". | 1953547073528090325 |
| Монетизация Liftoff (торги)* * До 30 января 2023 года эта сеть называлась "Vungle (торги)". | 4692500501762622185 |
| LY Ads Network | 3025503711505004547 |
| Рекламная сеть LY (торги) | 2615812619460460513 |
| Magnite DV+ (торги) | 3993193775968767067 |
| майо | 7505118203095108657 |
| Media.net (торги) | 2127936450554446159 |
| Реклама домов, размещенная через посредников. | 6060308706800320801 |
| Мета-аудитория* * До 6 июня 2022 года эта сеть называлась "Facebook Audience Network". | 10568273599589928883 |
| Meta Audience Network (торги)* * До 6 июня 2022 года эта сеть называлась "Facebook Audience Network (bidding)". | 11198165126854996598 |
| Минтеграл | 1357746574408896200 |
| Минтеграл (торги) | 6250601289653372374 |
| Mobfox (торги) | 3086513548163922365 |
| MobileFuse (торги) | 7303547408604090310 |
| Moloco Ads SDK (система назначения ставок) | 8267622065755668722 |
| myTarget | 8450873672465271579 |
| Nativo (торги) | 3240503836211327896 |
| Нексен (торги)* * До 1 мая 2024 года эта сеть называлась "UnrulyX". | 2831998725945605450 |
| OneTag Exchange (торги) | 4873891452523427499 |
| OpenX (торги) | 4918705482605678398 |
| Пэнгл | 4069896914521993236 |
| Pangle KR SDK (торги) | 12171279046073404914 |
| Pangle ROW SDK (торги) | 3525379893916449117 |
| Pangle US SDK (торги) | 15999446638585856012 |
| PubMatic (торги) | 3841544486172445473 |
| PubMatic OpenWrap SDK | 7702975372504485373 |
| PubMatic OpenWrap SDK (система торгов) | 1234567890123456789 |
| Кампания по бронированию | 7068401028668408324 |
| Повышение (ставки) | 6816468518946650043 |
| Совместное использование (торги) | 5247944089976324188 |
| Смаато (торги) | 3362360112145450544 |
| Соноби (торги) | 3270984106996027150 |
| TripleLift (торги) | 8332676245392738510 |
| Реклама Unity | 4970775877303683148 |
| Реклама в Unity (торги) | 7069338991535737586 |
| Verve Group (участник тендера) | 5013176581647059185 |
| Впон | 1940957084538325905 |
| Yieldmo (торги) | 4193081836471107579 |
| YieldOne (торги) | 3154533971590234104 |
| Цукс | 5506531810221735863 |
Вознаграждение пользователя
Принимая решение о вознаграждении пользователя, важно соблюдать баланс между удобством использования и проверкой достоверности вознаграждения. Серверные коллбэки могут испытывать задержки перед передачей данных во внешние системы. Поэтому рекомендуется использовать клиентский коллбэк для немедленного вознаграждения пользователя, одновременно выполняя проверку всех вознаграждений после получения серверных коллбэков. Такой подход обеспечивает удобство использования и гарантирует достоверность предоставленных вознаграждений.
Однако для приложений, где достоверность вознаграждения имеет решающее значение (например, вознаграждение влияет на внутриигровую экономику вашего приложения) и задержки в начислении вознаграждений допустимы, ожидание подтвержденного обратного вызова на стороне сервера может быть наилучшим подходом.
Пользовательские данные
Приложениям, требующим дополнительных данных в коллбэках проверки на стороне сервера, следует использовать функцию пользовательских данных в объявлениях с вознаграждением. Любое строковое значение, заданное для объекта объявления с вознаграждением, передается в параметр запроса custom_data коллбэка SSV. Если значение пользовательских данных не задано, значение параметра запроса custom_data не будет присутствовать в коллбэке SSV.
Приведенный ниже пример кода демонстрирует, как установить параметры SSV после загрузки рекламного объявления с вознаграждением:
private void LoadRewardedAd(string adUnitId)
{
// Send the request to load the ad.
AdRequest adRequest = new AdRequest();
RewardedAd.Load(adUnitId, adRequest, (RewardedAd rewardedAd, LoadAdError error) =>
{
// If the operation failed with a reason.
if (error != null)
{
Debug.LogError("Rewarded ad failed to load an ad with error : " + error);
return;
}
var options = new ServerSideVerificationOptions
.Builder()
.SetCustomData("SAMPLE_CUSTOM_DATA_STRING")
.Build()
rewardedAd.SetServerSideVerificationOptions(options);
});
}
Если вы хотите задать пользовательскую строку вознаграждения, это необходимо сделать до показа рекламы.
Ручная проверка вознагражденных SSV
Ниже описаны шаги, выполняемые классом RewardedAdsVerifier для проверки SSV с вознаграждением. Хотя приведенные фрагменты кода написаны на Java и используют стороннюю библиотеку Tink, вы можете реализовать эти шаги на любом языке программирования по вашему выбору, используя любую стороннюю библиотеку, поддерживающую ECDSA .
Получить открытые ключи
Для проверки полученного вознаграждения за обратный вызов SSV вам потребуется открытый ключ, предоставленный AdMob.
Список открытых ключей, используемых для проверки обратных вызовов SSV с вознаграждением, можно получить на сервере ключей AdMob . Список открытых ключей предоставляется в формате JSON, аналогичном следующему:
{
"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=="
},
],
}
Чтобы получить открытые ключи, подключитесь к серверу ключей AdMob и загрузите ключи. Следующий код выполняет эту задачу и сохраняет JSON-представление ключей в переменную 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();
}
Обратите внимание, что открытые ключи регулярно обновляются. Вы получите электронное письмо с уведомлением о предстоящем обновлении. Если вы кэшируете открытые ключи, вам следует обновить их после получения этого письма.
После получения открытых ключей их необходимо разобрать. Метод parsePublicKeysJson описанный ниже, принимает в качестве входных данных строку JSON, подобную приведенному выше примеру, и создает сопоставление значений key_id с открытыми ключами, которые инкапсулированы в объекты ECPublicKey из библиотеки 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;
}
Получите контент для проверки.
Последние два параметра запроса в коллбэках SSV с вознаграждением всегда являются signature и key_id, именно в таком порядке. Остальные параметры запроса указывают содержимое, подлежащее проверке. Предположим, вы настроили AdMob для отправки коллбэков с вознаграждением на https://www.myserver.com/mypath . Приведенный ниже фрагмент кода показывает пример коллбэка SSV с вознаграждением, где выделено содержимое, подлежащее проверке.
https://www.myserver.com/path?ad_network=54...55&ad_unit=12345678&reward_amount=10&reward_item=coins ×tamp=150777823&transaction_id=12...DEF&user_id=1234567&signature=ME...Z1c&key_id=1268887
Приведённый ниже код демонстрирует, как преобразовать содержимое, подлежащее проверке, из URL-адреса обратного вызова в массив байтов в кодировке 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"));
Получение подписи и key_id из URL-адреса обратного вызова.
Используя значение queryString из предыдущего шага, проанализируйте параметры запроса signature и key_id из URL-адреса обратного вызова, как показано ниже:
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()));
Провести проверку
Последний шаг — проверка содержимого URL-адреса обратного вызова с помощью соответствующего открытого ключа. Возьмите сопоставление, возвращаемое методом parsePublicKeysJson , и используйте параметр key_id из URL-адреса обратного вызова, чтобы получить открытый ключ из этого сопоставления. Затем проверьте подпись с помощью этого открытого ключа. Эти шаги показаны ниже в методе 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);
}
}
Если метод выполняется без генерации исключения, значит, URL-адрес обратного вызова был успешно проверен.
Часто задаваемые вопросы
- Можно ли кэшировать открытый ключ, предоставленный сервером ключей AdMob?
- Мы рекомендуем кэшировать открытый ключ, предоставленный сервером ключей AdMob, чтобы уменьшить количество операций, необходимых для проверки обратных вызовов SSV. Однако обратите внимание, что открытые ключи регулярно обновляются и не должны кэшироваться дольше 24 часов.
- Как часто меняются открытые ключи, предоставляемые сервером ключей AdMob?
- Открытые ключи, предоставляемые сервером ключей AdMob, обновляются по переменному расписанию. Для обеспечения корректной работы проверки обратных вызовов SSV открытые ключи не следует кэшировать дольше 24 часов.
- Что произойдет, если мой сервер окажется недоступен?
- Google ожидает код ответа
HTTP 200 OK(успешное выполнение) для обратных вызовов SSV. Если ваш сервер недоступен или не предоставляет ожидаемый ответ, Google попытается отправить обратные вызовы SSV до пяти раз с интервалом в одну секунду. - Как я могу убедиться, что обратные вызовы SSV поступают от Google?
- Используйте обратный DNS-запрос, чтобы убедиться, что обратные вызовы SSV исходят от Google.
Обратные вызовы серверной проверки (Server-side verification callbacks) — это запросы URL-адресов с параметрами запроса, расширяемыми Google, которые отправляются Google во внешнюю систему, чтобы уведомить её о том, что пользователь должен получить вознаграждение за взаимодействие с рекламным объявлением с вознаграждением или межстраничной рекламой с вознаграждением. Обратные вызовы серверной проверки с вознаграждением (Rewarded SSV) обеспечивают дополнительный уровень защиты от подделки обратных вызовов на стороне клиента для вознаграждения пользователей.
В этом руководстве показано, как проверить обратные вызовы SSV с вознаграждением, используя стороннюю криптографическую библиотеку Tink Java Apps, чтобы убедиться, что параметры запроса в обратном вызове являются допустимыми значениями. Хотя в этом руководстве используется Tink, вы можете использовать любую стороннюю библиотеку, поддерживающую ECDSA . Вы также можете протестировать свой сервер с помощью инструмента тестирования в пользовательском интерфейсе AdMob.
Предварительные требования
- Включите серверную проверку с вознаграждением для вашего рекламного блока.
Используйте RewardedAdsVerifier из библиотеки Tink Java Apps.
В репозитории Tink Java Apps на GitHub есть вспомогательный класс RewardedAdsVerifier , который сокращает объем кода, необходимого для проверки коллбэка SSV с вознаграждением. Использование этого класса позволяет проверить URL-адрес коллбэка с помощью следующего кода.
RewardedAdsVerifier verifier = new RewardedAdsVerifier.Builder()
.fetchVerifyingPublicKeysWith(
RewardedAdsVerifier.KEYS_DOWNLOADER_INSTANCE_PROD)
.build();
String rewardUrl = ...;
verifier.verify(rewardUrl);
Если метод verify() выполняется без генерации исключения, значит, URL-адрес обратного вызова был успешно проверен. В разделе «Вознаграждение пользователя» подробно описаны лучшие практики в отношении того, когда следует вознаграждать пользователей. Подробное описание шагов, выполняемых этим классом для проверки вознагражденных обратных вызовов SSV, можно найти в разделе «Ручная проверка вознагражденных SSV» .
Параметры обратного вызова SSV
Обратные вызовы проверки на стороне сервера содержат параметры запроса, описывающие взаимодействие с рекламным объявлением, за которое начисляется вознаграждение. Названия параметров, их описания и примеры значений приведены ниже. Параметры передаются в алфавитном порядке.
| Имя параметра | Описание | Пример значения |
|---|---|---|
| рекламная сеть | Идентификатор источника объявления, который выполнил показ данного объявления. Названия источников объявления, соответствующие значениям ID, перечислены в разделе « Идентификаторы источников объявления» . | 1953547073528090325 |
| рекламный блок | Идентификатор рекламного блока AdMob, использованный для запроса рекламного объявления с вознаграждением. | 2747237135 |
| key_id | Ключ, используемый для проверки обратного вызова SSV. Это значение соответствует открытому ключу, предоставленному сервером ключей AdMob . | 1234567890 |
| сумма вознаграждения | Размер вознаграждения указан в настройках рекламного блока. | 5 |
| награда_предмет | Вознаграждение предоставляется в соответствии с настройками рекламного блока. | монеты |
| подпись | Подпись для функции обратного вызова SSV, сгенерированная AdMob. | MEUCIQCLJS_s4ia_sN06HqzeW7Wc3nhZi4RlW3qV0oO-6AIYdQIgGJEh-rzKreO-paNDbSCzWGMtmgJHYYW9k2_icM9LFMY |
| метка времени | Отметка времени, когда пользователь получил вознаграждение, в формате Epoch time в миллисекундах. | 1507770365237823 |
| transaction_id | Уникальный шестнадцатеричный идентификатор для каждого события предоставления вознаграждения, генерируемого AdMob. | 18fa792de1bca816048293fc71035638 |
| ID пользователя | Идентификатор пользователя, предоставленный функцией SetUserId .Если приложение не предоставляет идентификатор пользователя, этот параметр запроса не будет присутствовать в функции обратного вызова SSV. | 1234567 |
Идентификаторы источника рекламы
Названия и идентификаторы источников рекламы
| Название источника рекламы | Идентификатор источника рекламы |
|---|---|
| Генерация рекламы (ставки) | 1477265452970951479 |
| Сеть AdMob | 5450213213286189855 |
| Водопад сети AdMob | 1215381445328257950 |
| AppLovin | 1063618907739174004 |
| AppLovin (торги) | 1328079684332308356 |
| Предложение (торги) | 3670825090829827805 |
| BidMachine (система торгов) | 7943972370566394673 |
| Chartboost | 2873236629771172317 |
| Шоколадная платформа (торги) | 6432849193975106527 |
| Пользовательское событие | 18351550913290782395 |
| Обмен DT* * До 21 сентября 2022 года эта сеть называлась "Fyber Marketplace". | 2179455223494392917 |
| DT Exchange (торги) | 8189833498765234879 |
| Экватив (торги)* * До 12 января 2023 года эта сеть называлась "Smart Adserver". | 5970199210771591442 |
| Колебание (торги) | 8419777862490735710 |
| i-mobile | 5208827440166355534 |
| Улучшение цифровых технологий (торгов) | 159382223051638006 |
| Биржа индексов (торги) | 4100650709078789802 |
| InMobi | 7681903010231960328 |
| InMobi (SDK) (торги) | 8468954295581492586 |
| Биржа InMobi (торги) | 5264320421916134407 |
| ironSource Реклама | 6925240245545091930 |
| ironSource Реклама (торги) | 1643326773739866623 |
| Взлет Монетизировать* * До 30 января 2023 года эта сеть называлась "Vungle". | 1953547073528090325 |
| Монетизация Liftoff (торги)* * До 30 января 2023 года эта сеть называлась "Vungle (торги)". | 4692500501762622185 |
| LY Ads Network | 3025503711505004547 |
| Рекламная сеть LY (торги) | 2615812619460460513 |
| Magnite DV+ (торги) | 3993193775968767067 |
| майо | 7505118203095108657 |
| Media.net (торги) | 2127936450554446159 |
| Реклама домов, размещенная через посредников. | 6060308706800320801 |
| Мета-аудитория* * До 6 июня 2022 года эта сеть называлась "Facebook Audience Network". | 10568273599589928883 |
| Meta Audience Network (торги)* * До 6 июня 2022 года эта сеть называлась "Facebook Audience Network (bidding)". | 11198165126854996598 |
| Минтеграл | 1357746574408896200 |
| Минтеграл (торги) | 6250601289653372374 |
| Mobfox (торги) | 3086513548163922365 |
| MobileFuse (торги) | 7303547408604090310 |
| Moloco Ads SDK (система назначения ставок) | 8267622065755668722 |
| myTarget | 8450873672465271579 |
| Nativo (торги) | 3240503836211327896 |
| Нексен (торги)* * До 1 мая 2024 года эта сеть называлась "UnrulyX". | 2831998725945605450 |
| OneTag Exchange (торги) | 4873891452523427499 |
| OpenX (торги) | 4918705482605678398 |
| Пэнгл | 4069896914521993236 |
| Pangle KR SDK (торги) | 12171279046073404914 |
| Pangle ROW SDK (торги) | 3525379893916449117 |
| Pangle US SDK (торги) | 15999446638585856012 |
| PubMatic (торги) | 3841544486172445473 |
| PubMatic OpenWrap SDK | 7702975372504485373 |
| PubMatic OpenWrap SDK (система торгов) | 1234567890123456789 |
| Кампания по бронированию | 7068401028668408324 |
| Повышение (ставки) | 6816468518946650043 |
| Совместное использование (торги) | 5247944089976324188 |
| Смаато (торги) | 3362360112145450544 |
| Соноби (торги) | 3270984106996027150 |
| TripleLift (торги) | 8332676245392738510 |
| Реклама Unity | 4970775877303683148 |
| Реклама в Unity (торги) | 7069338991535737586 |
| Verve Group (участник тендера) | 5013176581647059185 |
| Впон | 1940957084538325905 |
| Yieldmo (торги) | 4193081836471107579 |
| YieldOne (торги) | 3154533971590234104 |
| Цукс | 5506531810221735863 |
Вознаграждение пользователя
Принимая решение о вознаграждении пользователя, важно соблюдать баланс между удобством использования и проверкой достоверности вознаграждения. Серверные коллбэки могут испытывать задержки перед передачей данных во внешние системы. Поэтому рекомендуется использовать клиентский коллбэк для немедленного вознаграждения пользователя, одновременно выполняя проверку всех вознаграждений после получения серверных коллбэков. Такой подход обеспечивает удобство использования и гарантирует достоверность предоставленных вознаграждений.
Однако для приложений, где достоверность вознаграждения имеет решающее значение (например, вознаграждение влияет на внутриигровую экономику вашего приложения) и задержки в начислении вознаграждений допустимы, ожидание подтвержденного обратного вызова на стороне сервера может быть наилучшим подходом.
Пользовательские данные
Приложениям, требующим дополнительных данных в коллбэках проверки на стороне сервера, следует использовать функцию пользовательских данных в объявлениях с вознаграждением. Любое строковое значение, заданное для объекта объявления с вознаграждением, передается в параметр запроса custom_data коллбэка SSV. Если значение пользовательских данных не задано, значение параметра запроса custom_data не будет присутствовать в коллбэке SSV.
Приведенный ниже пример кода демонстрирует, как установить параметры SSV после загрузки рекламного объявления с вознаграждением:
private void LoadRewardedAd(string adUnitId)
{
// Send the request to load the ad.
AdRequest adRequest = new AdRequest();
RewardedAd.Load(adUnitId, adRequest, (RewardedAd rewardedAd, LoadAdError error) =>
{
// If the operation failed with a reason.
if (error != null)
{
Debug.LogError("Rewarded ad failed to load an ad with error : " + error);
return;
}
var options = new ServerSideVerificationOptions
.Builder()
.SetCustomData("SAMPLE_CUSTOM_DATA_STRING")
.Build()
rewardedAd.SetServerSideVerificationOptions(options);
});
}
Если вы хотите задать пользовательскую строку вознаграждения, это необходимо сделать до показа рекламы.
Ручная проверка вознагражденных SSV
Ниже описаны шаги, выполняемые классом RewardedAdsVerifier для проверки SSV с вознаграждением. Хотя приведенные фрагменты кода написаны на Java и используют стороннюю библиотеку Tink, вы можете реализовать эти шаги на любом языке программирования по вашему выбору, используя любую стороннюю библиотеку, поддерживающую ECDSA .
Получить открытые ключи
Для проверки полученного вознаграждения за обратный вызов SSV вам потребуется открытый ключ, предоставленный AdMob.
Список открытых ключей, используемых для проверки обратных вызовов SSV с вознаграждением, можно получить на сервере ключей AdMob . Список открытых ключей предоставляется в формате JSON, аналогичном следующему:
{
"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=="
},
],
}
Чтобы получить открытые ключи, подключитесь к серверу ключей AdMob и загрузите ключи. Следующий код выполняет эту задачу и сохраняет JSON-представление ключей в переменную 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();
}
Обратите внимание, что открытые ключи регулярно обновляются. Вы получите электронное письмо с уведомлением о предстоящем обновлении. Если вы кэшируете открытые ключи, вам следует обновить их после получения этого письма.
После получения открытых ключей их необходимо разобрать. Метод parsePublicKeysJson описанный ниже, принимает в качестве входных данных строку JSON, подобную приведенному выше примеру, и создает сопоставление значений key_id с открытыми ключами, которые инкапсулированы в объекты ECPublicKey из библиотеки 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;
}
Получите контент для проверки.
Последние два параметра запроса в коллбэках SSV с вознаграждением всегда являются signature и key_id, именно в таком порядке. Остальные параметры запроса указывают содержимое, подлежащее проверке. Предположим, вы настроили AdMob для отправки коллбэков с вознаграждением на https://www.myserver.com/mypath . Приведенный ниже фрагмент кода показывает пример коллбэка SSV с вознаграждением, где выделено содержимое, подлежащее проверке.
https://www.myserver.com/path?ad_network=54...55&ad_unit=12345678&reward_amount=10&reward_item=coins ×tamp=150777823&transaction_id=12...DEF&user_id=1234567&signature=ME...Z1c&key_id=1268887
Приведённый ниже код демонстрирует, как преобразовать содержимое, подлежащее проверке, из URL-адреса обратного вызова в массив байтов в кодировке 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"));
Получение подписи и key_id из URL-адреса обратного вызова.
Используя значение queryString из предыдущего шага, проанализируйте параметры запроса signature и key_id из URL-адреса обратного вызова, как показано ниже:
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()));
Провести проверку
Последний шаг — проверка содержимого URL-адреса обратного вызова с помощью соответствующего открытого ключа. Возьмите сопоставление, возвращаемое методом parsePublicKeysJson , и используйте параметр key_id из URL-адреса обратного вызова, чтобы получить открытый ключ из этого сопоставления. Затем проверьте подпись с помощью этого открытого ключа. Эти шаги показаны ниже в методе 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);
}
}
Если метод выполняется без генерации исключения, значит, URL-адрес обратного вызова был успешно проверен.
Часто задаваемые вопросы
- Можно ли кэшировать открытый ключ, предоставленный сервером ключей AdMob?
- Мы рекомендуем кэшировать открытый ключ, предоставленный сервером ключей AdMob, чтобы уменьшить количество операций, необходимых для проверки обратных вызовов SSV. Однако обратите внимание, что открытые ключи регулярно обновляются и не должны кэшироваться дольше 24 часов.
- Как часто меняются открытые ключи, предоставляемые сервером ключей AdMob?
- Открытые ключи, предоставляемые сервером ключей AdMob, обновляются по переменному расписанию. Для обеспечения корректной работы проверки обратных вызовов SSV открытые ключи не следует кэшировать дольше 24 часов.
- Что произойдет, если мой сервер окажется недоступен?
- Google ожидает код ответа
HTTP 200 OK(успешное выполнение) для обратных вызовов SSV. Если ваш сервер недоступен или не предоставляет ожидаемый ответ, Google попытается отправить обратные вызовы SSV до пяти раз с интервалом в одну секунду. - Как я могу убедиться, что обратные вызовы SSV поступают от Google?
- Используйте обратный DNS-запрос, чтобы убедиться, что обратные вызовы SSV исходят от Google.
Обратные вызовы серверной проверки (Server-side verification callbacks) — это запросы URL-адресов с параметрами запроса, расширяемыми Google, которые отправляются Google во внешнюю систему, чтобы уведомить её о том, что пользователь должен получить вознаграждение за взаимодействие с рекламным объявлением с вознаграждением или межстраничной рекламой с вознаграждением. Обратные вызовы серверной проверки с вознаграждением (Rewarded SSV) обеспечивают дополнительный уровень защиты от подделки обратных вызовов на стороне клиента для вознаграждения пользователей.
В этом руководстве показано, как проверить обратные вызовы SSV с вознаграждением, используя стороннюю криптографическую библиотеку Tink Java Apps, чтобы убедиться, что параметры запроса в обратном вызове являются допустимыми значениями. Хотя в этом руководстве используется Tink, вы можете использовать любую стороннюю библиотеку, поддерживающую ECDSA . Вы также можете протестировать свой сервер с помощью инструмента тестирования в пользовательском интерфейсе AdMob.
Предварительные требования
- Включите серверную проверку с вознаграждением для вашего рекламного блока.
Используйте RewardedAdsVerifier из библиотеки Tink Java Apps.
В репозитории Tink Java Apps на GitHub есть вспомогательный класс RewardedAdsVerifier , который сокращает объем кода, необходимого для проверки коллбэка SSV с вознаграждением. Использование этого класса позволяет проверить URL-адрес коллбэка с помощью следующего кода.
RewardedAdsVerifier verifier = new RewardedAdsVerifier.Builder()
.fetchVerifyingPublicKeysWith(
RewardedAdsVerifier.KEYS_DOWNLOADER_INSTANCE_PROD)
.build();
String rewardUrl = ...;
verifier.verify(rewardUrl);
Если метод verify() выполняется без генерации исключения, значит, URL-адрес обратного вызова был успешно проверен. В разделе «Вознаграждение пользователя» подробно описаны лучшие практики в отношении того, когда следует вознаграждать пользователей. Подробное описание шагов, выполняемых этим классом для проверки вознагражденных обратных вызовов SSV, можно найти в разделе «Ручная проверка вознагражденных SSV» .
Параметры обратного вызова SSV
Server-side verification callbacks contain query parameters that describe the rewarded ad interaction. Parameter names, descriptions, and example values are listed below. Parameters are sent in alphabetical order.
| Имя параметра | Описание | Example value |
|---|---|---|
| ad_network | Ad source identifier for the ad source that fulfilled this ad. Ad source names corresponding to ID values are listed in the Ad source identifiers section. | 1953547073528090325 |
| ad_unit | AdMob ad unit ID that was used to request the rewarded ad. | 2747237135 |
| key_id | Key to be used to verify SSV callback. This value maps to a public key provided by the AdMob key server . | 1234567890 |
| reward_amount | Reward amount as specified in the ad unit settings. | 5 |
| reward_item | Reward item as specified in the ad unit settings. | монеты |
| подпись | Signature for SSV callback generated by AdMob. | MEUCIQCLJS_s4ia_sN06HqzeW7Wc3nhZi4RlW3qV0oO-6AIYdQIgGJEh-rzKreO-paNDbSCzWGMtmgJHYYW9k2_icM9LFMY |
| метка времени | Timestamp of when the user was rewarded as Epoch time in ms. | 1507770365237823 |
| transaction_id | Unique hex encoded identifier for each reward grant event generated by AdMob. | 18fa792de1bca816048293fc71035638 |
| ID пользователя | User identifier as provided by SetUserId .If no user identifier is provided by the app, this query parameter will not be present in the SSV callback. | 1234567 |
Ad source identifiers
Ad source names and IDs
| Ad source name | Ad source ID |
|---|---|
| Ad Generation (bidding) | 1477265452970951479 |
| AdMob Network | 5450213213286189855 |
| AdMob Network Waterfall | 1215381445328257950 |
| AppLovin | 1063618907739174004 |
| AppLovin (bidding) | 1328079684332308356 |
| Bidease (bidding) | 3670825090829827805 |
| BidMachine (bidding) | 7943972370566394673 |
| Chartboost | 2873236629771172317 |
| Chocolate Platform (bidding) | 6432849193975106527 |
| Custom Event | 18351550913290782395 |
| DT Exchange* * Prior to September 21, 2022, this network was called "Fyber Marketplace". | 2179455223494392917 |
| DT Exchange (bidding) | 8189833498765234879 |
| Equativ (bidding)* * Prior to January 12, 2023, this network was called "Smart Adserver". | 5970199210771591442 |
| Fluct (bidding) | 8419777862490735710 |
| i-mobile | 5208827440166355534 |
| Improve Digital (bidding) | 159382223051638006 |
| Index Exchange (bidding) | 4100650709078789802 |
| InMobi | 7681903010231960328 |
| InMobi (SDK) (bidding) | 8468954295581492586 |
| InMobi Exchange (bidding) | 5264320421916134407 |
| ironSource Реклама | 6925240245545091930 |
| ironSource Ads (bidding) | 1643326773739866623 |
| 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 |
| LY Ads Network | 3025503711505004547 |
| LY Ads Network (bidding) | 2615812619460460513 |
| Magnite DV+ (bidding) | 3993193775968767067 |
| maio | 7505118203095108657 |
| Media.net (bidding) | 2127936450554446159 |
| Mediated house ads | 6060308706800320801 |
| 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 |
| Mintegral | 1357746574408896200 |
| Mintegral (bidding) | 6250601289653372374 |
| Mobfox (bidding) | 3086513548163922365 |
| MobileFuse (bidding) | 7303547408604090310 |
| Moloco Ads SDK (bidding) | 8267622065755668722 |
| myTarget | 8450873672465271579 |
| Nativo (bidding) | 3240503836211327896 |
| Nexxen (bidding)* * Prior to May 1, 2024, this network was called "UnrulyX". | 2831998725945605450 |
| OneTag Exchange (bidding) | 4873891452523427499 |
| OpenX (bidding) | 4918705482605678398 |
| Pangle | 4069896914521993236 |
| Pangle KR SDK (bidding) | 12171279046073404914 |
| Pangle ROW SDK (bidding) | 3525379893916449117 |
| Pangle US SDK (bidding) | 15999446638585856012 |
| PubMatic (bidding) | 3841544486172445473 |
| PubMatic OpenWrap SDK | 7702975372504485373 |
| PubMatic OpenWrap SDK (bidding) | 1234567890123456789 |
| Reservation campaign | 7068401028668408324 |
| Rise (bidding) | 6816468518946650043 |
| Sharethrough (bidding) | 5247944089976324188 |
| Smaato (bidding) | 3362360112145450544 |
| Sonobi (bidding) | 3270984106996027150 |
| TripleLift (bidding) | 8332676245392738510 |
| Реклама Unity | 4970775877303683148 |
| Unity Ads (bidding) | 7069338991535737586 |
| Verve Group (bidding) | 5013176581647059185 |
| Vpon | 1940957084538325905 |
| Yieldmo (bidding) | 4193081836471107579 |
| YieldOne (bidding) | 3154533971590234104 |
| Zucks | 5506531810221735863 |
Rewarding the user
It is important to balance user experience and reward validation when deciding when to reward a user. Server-side callbacks may experience delays before reaching external systems. Therefore, the recommended best practice is to use the client-side callback to reward the user immediately, while performing validation on all rewards upon the receipt of server-side callbacks. This approach provides a good user experience while ensuring the validity of granted rewards.
However, for applications where reward validity is critical (for example, the reward affects your app's in-game economy) and delays in granting rewards are acceptable, waiting for the verified server-side callback may be the best approach.
Custom data
Apps that require extra data in server-side verification callbacks should use the custom data feature of rewarded ads. Any string value set on a rewarded ad object is passed to the custom_data query parameter of the SSV callback. If no custom data value is set, the custom_data query parameter value won't be present in the SSV callback.
The following code sample demonstrates how to set the SSV options after the rewarded ad is loaded:
private void LoadRewardedAd(string adUnitId)
{
// Send the request to load the ad.
AdRequest adRequest = new AdRequest();
RewardedAd.Load(adUnitId, adRequest, (RewardedAd rewardedAd, LoadAdError error) =>
{
// If the operation failed with a reason.
if (error != null)
{
Debug.LogError("Rewarded ad failed to load an ad with error : " + error);
return;
}
var options = new ServerSideVerificationOptions
.Builder()
.SetCustomData("SAMPLE_CUSTOM_DATA_STRING")
.Build()
rewardedAd.SetServerSideVerificationOptions(options);
});
}
If you want to set the custom reward string, you must do so before showing the ad.
Manual verification of rewarded SSV
The steps performed by the RewardedAdsVerifier class to verify a rewarded SSV are outlined below. Although the included code snippets are in Java and leverage the Tink third-party library, these steps can be implemented by you in the language of your choice, using any third-party library that supports ECDSA .
Fetch public keys
To verify a rewarded SSV callback, you need a public key provided by AdMob.
A list of public keys to be used to validate the rewarded SSV callbacks can be fetched from the AdMob key server . The list of public keys is provided as a JSON representation with a format similar to the following:
{
"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=="
},
],
}
To retrieve the public keys, connect to the AdMob key server and download the keys. The following code accomplishes this task and saves the JSON representation of the keys to the data variable.
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();
}
Note that public keys are regularly rotated. You will receive an email to inform you of an upcoming rotation. If you're caching public keys, you should update the keys upon receiving this email.
Once the public keys have been fetched, they must be parsed. The parsePublicKeysJson method below takes a JSON string, such as the example above, as input, and creates a mapping from key_id values to public keys, which are encapsulated as ECPublicKey objects from the Tink library.
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;
}
Get content to be verified
The last two query parameters of rewarded SSV callbacks are always signature and key_id, in that order. The remaining query parameters specify the content to be verified. Let's assume you configured AdMob to send reward callbacks to https://www.myserver.com/mypath . The snippet below shows an example rewarded SSV callback with the content to be verified highlighted.
https://www.myserver.com/path?ad_network=54...55&ad_unit=12345678&reward_amount=10&reward_item=coins ×tamp=150777823&transaction_id=12...DEF&user_id=1234567&signature=ME...Z1c&key_id=1268887
The code below demonstrates how to parse the content to be verified from a callback URL as a UTF-8 byte array.
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"));
Get signature and key_id from callback URL
Using the queryString value from the previous step, parse the signature and key_id query parameters from the callback URL as shown below:
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()));
Perform verification
The final step is to verify the content of the callback URL with the appropriate public key. Take the mapping returned from the parsePublicKeysJson method and use the key_id parameter from the callback URL to get the public key from that mapping. Then verify the signature with that public key. These steps are demonstrated below in the verify method.
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);
}
}
If the method executes without throwing an exception, the callback URL was successfully verified.
Часто задаваемые вопросы
- Can I cache the public key provided by the AdMob key server?
- We recommend that you cache the public key provided by the AdMob key server to reduce the number of operations required to validate SSV callbacks. However, note that public keys are regularly rotated and should not be cached for longer than 24 hours.
- How frequently are the public keys provided by the AdMob key server rotated?
- Public keys provided by the AdMob key server are rotated on a variable schedule. To ensure that verification of SSV callbacks continues to work as intended, public keys should not be cached for longer than 24 hours.
- What happens if my server can't be reached?
- Google expects an
HTTP 200 OKsuccess status response code for SSV callbacks. If your server cannot be reached or does not provide the expected response, Google will re-attempt to send SSV callbacks up to five times in one-second intervals. - How can I verify that SSV callbacks are coming from Google?
- Use reverse DNS lookup to verify that SSV callbacks originate from Google.