Get started

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.

Using Claude Code, Cursor or Codex?

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

Five steps

  1. 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 with npx bonusround init, or from your AI agent with the MCP server.

  2. Add the script tag

    Put it in the <head> of the HTML page that runs your game. It's async, small, and loads the rest of the SDK lazily.

    <script async src="https://bonusround.io/v1/br.js" data-pub="pub_XXXXXXXX"></script>
  3. 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 same THREE your game imports, so the SDK uses your three.js version.

  4. Call break() at natural breaks

    Round 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.

  5. 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 pending to detected in the dashboard. Our play agent then starts learning the game (learning, then ready).

    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.

Next

Install for your stackScript tag, Vite, webpack, React Three Fiber, vanilla. Native takeoverPass a host adapter so the round runs in your world, with your physics. Formats & triggersIntermission, interval, rewarded, ambient props and portals. Go liveTurn off test mode, add ads.txt, get paid.