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.
How it works
- Get the bid: Obtain a video bid (VAST) with Prebid.js.
- Create the player: Pass the bid to
createRewardPlayer()to open a modal and start loading and playing the ad. - Watch: Once the policy's conditions are met (watched to the end / watched for a set number of seconds, etc.), the reward becomes claimable.
- Grant the reward: Grant the reward via the
onRewardcallback or whenresultresolves. - 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).
<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>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.
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).
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.
| Callback | When it fires |
|---|---|
onImpression | The ad was displayed |
onClick | The ad was clicked |
onReward | The reward was granted |
onClose | The user closed it |
onFail | The ad failed to load or play |
onQuartile | A 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).
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).
createRewardPlayer({
target: "reward-slot",
bid, // VAST containing multiple ads
policy: { pod: { required: "all" } }, // earn by watching all ads
});Banner reward (display)
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 withrender. - Banners have no concept of "to the end," so a time trigger (
minWatch/watchThroughOr) is required.
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);
},
});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.
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.
<!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(orresult.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: falseshows a gate that starts playback with sound on the first tap. - Theme: Use
themeto specifyauto(follows the OS) /ivory(light) /noir(dark), an accent color, and more. See the API reference for details.