Skip to content

Add a reward ad

A reward ad is an ad format where users earn a reward by watching the ad to the end. With Michao Player, you pass a video bid obtained from Prebid.js, and the player handles everything from detecting completion to granting the reward.

Launches the reward player with a sample ad.

How it works

  1. Get the bid: Obtain a video bid (VAST) with Prebid.js.
  2. Create the player: Pass the bid to createRewardPlayer() to open a modal and start loading and playing the ad.
  3. Watch: Once the policy's conditions are met (watched to the end / watched for a set number of seconds, etc.), the reward becomes claimable.
  4. Grant the reward: Grant the reward via the onReward callback or when result resolves.
  5. Errors: On no-fill or a load failure, handle it in onFail (no reward is granted).

Quick start

Pass a Prebid video bid (vastXml or vastUrl).

html
<div id="reward-slot"></div>
<script src="https://cdn.michao-ssp.com/player/v1/reward.js"></script>
<script>
  const handle = window.michao.createRewardPlayer({
    target: "reward-slot", // element ID or HTMLElement
    bid, // Prebid video bid
    reward: "50 coins", // optional: emphasized with the accent color
    policy: { trigger: { type: "watchThroughOr", seconds: 15 } },
    callbacks: {
      onReward: () => grantReward(), // grant the reward
      onClose: () => {},
    },
  });
</script>
ts
import { createRewardPlayer } from "https://cdn.michao-ssp.com/player/v1/reward.mjs";

const handle = createRewardPlayer({
  target: "reward-slot",
  bid,
  reward: "50 coins",
  policy: { trigger: { type: "watchThroughOr", seconds: 15 } },
  callbacks: {
    onReward: () => grantReward(),
    onClose: () => {},
  },
});

Awaiting the result

Instead of callbacks, you can await the final result with the result Promise. The result resolves exactly once.

ts
const result = await handle.result;
// { status: "rewarded" | "dismissed" | "failed", reason?: string }

if (result.status === "rewarded") {
  grantReward();
}

handle.destroy(); // destroy early (resolves as "dismissed" if still pending)

Shape of the bid

Pass a subset of a Prebid video bid. Specify VAST with either vastXml (inline XML) or vastUrl (tag URL).

ts
const bid = {
  mediaType: "video",
  adUnitCode: "reward-slot",
  vastXml: "<VAST>…</VAST>", // or vastUrl: "https://…"
  width: 640,
  height: 360,
};

VAST comes from a video imp

VAST is only available from a video bid. Run a video adapter in Prebid. A banner imp returns markup, not VAST.

Events (callbacks)

Subscribe to each event via callbacks. Use them for measurement and granting rewards.

CallbackWhen it fires
onImpressionThe ad was displayed
onClickThe ad was clicked
onRewardThe reward was granted
onCloseThe user closed it
onFailThe ad failed to load or play
onQuartileA playback milestone (first / mid / third / complete)

Reward policy

Use policy to control "when the reward is earned" as well as the close and claim behavior. The default is earn by watching to the end (must-watch).

ts
createRewardPlayer({
  target: "reward-slot",
  bid,
  policy: {
    // earn by watching a set number of seconds, or by watching to the end
    trigger: { type: "watchThroughOr", seconds: 15 },
    // skip disabled (must-watch)
    skip: { mode: "disabled" },
  },
});

For long ads

The default is "watch to the end." For 60-second-class creatives, use minWatch or watchThroughOr so the reward can be earned before the ad finishes.

For details on each option, see the API reference.

Ad pods (multiple ads)

When the VAST returns multiple ads (an ad pod with sequence), each ad plays in order. Use policy.pod to specify how many ads in the pod must be watched to earn the reward (the default is 1; "all" requires every ad).

ts
createRewardPlayer({
  target: "reward-slot",
  bid, // VAST containing multiple ads
  policy: { pod: { required: "all" } }, // earn by watching all ads
});
Two ads play in sequence; finish both to claim the reward.

Beyond video, you can serve rewards with banner (display) bids as well. However, the caller renders the creative (Prebid renderAd / GPT). The player takes no responsibility for rendering the ad and handles only the "earn after N seconds displayed" gating.

  • Pass mediaType: "banner" in the bid and render into the slot with render.
  • Banners have no concept of "to the end," so a time trigger (minWatch / watchThroughOr) is required.
ts
createRewardPlayer({
  target: "reward-slot",
  bid, // banner bid (uses width / height)
  policy: { trigger: { type: "minWatch", seconds: 15 } },
  // Render the creative yourself (the player does not render it).
  // slot is the render-target element itself (no element id needed). The bid carries adId.
  render: (slot, bid) => {
    const iframe = document.createElement("iframe");
    iframe.style.cssText = "width:100%;height:100%;border:0";
    slot.appendChild(iframe);
    window.pbjs.renderAd(iframe.contentWindow.document, bid.adId);
    // For GPT: slot.id = bid.adUnitCode; googletag.display(slot.id);
  },
});
View the banner drawn by render for 5 s to claim.

About measurement

Display measurement (viewability, etc.) follows the mechanisms on the Prebid / ad server side. The player handles neither rendering nor measurement.

Showing it multiple times

Each call to createRewardPlayer() creates one instance. To show it again, call it again. If a previous handle is still around, destroy() it before creating a new one.

ts
let handle = null;

function showRewardAd() {
  handle?.destroy();
  handle = createRewardPlayer({ target: "reward-slot", bid, callbacks: { onReward } });
}

Full example

A minimal example that shows a reward ad on button click and grants coins on completion.

html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Reward ad demo</title>
    <script src="https://cdn.michao-ssp.com/player/v1/reward.js"></script>
  </head>
  <body>
    <h1>Coins: <span id="coins">0</span></h1>
    <button id="watch">Watch the video to earn 50 coins</button>
    <div id="reward-slot"></div>

    <script>
      let coins = 0;

      document.getElementById("watch").addEventListener("click", () => {
        // bid is a video bid obtained from Prebid
        window.michao.createRewardPlayer({
          target: "reward-slot",
          bid,
          reward: "50 coins",
          policy: { trigger: { type: "watchThroughOr", seconds: 15 } },
          callbacks: {
            onReward: () => {
              coins += 50;
              document.getElementById("coins").textContent = coins;
            },
            onFail: () => alert("Could not load the ad. Please try again later."),
          },
        });
      });
    </script>
  </body>
</html>

Technical notes

  • Error handling: To guard against no-fill and network errors, always handle onFail (or result.status === "failed"). A measurement (OMID) failure does not count as a reward failure.
  • Muted playback: The default is muted autoplay, and the user can turn the sound on. Setting muted: false shows a gate that starts playback with sound on the first tap.
  • Theme: Use theme to specify auto (follows the OS) / ivory (light) / noir (dark), an accent color, and more. See the API reference for details.

Michao!! player documentation