RewardedAd
Gates an in-app reward behind a mini-game. The user plays for a minimum duration, then claims their reward. Reward verification happens server-side for tamper resistance.

Basic Usage (Imperative)
import { useEffect, useRef, useState } from 'react';
import {
SimulaRewardedAd,
SimulaAdEventType,
SimulaRewardedAdEventType,
} from '@simula/ads-react-native';
function MessageLimitGate() {
const [loaded, setLoaded] = useState(false);
const retryAttempt = useRef(0);
const rewarded = useRef(null);
useEffect(() => {
rewarded.current = SimulaRewardedAd.create('SIM-RWD-XXXXXXXX');
const subs = [
rewarded.current.addAdEventListener(SimulaAdEventType.LOADED, () => {
retryAttempt.current = 0;
setLoaded(true);
}),
rewarded.current.addAdEventListener(SimulaAdEventType.LOAD_FAILED, () => {
retryAttempt.current += 1;
if (retryAttempt.current > 6) return;
const delay = Math.pow(2, retryAttempt.current);
setTimeout(() => rewarded.current.load(), delay * 1000);
}),
rewarded.current.addAdEventListener(
SimulaRewardedAdEventType.REWARD_VERIFIED,
(event) => {
// Server verified the play — grant the reward here
grantMessageCredits(10, event.rewardToken);
}
),
rewarded.current.addAdEventListener(
SimulaRewardedAdEventType.REWARD_VERIFICATION_FAILED,
() => {
showError('Reward verification failed. Please try again.');
}
),
rewarded.current.addAdEventListener(SimulaAdEventType.CLOSED, () => {
setLoaded(false);
}),
];
rewarded.current.load({
charId: 'reze-01',
charName: 'Reze',
charImage: 'https://cdn.example.com/avatars/reze.png',
});
return () => {
subs.forEach((unsub) => unsub());
rewarded.current.destroy();
};
}, []);
return (
<Button
title="Watch an ad to earn 10 credits"
disabled={!loaded}
onPress={() => rewarded.current.show()}
/>
);
}Basic Usage (Hook)
The useRewardedAd hook manages the ad lifecycle automatically:
import { useEffect } from 'react';
import { useRewardedAd } from '@simula/ads-react-native';
function MessageLimitGate() {
const {
isLoaded,
rewardVerified,
rewardToken,
error,
load,
show,
} = useRewardedAd('SIM-RWD-XXXXXXXX');
useEffect(() => {
load({ charId: 'reze-01', charName: 'Reze' });
}, [load]);
useEffect(() => {
if (rewardToken !== undefined) {
// token is a string on first verification, null on re-verification
grantMessageCredits(10, rewardToken);
}
}, [rewardToken]);
return (
<Button title="Earn 10 credits" disabled={!isLoaded} onPress={show} />
);
}Hook Return Values
| Field | Type | Description |
|---|---|---|
isLoaded | boolean | Ad is ready to show() |
isClosed | boolean | Ad was just dismissed by the user |
earnedReward | boolean | User played long enough (client-side signal, fires before server verification) |
rewardVerified | boolean | Server-side verification succeeded |
rewardToken | string | null | undefined | Verification token. undefined = not yet verified; string = first verification; null = re-verification |
error | SimulaAdError | undefined | Last error from load or show |
load | (options?) => void | Load an ad with optional character context |
show | () => void | Present the loaded ad |
AI Character Integration
Same as InterstitialAd — pass your app's character data so the character plays alongside the user in the ad playable experience. The character commentates, competes, and engages with the user during gameplay, improving the ad experience and raising payout.
Event Types
Common Events (SimulaAdEventType)
| Event | Description |
|---|---|
LOADED | Ad loaded and ready to show |
LOAD_FAILED | Ad failed to load. Retry with exponential backoff |
DISPLAYED | Ad displayed on screen |
IMPRESSION | Impression recorded (server impression beacon fired) |
DISPLAY_FAILED | Ad failed to display. Load a new ad |
CLICKED | User tapped the CTA |
PAID | Estimated per-impression revenue available. event.adValue contains the AdValue |
CLOSED | User dismissed the ad. Next ad auto-preloads |
Reward Events (SimulaRewardedAdEventType)
| Event | Description |
|---|---|
EARNED_REWARD | User played long enough. Client-side signal only -- fires before server verification |
REWARD_VERIFIED | Server verified the play session. Grant the reward here. event.rewardToken contains the verification token |
REWARD_VERIFICATION_FAILED | Server verification failed. Do not grant the reward |
Reward Verification Flow
- User plays the mini-game for the required duration
EARNED_REWARDfires (client-side play-time threshold met -- not yet verified)- The SDK verifies the play session with the Simula server (in the background)
REWARD_VERIFIEDfires with a verification token -- grant the reward here- If you use Server-Side Verification (SSV), Simula's server also sends a POST to your callback URL
Listening to All Events
Use addAdEventsListener to subscribe to every event on an ad instance with a single listener. Useful for analytics or debug logging:
rewarded.current.addAdEventsListener((event) => {
analytics.track('ad_event', { type: event.type, adUnit: 'SIM-RWD-XXXXXXXX' });
});Grant rewards on REWARD_VERIFIED, not EARNED_REWARD
REWARD_VERIFIED is the trusted signal -- it fires after the server verifies the play session and includes a rewardToken for your records. EARNED_REWARD is a client-side signal that fires before verification completes. Verification is durable: the native SDK retries in the background and may deliver REWARD_VERIFIED after the ad closes or even after app relaunch.
Lifecycle
- Auto-preload on close: The SDK automatically preloads the next ad when the user dismisses the current one.
- 1-hour expiry: A loaded ad expires after 1 hour.
show()fails withstale-- callload()again. - Deduplication: Calling
load()with the same parameters within 5 minutes returnsduplicate_request. Wait for the retry window or change the character context. - Cleanup: Always call
destroy()when using the imperative API. The hook handles cleanup automatically.
