Granting Ad Rewards
Reward users for completing AdMob rewarded ads, verified server-side by RevenueCat
Grant a reward to users when they complete an AdMob rewarded or rewarded interstitial ad. RevenueCat verifies each reward server-side using AdMob Server-Side Verification (SSV) before granting it, so rewards can't be spoofed by a tampered client. You configure what each ad unit grants in the RevenueCat dashboard — no backend of your own required.
Today, verified rewards grant virtual currency. Support for additional reward types may be added in the future.
This feature is currently in beta.
How it works
- A user completes a rewarded ad.
- AdMob sends an SSV callback to RevenueCat, with the user attached by the SDK.
- RevenueCat verifies the reward and grants a reward based on your configuration.
- The SDK reports the verified result to your app.
Prerequisites
- The AdMob Adapter SDK is installed and the RevenueCat SDK is configured.
- SDK minimums:
purchases-ios5.0.0+ (iOS 15+) orpurchases-android8.0.0+. The reward verification APIs are experimental and require explicit opt-in. - Your AdMob account is connected to RevenueCat so your rewarded ad units sync to the dashboard.
Step 1: Configure ad units in AdMob
For each rewarded ad unit you want to grant a reward for, enable Server-side verification in the AdMob console and set the SSV callback URL to:
https://api.revenuecat.com/v1/incoming-webhooks/admob-ssv-rewarded
This is a single, shared endpoint — use the same URL for every ad unit. RevenueCat resolves the ad unit and the user from data the SDK attaches at show time. See Google's guide on setting up SSV for where to enter the URL.

Step 2: Configure the reward in the RevenueCat dashboard
On the Rewards page (under Ads) in your RevenueCat dashboard, add a rule for each rewarded ad unit:
- Select a synced rewarded ad unit.
- Choose the virtual currency to grant.
- Enter the amount (a positive whole number).

RevenueCat grants this amount every time a reward from that ad unit is verified.
Step 3: Implement it in your app
Enable verification on each ad after it loads, then present it with the verification callbacks.
When verification succeeds with a virtual currency reward, the SDK automatically invalidates the local virtual currencies cache before delivering the result. Refetch balances only when your UI needs the updated total — see reading balances.
Rewarded ads
- Swift
- Kotlin
// Opt in to the experimental reward verification APIs.
@_spi(Experimental) import RevenueCatAdMob
RewardedAd.loadAndTrack(
withAdUnitID: "AD_UNIT_ID",
request: Request(),
placement: "bonus_coins",
fullScreenContentDelegate: self
) { ad, error in
if error != nil { return }
guard let ad else { return }
// Enable RevenueCat reward verification before presenting the ad.
ad.enableRewardVerification()
self.rewardedAd = ad
}
// Later, present the ad with verification callbacks:
rewardedAd?.present(
from: self,
rewardVerificationStarted: {
// RevenueCat is verifying the reward server-side.
// Show a loading state here if you gate the reward on the result.
},
rewardVerificationCompleted: { result in
if let virtualCurrency = result.verifiedReward?.virtualCurrency {
// Verified. RevenueCat already granted the reward server-side; the amount
// and currency come from the reward rule you configured in the dashboard.
print("Granted \(virtualCurrency.amount) \(virtualCurrency.code)")
// The SDK has already invalidated the cache. Refetch balances so your
// UI reflects the new total.
Purchases.shared.virtualCurrencies { _, _ in
// Update your UI with the refreshed balances.
}
} else {
// Verification did not succeed — for example AdMob rejected the reward,
// the callback timed out, or a network error occurred. Do not grant the
// reward client-side as a fallback; let the user retry with another ad.
}
}
)
@OptIn(ExperimentalPreviewRevenueCatPurchasesAPI::class)
fun loadRewardedAd(context: Context) {
Purchases.sharedInstance.adTracker.loadAndTrackRewardedAd(
context = context,
adUnitId = "AD_UNIT_ID",
adRequest = AdRequest.Builder().build(),
placement = "bonus_coins",
loadCallback = object : RewardedAdLoadCallback() {
override fun onAdLoaded(ad: RewardedAd) {
// Enable RevenueCat reward verification before presenting the ad.
ad.enableRewardVerification()
rewardedAd = ad
}
override fun onAdFailedToLoad(error: LoadAdError) {
rewardedAd = null
}
},
)
}
// Later, show the ad with verification callbacks:
@OptIn(ExperimentalPreviewRevenueCatPurchasesAPI::class)
fun showRewardedAd(activity: Activity) {
rewardedAd?.show(
activity = activity,
rewardVerificationStarted = {
// RevenueCat is verifying the reward server-side.
// Show a loading state here if you gate the reward on the result.
},
rewardVerificationCompleted = { result ->
rewardedAd = null
val virtualCurrency = result.verifiedReward as? VerifiedReward.VirtualCurrency
if (virtualCurrency != null) {
// Verified. RevenueCat already granted the reward server-side; the amount
// and currency come from the reward rule you configured in the dashboard.
println("Granted ${virtualCurrency.amount} ${virtualCurrency.code}")
// The SDK has already invalidated the cache. Refetch balances so your
// UI reflects the new total.
Purchases.sharedInstance.getVirtualCurrenciesWith(
onError = { /* Handle error */ },
onSuccess = { /* Update your UI with the refreshed balances */ },
)
} else {
// Verification did not succeed — for example AdMob rejected the reward,
// the callback timed out, or a network error occurred. Do not grant the
// reward client-side as a fallback; let the user retry with another ad.
}
},
)
}
Rewarded interstitial ads
- Swift
- Kotlin
// Opt in to the experimental reward verification APIs.
@_spi(Experimental) import RevenueCatAdMob
RewardedInterstitialAd.loadAndTrack(
withAdUnitID: "AD_UNIT_ID",
request: Request(),
placement: "between_levels",
fullScreenContentDelegate: self
) { ad, error in
if error != nil { return }
guard let ad else { return }
// Enable RevenueCat reward verification before presenting the ad.
ad.enableRewardVerification()
self.rewardedInterstitialAd = ad
}
// Later, present the ad with verification callbacks:
rewardedInterstitialAd?.present(
from: self,
rewardVerificationStarted: {
// RevenueCat is verifying the reward server-side.
// Show a loading state here if you gate the reward on the result.
},
rewardVerificationCompleted: { result in
if let virtualCurrency = result.verifiedReward?.virtualCurrency {
// Verified. RevenueCat already granted the reward server-side; the amount
// and currency come from the reward rule you configured in the dashboard.
print("Granted \(virtualCurrency.amount) \(virtualCurrency.code)")
// The SDK has already invalidated the cache. Refetch balances so your
// UI reflects the new total.
Purchases.shared.virtualCurrencies { _, _ in
// Update your UI with the refreshed balances.
}
} else {
// Verification did not succeed — for example AdMob rejected the reward,
// the callback timed out, or a network error occurred. Do not grant the
// reward client-side as a fallback; let the user retry with another ad.
}
}
)
@OptIn(ExperimentalPreviewRevenueCatPurchasesAPI::class)
fun loadRewardedInterstitialAd(context: Context) {
Purchases.sharedInstance.adTracker.loadAndTrackRewardedInterstitialAd(
context = context,
adUnitId = "AD_UNIT_ID",
adRequest = AdRequest.Builder().build(),
placement = "between_levels",
loadCallback = object : RewardedInterstitialAdLoadCallback() {
override fun onAdLoaded(ad: RewardedInterstitialAd) {
// Enable RevenueCat reward verification before presenting the ad.
ad.enableRewardVerification()
rewardedInterstitialAd = ad
}
override fun onAdFailedToLoad(error: LoadAdError) {
rewardedInterstitialAd = null
}
},
)
}
// Later, show the ad with verification callbacks:
@OptIn(ExperimentalPreviewRevenueCatPurchasesAPI::class)
fun showRewardedInterstitialAd(activity: Activity) {
rewardedInterstitialAd?.show(
activity = activity,
rewardVerificationStarted = {
// RevenueCat is verifying the reward server-side.
// Show a loading state here if you gate the reward on the result.
},
rewardVerificationCompleted = { result ->
rewardedInterstitialAd = null
val virtualCurrency = result.verifiedReward as? VerifiedReward.VirtualCurrency
if (virtualCurrency != null) {
// Verified. RevenueCat already granted the reward server-side; the amount
// and currency come from the reward rule you configured in the dashboard.
println("Granted ${virtualCurrency.amount} ${virtualCurrency.code}")
// The SDK has already invalidated the cache. Refetch balances so your
// UI reflects the new total.
Purchases.sharedInstance.getVirtualCurrenciesWith(
onError = { /* Handle error */ },
onSuccess = { /* Update your UI with the refreshed balances */ },
)
} else {
// Verification did not succeed — for example AdMob rejected the reward,
// the callback timed out, or a network error occurred. Do not grant the
// reward client-side as a fallback; let the user retry with another ad.
}
},
)
}
Granted reward events
Rewards earned in debug builds are treated as sandbox events. After a verified grant, confirm it in the Customer History timeline, where it appears as a virtual currency transaction:

Opening the entry shows a VIRTUAL_CURRENCY_TRANSACTION event whose "source" is "ad_reward", which distinguishes ad-reward grants from purchases and other sources. The event also includes an ad_transaction_id matching the reward verification.