Skip to main content

Rewarded Ads

Rewarded ads give players in-game rewards (coins, lives, power-ups) in exchange for watching an advertisement. These ads have the highest engagement and revenue potential.

Overview

Use Cases:

  • Extra lives
  • Bonus coins/currency
  • Power-ups and boosters
  • Hint/skip level
  • Double rewards

Characteristics:

  • Opt-in (player chooses to watch)
  • Server-side validation
  • Frequency capping
  • High eCPM
  • Display or video format

Basic Usage

Display Rewarded (GPT)

sdk.showRewarded({
placement: 'extra_lives',
rewardType: 'lives',
rewardAmount: 3,
onRewarded: (reward) => {
if (reward.granted) {
// Server validated - give reward
player.lives += 3;
showMessage('You earned 3 lives!');
} else {
showMessage('Reward not available right now.');
}
},
onError: (error) => {
console.warn('Rewarded ad error:', error);
showMessage('Ad not available.');
}
});

Video Rewarded (IMA)

sdk.showRewardedVideo({
placement: 'bonus_coins',
rewardType: 'coins',
rewardAmount: 100,
onRewarded: (reward) => {
if (reward.granted) {
player.coins += 100;
showMessage('You earned 100 coins!');
}
}
}, 'https://pubads.g.doubleclick.net/gampad/ads?...');

Configuration Options

RewardedConfig Interface

interface RewardedConfig {
/** Placement identifier for reporting (required) */
placement: string;

/** Reward type identifier (required) */
rewardType: string;

/** Reward amount (required) */
rewardAmount: number;

/** Ad unit path (optional) */
adUnitPath?: string;

/** Custom targeting (optional) */
targeting?: Record<string, string | string[]>;

/** Callbacks */
onRewarded: (reward: Reward) => void; // Required
onError?: (error: Error) => void;
onLoad?: () => void;
onImpression?: () => void;
onClose?: () => void;
}

interface Reward {
granted: boolean;
rewardType: string;
rewardAmount: number;
message?: string;
}

Complete Examples

Example 1: Extra Lives

class Game {
showExtraLivesOffer() {
// Show UI button
const button = this.createButton('Watch Ad for 3 Lives');

button.onclick = async () => {
// Disable button while loading
button.disabled = true;
button.textContent = 'Loading...';

await sdk.showRewardedVideo({
placement: 'extra_lives',
rewardType: 'lives',
rewardAmount: 3,
onRewarded: (reward) => {
if (reward.granted) {
// Server validated - grant reward
this.grantLives(3);
this.showNotification('You earned 3 lives!', 'success');
button.remove(); // Remove offer after use
} else {
// Reward denied (frequency cap, fraud detection, etc.)
this.showNotification(
reward.message || 'Reward not available right now.',
'warning'
);
button.disabled = false;
button.textContent = 'Try Again Later';
}
},
onError: (error) => {
// Ad failed to load
console.warn('Rewarded ad error:', error);
this.showNotification('Ad not available.', 'error');
button.disabled = false;
button.textContent = 'Watch Ad for 3 Lives';
}
}, this.getRewardedVideoUrl());
};
}

grantLives(amount) {
this.player.lives += amount;
this.saveProgress();
this.updateUI();
}
}

Example 2: Double Rewards

class RewardScreen {
showDoubleCoinOffer(baseCoins) {
const doubleCoins = baseCoins * 2;

// Show offer UI
this.showOfferDialog({
title: 'Double Your Coins!',
message: `Watch an ad to get ${doubleCoins} coins instead of ${baseCoins}`,
acceptLabel: 'Watch Ad',
declineLabel: 'No Thanks',
onAccept: async () => {
await sdk.showRewardedVideo({
placement: 'double_reward',
rewardType: 'coins',
rewardAmount: doubleCoins,
targeting: {
base_reward: baseCoins.toString(),
multiplier: '2'
},
onRewarded: (reward) => {
if (reward.granted) {
// Grant doubled amount
this.grantCoins(doubleCoins);
this.showCelebration();
} else {
// Fallback to base amount
this.grantCoins(baseCoins);
}
this.close();
},
onError: () => {
// Ad failed - give base amount
this.grantCoins(baseCoins);
this.close();
}
}, this.getRewardedVideoUrl());
},
onDecline: () => {
// User declined - give base amount
this.grantCoins(baseCoins);
this.close();
}
});
}
}

Example 3: Hint System

class PuzzleGame {
async showHint() {
// Check if player has hints
if (this.player.hints > 0) {
this.useHint();
this.player.hints--;
return;
}

// No hints - offer rewarded ad
const accepted = await this.showDialog({
title: 'Out of Hints!',
message: 'Watch an ad to get a hint?',
buttons: ['Watch Ad', 'Cancel']
});

if (accepted) {
await sdk.showRewardedVideo({
placement: 'hint_unlock',
rewardType: 'hint',
rewardAmount: 1,
targeting: {
level: this.currentLevel.toString(),
difficulty: this.difficulty
},
onRewarded: (reward) => {
if (reward.granted) {
this.useHint();
this.showNotification('Hint revealed!');
} else {
this.showNotification('Hint not available. Try again later.');
}
}
}, this.getRewardedVideoUrl());
}
}
}

Server-Side Validation

All rewarded ads are validated server-side to prevent fraud:

sdk.showRewardedVideo({
placement: 'bonus_coins',
rewardType: 'coins',
rewardAmount: 100,
onRewarded: (reward) => {
// reward.granted is determined by backend validation:
// 1. Ad was fully watched
// 2. Session is valid
// 3. Frequency cap not exceeded
// 4. No fraud detected

if (reward.granted) {
// ✅ Safe to grant reward
grantCoins(100);
} else {
// ❌ Validation failed
console.log('Reward denied:', reward.message);
// Common reasons:
// - "Frequency cap exceeded" (too many rewards recently)
// - "Invalid session" (session expired)
// - "Reward already claimed" (duplicate request)
}
}
});

Validation Flow

1. Player clicks "Watch Ad"
2. SDK requests reward from backend
3. Backend creates reward validation record
4. Ad is shown to player
5. Player watches ad to completion
6. SDK notifies backend of completion
7. Backend validates:
- Ad was watched fully
- Session is valid
- Frequency cap OK
- No fraud indicators
8. Backend marks reward as granted
9. SDK calls onRewarded(reward)
10. Game grants reward to player

Frequency Capping

Prevent reward farming with frequency caps:

class RewardManager {
constructor() {
this.rewardCounts = new Map();
this.rewardLimits = {
lives: { max: 3, period: 3600000 }, // 3 per hour
coins: { max: 5, period: 3600000 }, // 5 per hour
hint: { max: 10, period: 86400000 }, // 10 per day
powerup: { max: 2, period: 7200000 } // 2 per 2 hours
};
}

canClaimReward(rewardType) {
const limit = this.rewardLimits[rewardType];
if (!limit) return true;

const rewards = this.rewardCounts.get(rewardType) || [];
const now = Date.now();
const cutoff = now - limit.period;

// Remove old rewards
const recent = rewards.filter(time => time > cutoff);
this.rewardCounts.set(rewardType, recent);

return recent.length < limit.max;
}

recordReward(rewardType) {
const rewards = this.rewardCounts.get(rewardType) || [];
rewards.push(Date.now());
this.rewardCounts.set(rewardType, rewards);
}

async showRewardedAd(config) {
// Check local frequency cap
if (!this.canClaimReward(config.rewardType)) {
this.showMessage('You\'ve claimed too many rewards recently. Try again later.');
return false;
}

// Show ad
await sdk.showRewardedVideo({
...config,
onRewarded: (reward) => {
if (reward.granted) {
this.recordReward(config.rewardType);
config.onSuccess(reward);
} else {
// Server-side frequency cap
this.showMessage(reward.message || 'Reward not available.');
}
}
}, this.getRewardedVideoUrl());

return true;
}
}

// Usage
const rewardManager = new RewardManager();

rewardManager.showRewardedAd({
placement: 'extra_lives',
rewardType: 'lives',
rewardAmount: 3,
onSuccess: (reward) => {
grantLives(3);
}
});

Best Practices

1. Clear Value Proposition

Show players exactly what they'll earn:

// ✅ Good - clear offer
showButton({
text: 'Watch Ad → Get 100 Coins',
icon: 'video-icon',
onclick: () => showRewardedAd()
});

// ❌ Bad - vague offer
showButton({
text: 'Watch Video',
onclick: () => showRewardedAd()
});

2. Strategic Placement

Offer rewards at optimal moments:

// ✅ Good timing
onGameOver()"Watch ad for continue?"
onLevelFailed()"Watch ad for hint?"
onLowResources()"Watch ad for coins?"
onShopVisit()"Watch ad for discount?"

// ❌ Bad timing
onGameStart() → Too early
onEveryClick() → Too frequent
onRandomly() → Confusing

3. Graceful Degradation

Always handle failures:

async function offerRewardedAd() {
try {
await sdk.showRewardedVideo({
placement: 'extra_lives',
rewardType: 'lives',
rewardAmount: 3,
onRewarded: (reward) => {
if (reward.granted) {
grantLives(3);
} else {
// Show alternative (IAP, wait timer, etc.)
showAlternativeOptions();
}
},
onError: () => {
showAlternativeOptions();
}
}, videoUrl);
} catch (error) {
// SDK error
showAlternativeOptions();
}
}

4. Reward Balance

Don't make rewarded ads too generous:

// ✅ Good - balanced rewards
const REWARDS = {
lives: 3, // Enough to continue, not too many
coins: 100, // ~10% of level earnings
hint: 1, // One hint per ad
powerup: 1 // One powerup per ad
};

// ❌ Bad - too generous
const REWARDS = {
lives: 99, // Infinite lives = no challenge
coins: 10000, // Breaks economy
hint: 999, // No reason to solve puzzles
powerup: 100 // Way too many
};

Advanced Features

Dynamic Rewards

Adjust rewards based on context:

class DynamicRewards {
getRewardAmount(rewardType, context) {
switch (rewardType) {
case 'lives':
// More lives on harder levels
return context.level > 10 ? 5 : 3;

case 'coins':
// Scale with player level
return Math.floor(100 * (1 + context.playerLevel * 0.1));

case 'hint':
// Always 1 hint
return 1;

default:
return 1;
}
}

async showContextualReward(rewardType) {
const context = {
level: this.currentLevel,
playerLevel: this.player.level,
difficulty: this.difficulty
};

const amount = this.getRewardAmount(rewardType, context);

await sdk.showRewardedVideo({
placement: `${rewardType}_dynamic`,
rewardType,
rewardAmount: amount,
targeting: {
level: context.level.toString(),
player_level: context.playerLevel.toString()
},
onRewarded: (reward) => {
if (reward.granted) {
this.grantReward(rewardType, amount);
}
}
}, this.getRewardedVideoUrl());
}
}

Reward Multipliers

Special events with bonus rewards:

class RewardMultiplier {
constructor() {
this.multiplier = this.getActiveMultiplier();
}

getActiveMultiplier() {
const now = new Date();
const hour = now.getHours();

// Happy hour multiplier
if (hour >= 18 && hour < 20) {
return 2.0; // 2x rewards 6-8 PM
}

// Weekend bonus
if (now.getDay() === 0 || now.getDay() === 6) {
return 1.5; // 1.5x on weekends
}

return 1.0;
}

async showBoostedReward(baseAmount) {
const finalAmount = Math.floor(baseAmount * this.multiplier);

if (this.multiplier > 1) {
this.showNotification(
`${this.multiplier}x Bonus Active! Earn ${finalAmount} coins!`,
'success'
);
}

await sdk.showRewardedVideo({
placement: 'bonus_coins_multiplier',
rewardType: 'coins',
rewardAmount: finalAmount,
targeting: {
multiplier: this.multiplier.toString(),
base_amount: baseAmount.toString()
},
onRewarded: (reward) => {
if (reward.granted) {
this.grantCoins(finalAmount);
if (this.multiplier > 1) {
this.showCelebration();
}
}
}
}, this.getRewardedVideoUrl());
}
}

Troubleshooting

Reward Not Granted

Problem: Player watches ad but reward.granted = false

Common Reasons:

  1. Frequency cap exceeded
  2. Session expired
  3. Duplicate request
  4. Fraud detection triggered

Solution:

onRewarded: (reward) => {
if (!reward.granted) {
console.log('Reward denied:', reward.message);

// Show user-friendly message
switch (reward.message) {
case 'Frequency cap exceeded':
showMessage('You\'ve earned too many rewards recently. Try again in 1 hour.');
break;
case 'Invalid session':
showMessage('Session expired. Please refresh the page.');
break;
default:
showMessage('Reward not available right now.');
}
}
}

Video Won't Load

Problem: Rewarded video doesn't start

Solutions:

  1. Check VAST URL is correct
  2. Verify network connection
  3. Test on different device/browser
  4. Check browser console for errors
sdk.showRewardedVideo({
placement: 'test',
rewardType: 'test',
rewardAmount: 1,
onRewarded: (reward) => console.log('Success:', reward),
onError: (error) => {
console.error('Video error:', error);
// Try fallback to display ad
sdk.showRewarded({ ... });
}
}, videoUrl);

Testing

Development Mode

await sdk.init({
environment: 'development' // Test ads, no frequency caps
});

sdk.showRewardedVideo({
placement: 'test_reward',
rewardType: 'test',
rewardAmount: 999,
onRewarded: (reward) => {
console.log('Test reward:', reward);
// Always granted in development
}
}, testVideoUrl);

Production Testing

// Enable debug logging
sdk.showRewardedVideo({
placement: 'bonus_coins',
rewardType: 'coins',
rewardAmount: 100,
onLoad: () => console.log('[DEBUG] Video loaded'),
onImpression: () => console.log('[DEBUG] Video started'),
onRewarded: (reward) => {
console.log('[DEBUG] Reward result:', reward);
if (reward.granted) {
console.log('[DEBUG] Granting 100 coins');
} else {
console.warn('[DEBUG] Reward denied:', reward.message);
}
},
onError: (error) => console.error('[DEBUG] Video error:', error)
}, videoUrl);


Support

Need help with rewarded ads? Contact: support@getjar.com