Quickstart
Five minutes from a plain three.js game to a test Bonus Round playing in it. You add one script tag, one line after your renderer, and one await at a natural break. Developers earn 70% of net ad revenue.
Tell your agent “Integrate Bonus Round” and point it at bonusround.io/integrate.md. It follows the same steps as this page and checks that the game is live. See Integrate with AI agents.
What you get
- The Bonus Round: at a natural break, a 15-second branded Bonus Round takes over the game. Players collect branded items, see a sponsored leaderboard, and return to your game. With a host adapter they keep their own character, controls and physics.
- An ambient prop: a branded portal, statue or billboard placed in an open spot in your world, paired with the round.
- Game-native creative: our agents play your game to learn its controls, scale and art style. Each brand's round is generated for your game specifically.
Five steps
-
Create an account and add your game
Sign up, open Games and add the URL where your game runs. You get a public publisher id like
pub_7f3a9c1e2b4d6f80. You can also do this from the terminal withnpx bonusround init, or from your AI agent with the MCP server. -
Add the script tag
Put it in the
<head>of the HTML page that runs your game. It'sasync, small, and loads the rest of the SDK lazily.<script async src="https://bonusround.io/v1/br.js" data-pub="pub_XXXXXXXX"></script> -
Attach after your renderer, scene and camera exist
import * as THREE from 'three'; const renderer = new THREE.WebGLRenderer({ antialias: true }); const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 0.1, 500); // Bonus Round: works whether br.js has loaded yet or not (window.bonusround = window.bonusround || []).push((BR) => BR.attach({ THREE, scene, camera, renderer }));That queue line is the safe form of
BonusRound.attach({ THREE, scene, camera, renderer }). Because the script tag is async, your game code may run before it has loaded; the queue runs your call as soon as it does. Pass the sameTHREEyour game imports, so the SDK uses your three.js version. -
Call
break()at natural breaksRound over, level complete, death and respawn, back to the lobby: wherever a player would accept a short pause.
async function onRoundEnd() { pauseGameplay(); // stop timers, enemies, scoring await window.BonusRound?.break('intermission'); // plays a Bonus Round, or resolves at once if there's no ad startNextRound(); }The
?.means an ad blocker can never break your game: if the SDK didn't load, the line does nothing. -
Load your game and watch it go live
Open the game in a browser. The SDK pings Bonus Round on load and your game flips from
pendingtodetectedin the dashboard. Our play agent then starts learning the game (learning, thenready).New games start in test mode, so your next
break()plays the Fizzpop Soda test Bonus Round right away. Test traffic is never billed and never paid. More about test mode.
Check it worked
In the browser console on your game's page:
BonusRound.version // "1.0.0": the loader is on the page
await BonusRound.debug() // the SDK's state: mode, attached, testMode, last ad requests and events
await BonusRound.break('intermission') // in test mode: plays the Fizzpop test round right now
Or from a terminal: npx bonusround status --wait 60. Or ask your agent to call bonusround_integration_status.