Frequency Capping

Control how often users see ads with automatic limits and cooldown timers to ensure quality user experience.

ℹ️ Why Frequency Capping? Showing too many ads can frustrate users and lead to app uninstalls. Frequency capping ensures a balanced experience while maximizing revenue.

How Frequency Capping Works

BZZE Ads SDK enforces three types of frequency limits automatically:

  1. Cooldown Between Ads: 60 seconds minimum between each ad
  2. Hourly Limit: Maximum 10 ads per hour per user
  3. Daily Limit: Maximum 50 ads per day per user

Default Limits

Limit Type Value Enforcement Can Override?
Between Ads (Cooldown) 60 seconds Client + Server ❌ No (fixed)
Per Hour 10 ads Server-side ❌ No (fixed)
Per Day 50 ads Server-side ❌ No (fixed)
⚠️ Note: Frequency limits are per-user global, meaning they apply across all apps using the same user ID. If a user watches 10 ads in Game A, they cannot watch any more ads in Game B within the same hour.

Checking Cooldown Status

Method 1: getCooldownRemaining()

Get the number of seconds until the next ad can be shown:

JavaScript
const cooldown = RewardedAd.getCooldownRemaining();

if (cooldown === 0) {
    console.log('✅ Ready to show ad!');
    RewardedAd.showAd();
} else {
    console.log(`⏳ Wait ${cooldown} seconds`);
    showCooldownTimer(cooldown);
}

Method 2: isAdAvailable()

Simple boolean check if ad can be shown right now:

JavaScript
if (RewardedAd.isAdAvailable()) {
    // Cooldown passed AND ad is preloaded
    RewardedAd.showAd();
} else {
    // Either in cooldown or no ad ready
    console.log('Ad not available');
}

Displaying Cooldown Timer

Basic Countdown

JavaScript
const button = document.getElementById('rewardButton');

function updateButtonState() {
    const cooldown = RewardedAd.getCooldownRemaining();
    
    if (cooldown === 0) {
        button.disabled = false;
        button.textContent = '▶️ Watch Ad';
        button.style.background = '#4CAF50';
    } else {
        button.disabled = true;
        button.textContent = `⏳ Wait ${cooldown}s`;
        button.style.background = '#9E9E9E';
    }
}

// Update every second
setInterval(updateButtonState, 1000);
updateButtonState();

Formatted Countdown

JavaScript
function formatTime(seconds) {
    if (seconds === 0) return 'Ready!';
    if (seconds < 60) return `${seconds}s`;
    
    const mins = Math.floor(seconds / 60);
    const secs = seconds % 60;
    return `${mins}:${secs.toString().padStart(2, '0')}`;
}

function updateCooldown() {
    const cooldown = RewardedAd.getCooldownRemaining();
    const statusText = document.getElementById('cooldownStatus');
    
    statusText.textContent = formatTime(cooldown);
}

setInterval(updateCooldown, 1000);

Progress Bar

JavaScript
const COOLDOWN_DURATION = 60; // 60 seconds
const progressBar = document.getElementById('cooldownProgress');
const progressText = document.getElementById('cooldownText');

function updateProgressBar() {
    const remaining = RewardedAd.getCooldownRemaining();
    const progress = ((COOLDOWN_DURATION - remaining) / COOLDOWN_DURATION) * 100;
    
    progressBar.style.width = `${progress}%`;
    
    if (remaining === 0) {
        progressText.textContent = '✅ Ready!';
        progressBar.style.backgroundColor = '#10b981';
    } else {
        progressText.textContent = `${remaining}s remaining`;
        progressBar.style.backgroundColor = '#f59e0b';
    }
}

setInterval(updateProgressBar, 100);

Handling Rate Limit Errors

Client-Side Detection

JavaScript
RewardedAd.init({
    appId: "YOUR_APP_ID",
    apiKey: "YOUR_API_KEY",
    userId: "user_123",
    
    onNoFill: function(data) {
        if (data.reason === 'RATE_LIMIT') {
            console.log('⚠️ Rate limit reached');
            showMessage('You\'ve watched the maximum ads for now. Try again later!');
        } else if (data.reason === 'COOLDOWN') {
            const cooldown = RewardedAd.getCooldownRemaining();
            showMessage(`Please wait ${cooldown} seconds before the next ad.`);
        } else {
            showMessage('No ads available right now.');
        }
    }
});

Server-Side Rate Limit Response

When server-side limits are hit, the SDK receives a specific error:

JavaScript
RewardedAd.init({
    appId: "YOUR_APP_ID",
    apiKey: "YOUR_API_KEY",
    userId: "user_123",
    
    onError: function(error) {
        if (error.code === 'RATE_LIMIT_HOURLY') {
            console.log('⚠️ Hourly limit (10 ads/hour) reached');
            showMessage('Hourly ad limit reached. Try again in an hour!');
        } else if (error.code === 'RATE_LIMIT_DAILY') {
            console.log('⚠️ Daily limit (50 ads/day) reached');
            showMessage('Daily ad limit reached. Come back tomorrow!');
        }
    }
});

Best Practices

1. Always Show Cooldown Timer

✅ DO: Display countdown timer so users know when next ad is available
JavaScript
// Good: Clear feedback
const cooldown = RewardedAd.getCooldownRemaining();
if (cooldown > 0) {
    showNotification(`Next ad available in ${cooldown}s`);
}
❌ DON'T: Leave users guessing why the button is disabled

2. Disable Button During Cooldown

JavaScript
// Good: Prevent clicks during cooldown
button.disabled = RewardedAd.getCooldownRemaining() > 0;

// Bad: Allow clicks that will fail
button.disabled = false; // Always enabled

3. Offer Alternative Actions

JavaScript
function tryShowAd() {
    const cooldown = RewardedAd.getCooldownRemaining();
    
    if (cooldown > 0) {
        // Offer alternative ways to earn rewards
        showAlternativeOptions([
            'Complete daily quest',
            'Invite friends',
            'Purchase coins'
        ]);
    } else {
        RewardedAd.showAd();
    }
}

4. Respect Global Limits

⚠️ Important: Don't try to bypass limits by:
  • Creating multiple user IDs per user
  • Clearing localStorage to reset cooldown
  • Modifying client-side timers

Limits are enforced server-side. Bypass attempts will be detected and may result in account suspension.

Industry Standards

Recommended Frequency Caps

Industry Between Ads Per Hour Per Day
Mobile Games 30-60s 10-15 ads 30-50 ads
Casual Games 60-120s 6-10 ads 20-30 ads
Social Apps 120-300s 5-8 ads 15-25 ads
BZZE Ads 60s 10 ads 50 ads

BZZE Ads uses industry-standard limits optimized for both user experience and revenue.

Complete Example

JavaScript
// Complete frequency capping implementation
class AdManager {
    constructor() {
        this.button = document.getElementById('adButton');
        this.statusText = document.getElementById('adStatus');
        this.progressBar = document.getElementById('adProgress');
        
        this.initializeAds();
        this.startUIUpdates();
    }
    
    initializeAds() {
        RewardedAd.init({
            appId: "YOUR_APP_ID",
            apiKey: "YOUR_API_KEY",
            userId: "user_123",
            
            onReward: (reward) => {
                console.log('✅ Reward earned!');
                this.grantReward();
            },
            
            onNoFill: (data) => {
                if (data.reason === 'RATE_LIMIT') {
                    this.showMessage('Hourly/daily limit reached!');
                } else if (data.reason === 'COOLDOWN') {
                    this.showMessage('Please wait for cooldown');
                }
            },
            
            onError: (error) => {
                console.error('Ad error:', error);
                if (error.code.includes('RATE_LIMIT')) {
                    this.showMessage('Ad limit reached. Try again later!');
                }
            }
        });
    }
    
    startUIUpdates() {
        setInterval(() => this.updateUI(), 1000);
        this.updateUI();
    }
    
    updateUI() {
        const cooldown = RewardedAd.getCooldownRemaining();
        const available = RewardedAd.isAdAvailable();
        
        if (cooldown === 0 && available) {
            // Ready to show ad
            this.button.disabled = false;
            this.button.textContent = '▶️ Watch Ad for Reward';
            this.button.style.background = '#4CAF50';
            this.statusText.textContent = 'Ready!';
            this.progressBar.style.width = '100%';
            this.progressBar.style.backgroundColor = '#10b981';
        } else if (cooldown > 0) {
            // In cooldown
            this.button.disabled = true;
            this.button.textContent = `⏳ Cooldown: ${cooldown}s`;
            this.button.style.background = '#9E9E9E';
            this.statusText.textContent = `Wait ${cooldown} seconds`;
            
            const progress = ((60 - cooldown) / 60) * 100;
            this.progressBar.style.width = `${progress}%`;
            this.progressBar.style.backgroundColor = '#f59e0b';
        } else {
            // No ad available
            this.button.disabled = true;
            this.button.textContent = '📭 No ads available';
            this.button.style.background = '#9E9E9E';
            this.statusText.textContent = 'Loading ads...';
        }
    }
    
    showAd() {
        const cooldown = RewardedAd.getCooldownRemaining();
        
        if (cooldown > 0) {
            this.showMessage(`Please wait ${cooldown} seconds`);
            return;
        }
        
        RewardedAd.showAd({
            rewardPreview: '100 coins'
        });
    }
    
    grantReward() {
        // Your reward logic here
        console.log('💰 100 coins granted!');
        this.showMessage('You earned 100 coins!');
    }
    
    showMessage(text) {
        alert(text); // Replace with your notification system
    }
}

// Initialize
const adManager = new AdManager();

// Attach to button
document.getElementById('adButton').onclick = () => {
    adManager.showAd();
};

FAQ

Q: Can I customize the frequency limits?

A: No, limits are fixed to ensure quality for all users and advertisers. Contact support if you have special requirements.

Q: Do limits reset at midnight?

A: Daily limits reset 24 hours after the first ad of the day. Hourly limits are rolling (not clock-based).

Q: What happens if a user clears localStorage?

A: Client-side cooldown may reset, but server-side hourly/daily limits remain enforced.

Q: Are limits per-app or per-user?

A: Per-user global. A user who hits the limit in one app cannot watch ads in another app using the same SDK.

Q: Can I detect when limits will reset?

A: The SDK doesn't provide limit reset times, but you can track the time of the first ad impression and calculate accordingly.

Related Topics