Реализация рекламы с вознаграждением (Rewarded Ads) в мобильном приложении
Rewarded — единственный рекламный формат, который пользователи смотрят добровольно. Конверсия в просмотр там, где он встроен органично в механику (дополнительная жизнь, бонусная валюта, разблокировка уровня), достигает 40–70% от показов кнопки. Неудивительно, что eCPM у rewarded в 3–10 раз выше интерстициала.
Но у этого формата есть специфика: фиксация момента выдачи награды. Если реализовать неправильно, пользователи получают награды без просмотра рекламы — либо не получают после честного просмотра. Оба варианта разрушают доверие.
Где конкретно ломается логика выдачи наград
Ошибка №1: выдаём награду в onAdDismissed.
// НЕПРАВИЛЬНО
rewardedAd?.fullScreenContentCallback = object : FullScreenContentCallback() {
override fun onAdDismissedFullScreenContent() {
giveReward() // пользователь мог закрыть на 3-й секунде
}
}
onAdDismissedFullScreenContent срабатывает при любом закрытии — и после полного просмотра, и после нажатия кнопки «закрыть» в начале. Награда нужна только в onUserEarnedReward:
rewardedAd?.show(activity) { rewardItem ->
// Этот callback = пользователь досмотрел до конца
val rewardAmount = rewardItem.amount
val rewardType = rewardItem.type
giveReward(rewardType, rewardAmount)
}
Ошибка №2: клиентская верификация.
Выдавать награду прямо в onUserEarnedReward — нормально для простых случаев (добавить жизнь в памяти). Но если награда влияет на серверное состояние (виртуальная валюта, premium-контент) — нужна серверная верификация через SSV (Server-Side Verification).
Схема работы SSV:
- Приложение запрашивает rewarded с
customData— строкой с userId и nonce - После просмотра рекламная сеть (AdMob/IronSource) делает GET-запрос на ваш verification endpoint
- Параметры запроса подписаны ECDSA-ключом сети (публичный ключ можно получить с
https://www.gstatic.com/admob/reward/verifier-keys.json) - Ваш сервер верифицирует подпись, начисляет награду
- Клиент получает подтверждение по WebSocket или polling
// Передаём userId в customData при загрузке
val serverSideVerificationOptions = ServerSideVerificationOptions.Builder()
.setCustomData("userId:${currentUser.id};nonce:${UUID.randomUUID()}")
.build()
val adRequest = AdRequest.Builder().build()
RewardedAd.load(context, AD_UNIT_ID, adRequest, object : RewardedAdLoadCallback() {
override fun onAdLoaded(ad: RewardedAd) {
ad.setServerSideVerificationOptions(serverSideVerificationOptions)
rewardedAd = ad
}
})
SSV критичен для любого приложения, где валюта конвертируется в реальные ценности или влияет на PvP-баланс.
UX-управление показом
Кнопку «Посмотреть рекламу» нужно показывать только когда реклама загружена — то есть когда rewardedAd != null. Серая неактивная кнопка хуже, чем скрытая. Загрузку начинаем заблаговременно: сразу после предыдущего показа или при открытии экрана, где кнопка может появиться.
Если загрузка не удалась — скрываем кнопку, не показываем ошибку пользователю. Retry с экспоненциальным backoff (1 → 2 → 4 → 8 сек) реализуем тихо в фоне.
Rewarded Interstitial
Менее известный, но полезный формат — RewardedInterstitialAd. В отличие от rewarded, он не требует явного согласия пользователя перед показом (нет opt-in диалога), но всё равно даёт награду за досмотр. Хорошо работает в точках естественных пауз: старт уровня, загрузочный экран. eCPM ниже rewarded, но выше interstitial.
Сроки
Базовая реализация rewarded без SSV — 1–2 дня. С серверной верификацией и интеграцией в игровую экономику — 2–3 дня. Стоимость — по итогам анализа проекта и требований к верификации.







