Events & measurement
The SDK measures everything for you; you never send an event by hand. This page explains what each event means, which ones brands pay for, and how to listen to them in your own code.
The events
Every filled ad request gets a signed token. The SDK reports events against that token to POST /v1/event, and each event type counts at most once per ad, so a player who collects ten cans is one engagement, not ten.
| Event | Bonus Round (takeover) | Ambient prop |
|---|---|---|
request | The SDK asked for an ad. Counted whether or not it filled; fill rate is fills ÷ requests. | |
impression | The round started on screen. | The prop was first rendered on screen (any visible part, not hidden behind something). |
viewable | The round (which fills the whole screen) was showing in a visible tab for 2 continuous seconds. | The IAB/MRC in-game rule: at least 50% of the prop's pixels in view and not hidden, the visible prop at least 1.5% of the screen, a viewing angle of at most 55°, for 1 continuous second in a visible tab (2 seconds for video or animated surfaces). |
start | The round began (sent with the impression). | n/a |
engagement | The player entered the round and touched at least one item. | The player walked up to the prop (needs native mode, where the SDK knows the player's position). |
complete | The round played to the end and the sponsored leaderboard showed. | n/a |
click | The player clicked the brand's call to action. | |
reward | A rewarded round was completed and your onReward ran. | n/a |
What's billed
Brands choose how they pay. Only that one event type is billed for their ads; everything else is measured for reporting.
| Bid strategy | Billed on | Typical use |
|---|---|---|
vcpm | viewable, priced per 1,000 | Awareness; ambient props |
cpe | engagement: a player who actually played | The default for Bonus Rounds |
cpc | click on the call to action | Traffic |
- For every billed event, you earn 70% of the net revenue and Bonus Round keeps 30%. Net revenue is the cost, minus a disclosed 15% agency commission when an ad agency placed the campaign. It's written to an append-only ledger at the moment it happens.
- Test-mode traffic and
break('test')rounds are recorded, but never billed and never paid. - A brand's spend stops at a zero balance, so you're never credited for an event nobody paid for.
Viewability
We follow the IAB / IAB Tech Lab / MRC Intrinsic In-Game Measurement Guidelines 2.0 (2022). The SDK measures in your game, and the ad server checks the measurement again before a viewable counts or bills. We say "aligned with IIG 2.0", not "accredited": no third party has audited us yet.
| Ad | Pixels in view | Size on screen | Viewing angle | Continuous time |
|---|---|---|---|---|
| Ambient prop (billboard, statue, portal arch) | ≥ 50% | ≥ 1.5% of the screen | ≤ 55° on any axis | 1 second |
| Video or animated surface | ≥ 50% | ≥ 1.5% of the screen | ≤ 55° on any axis | 2 seconds |
| Bonus Round (full takeover) | The round is the whole screen, so it's always 100% in view and face-on | 2 seconds, in a visible tab | ||
- How it's measured. The SDK projects the prop's real 3D bounds through your camera to get its on-screen area, clips it to the viewport, and casts sample rays across it every 200 ms to tell the prop's pixels from empty space and to find anything in front of it (walls, terrain, the player's avatar). See-through and invisible objects don't count as blocking.
- Angle. The angle between the line of sight and the front of the ad, horizontally and vertically. Billboards count from the front only, portal surfaces from both sides, and 3D statues from any side.
- Continuous. Any 200 ms check that fails, a hidden tab or a stalled frame loop restarts the clock. Each ad counts as viewable at most once.
- Takeovers. IIG 2.0 leaves interstitials and advergames out of scope, so this is our own definition: the round covers the screen, and it counts after 2 continuous seconds on screen (the video duration, because rounds are animated). Completion is the main quality metric for rounds.
- Custom integrations. A
viewablesent toPOST /v1/eventfrom a browser must carry the measurement,vw: { std: "iig2", rule: "display" | "video" | "takeover", ms, px, cov, ang }(pixels and screen share as 0–1 fractions, angle in degrees). Anything below the rule is refused with a 422 and never billed. - Viewable events recorded before 6 Oct 2026 keep their original values and are marked as pre-IIG in our data; they aren't recomputed.
Listening to SDK events
Use BonusRound.on(type, cb) to react in your game: duck your music, pause a timer, log to your own analytics.
BonusRound.on('start', (e) => { music.duck(); console.log('Bonus Round by', e.brand); });
BonusRound.on('end', (e) => { music.unduck(); analytics.track('bonus_round', e); });
BonusRound.on('reward', (e) => console.log('reward granted', e));
| Type | When | Payload |
|---|---|---|
attach | attach() finished | { mode } |
start | A Bonus Round started | { format, trigger, brand, requestId, test } |
end | A Bonus Round ended (whatever started it) | { filled, completed, score, trigger, requestId, brand } |
reward | A rewarded round was completed | the same shape as end |
ambient | The ambient prop was placed | { placed, kind, brand } |
event | Any measurement event was reported | { type, value } |
start and end also fire for interval rounds and portal rounds that your code didn't start, which makes them the right place to pause and resume game rules.
Reports
Your game's dashboard and GET /api/games/:id/stats?days=30 show requests, fills, fill rate, impressions, viewable impressions, engagements, clicks, earnings and eCPM, by day, by brand and by format. Money is in micros (1 USD = 1,000,000).
Live, the game:<id> event stream (GET /api/live/game:<id>, server-sent events) carries sdk_ping, status, test_ad and earning events as they happen.
Privacy
The SDK sets no cookies. In contextual-only mode it stores nothing at all and uses a random id that lives for one page load (s_…), only to cap how often the same player sees a round in that session. The ad server keeps only a daily-rotating hash of it, never puts it on events, and uses no user-level or behavioural signals. IP addresses are never stored.
Contextual-only mode is on when any of these is true:
- you answered "Is this game made for kids?" with Yes or Mixed audience, or haven't answered yet;
- the game is rated Everyone or Everyone 10+, or has no content rating yet, or our agent saw signs it's made for kids;
- your consent tool calls
BonusRound.consent(false)(or setswindow.bonusroundConfig = { consent: false }before the tag), the tag hasdata-storage="none", or the browser sends Global Privacy Control and you haven't calledconsent(true).
Otherwise (you said No, and the game is rated Teen or above), the SDK keeps an anonymous player id (p_…) in localStorage for frequency caps. It isn't tied to an account, an email or a device fingerprint, and consent(false) deletes it. BonusRound.debug().privacy shows which mode a page is in, and why.