Skip to content

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.

Rewarded ad with claim reward flow on mobile

Basic Usage (Imperative)

jsx
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:

jsx
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

FieldTypeDescription
isLoadedbooleanAd is ready to show()
isClosedbooleanAd was just dismissed by the user
earnedRewardbooleanUser played long enough (client-side signal, fires before server verification)
rewardVerifiedbooleanServer-side verification succeeded
rewardTokenstring | null | undefinedVerification token. undefined = not yet verified; string = first verification; null = re-verification
errorSimulaAdError | undefinedLast error from load or show
load(options?) => voidLoad an ad with optional character context
show() => voidPresent 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)

EventDescription
LOADEDAd loaded and ready to show
LOAD_FAILEDAd failed to load. Retry with exponential backoff
DISPLAYEDAd displayed on screen
IMPRESSIONImpression recorded (server impression beacon fired)
DISPLAY_FAILEDAd failed to display. Load a new ad
CLICKEDUser tapped the CTA
PAIDEstimated per-impression revenue available. event.adValue contains the AdValue
CLOSEDUser dismissed the ad. Next ad auto-preloads

Reward Events (SimulaRewardedAdEventType)

EventDescription
EARNED_REWARDUser played long enough. Client-side signal only -- fires before server verification
REWARD_VERIFIEDServer verified the play session. Grant the reward here. event.rewardToken contains the verification token
REWARD_VERIFICATION_FAILEDServer verification failed. Do not grant the reward

Reward Verification Flow

  1. User plays the mini-game for the required duration
  2. EARNED_REWARD fires (client-side play-time threshold met -- not yet verified)
  3. The SDK verifies the play session with the Simula server (in the background)
  4. REWARD_VERIFIED fires with a verification token -- grant the reward here
  5. 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:

jsx
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 with stale -- call load() again.
  • Deduplication: Calling load() with the same parameters within 5 minutes returns duplicate_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.