Embedding
The sportsbook runs in an iframe on your page. The embed SDK creates the iframe, passes the launch token, sizes it, checks the origin of every message and gives you callbacks for what happens inside. It has no dependencies and weighs about 5 KB.
With the SDK
Section titled “With the SDK”<div id="sportsbook"></div><script src="https://sportsbook.example.com/embed/v1.js"></script><script> const sb = BookmakerSportsbook.mount('#sportsbook', { url: 'https://sportsbook.example.com', tenant: 'your-tenant', launchToken: '<launch token from your server>', // omit for anonymous browsing height: matchMedia('(max-width: 899px)').matches ? 'fill' : 'auto', onLoginRequired: () => openYourLogin(), onDepositRequested: () => openYourCashier(), onBalanceChanged: ({ balance, currency }) => updateYourHeader(balance, currency), onSessionExpired: () => fetchLaunchTokenFromYourServer(), // a string, a promise of one, or null });</script>/embed/v1.js is the SDK for protocol version 1. It receives compatible fixes
under the same URL and is served with Cache-Control: public, max-age=3600, stale-while-revalidate=86400 and Access-Control-Allow-Origin: *. Do not add
an integrity attribute: the next compatible update would break it.
npm install @bookmaker/embed-sdkimport { mount, type SportsbookInstance } from '@bookmaker/embed-sdk';// the protocol types and helpers on their own:import type { SportsbookEvents, HostCommands } from '@bookmaker/embed-sdk/protocol';
const sb: SportsbookInstance = mount(document.getElementById('sportsbook')!, { url: 'https://sportsbook.example.com', tenant: 'your-tenant', launchToken, onBetPlaced: ({ betId, stake, currency }) => analytics.track('bet', { betId, stake, currency }),});ES module with type declarations, zero runtime dependencies.
Options
Section titled “Options”| Option | Type | |
|---|---|---|
url |
string | Sportsbook base URL. Its origin is the only one the SDK talks to. Required. |
tenant |
string | Your tenant id. Required. |
launchToken |
string | From your server (Player sessions). Omit it and players browse anonymously. |
path |
string | First route, see deep links. |
locale |
'en' | 'pt-BR' |
Language; default the tenant’s. |
height |
'auto' | 'fill' |
Sizing mode. Default 'fill'. |
minHeight |
number | auto mode: height in px until the first resize event. Default 600. |
scrollOffset |
number | false |
auto mode: when the player opens another sportsbook page while the top of the iframe is scrolled out of view, the SDK scrolls your page back to it, leaving this many pixels for your sticky header. Default 0; false turns it off. |
title |
string | Accessible name of the iframe. Default Sportsbook. |
onReady, onLoginRequired, onDepositRequested, onBalanceChanged, onBetPlaced, onBetSettled, onRouteChange, onResize, onError |
function | One callback per event, called with its payload. |
onSessionExpired |
function | Return a fresh launch token (or a promise of one) and the SDK sends set_launch_token; return null to leave the player logged out. One call at a time. |
onEvent |
function | (type, payload) for every event, after the specific callback. |
A callback that throws is reported with console.error and does not stop the
others.
The instance
Section titled “The instance”| Member | |
|---|---|
setLaunchToken(token) |
Log a player in, or replace an expired session, without reloading the iframe. |
navigate(path) |
Go to a deep link. |
setLocale(locale) |
en or pt-BR. |
refreshBalance() |
Re-read the balance from your wallet, e.g. after a deposit in your cashier. The iframe answers with balance_changed. |
logout() |
End the sportsbook session (call it when the player logs out of your site). |
on(type, handler) |
Subscribe to one event type; returns an unsubscribe function. |
ready |
Promise of the ready payload. |
iframe, origin |
The iframe element and the sportsbook origin. |
destroy() |
Remove the iframe and every listener. |
Commands sent before the sportsbook is ready are queued and sent after ready.
Without the SDK
Section titled “Without the SDK”Any page can frame the sportsbook and speak the postMessage
protocol directly. Use the launchUrl returned by
integration/sessions, or build the URL yourself:
https://sportsbook.example.com/?tenant=<tenant>&launch=<launchToken>&path=/live&locale=pt-BR&height=fill&origin=https%3A%2F%2Fwww.your-site.example| Parameter | |
|---|---|
tenant |
Required. Also tells the server which frame-ancestors to send. |
launch |
The single-use launch token. Removed from the iframe’s address at once. |
path |
First route (deep links). An invalid path is ignored and reported with an error event. |
locale |
en or pt-BR. |
height |
auto or fill (default). In auto the page must not depend on the viewport height, so tell the sportsbook. |
origin |
Your page’s origin. Browsers without location.ancestorOrigins (Firefox) use it, or the Referer, to know who the parent is. It is checked against your allowed origins like any other. |
token |
Deprecated: the session token itself in the URL. Still accepted for existing integrations; use launch. |
<iframe id="sb" title="Sportsbook" style="width: 100%; height: 100%; border: 0" src="https://sportsbook.example.com/?tenant=your-tenant&launch=lt_…&origin=https%3A%2F%2Fwww.your-site.example"></iframe><script> const SB = 'https://sportsbook.example.com'; const frame = document.getElementById('sb');
window.addEventListener('message', (e) => { // only our iframe, only the sportsbook origin, only protocol v1 if (e.source !== frame.contentWindow || e.origin !== SB) return; const m = e.data; if (!m || m.source !== 'bookmaker-sportsbook' || m.v !== 1) return; if (m.type === 'login_required') openYourLogin(); if (m.type === 'balance_changed') updateYourHeader(m.payload.balance, m.payload.currency); });
function send(type, payload = {}) { // always the exact sportsbook origin, never '*' frame.contentWindow.postMessage({ source: 'bookmaker-host', v: 1, type, payload }, SB); } // e.g. after your login: send('set_launch_token', { launchToken });</script>Sizing modes
Section titled “Sizing modes”auto |
fill |
|
|---|---|---|
| Height | The iframe grows to its content: the SDK sets it from every resize event. |
The iframe fills its container (height: 100%); give the container a height. |
| Scrolling | Your page scrolls; the iframe has no scrollbar. | The sportsbook scrolls inside the iframe. |
| Best for | Desktop pages with your header and footer around the sportsbook. | Phones and app-like layouts. |
| Sticky parts | The sportsbook header and bet slip scroll away with the page. | Header, bet slip and bottom navigation stay in view. |
/* fill: the sportsbook takes the screen below your header */#sportsbook { height: calc(100dvh - var(--your-header-height)); }Mobile
Section titled “Mobile”On screens narrower than 900 px the sportsbook switches to its mobile layout:
a bottom navigation bar and a bottom-sheet bet slip, both fixed to the
bottom of the iframe’s viewport. In auto mode that viewport is as tall as
the whole sportsbook, so they would sit far below the screen. Use fill on
phones, sized with dvh so the browser’s toolbars are accounted for. The
reference operator chooses per device:
height: matchMedia('(max-width: 899px)').matches ? 'fill' : 'auto',Deep links
Section titled “Deep links”| Path | Opens |
|---|---|
/ |
Home: live now and featured coupons |
/live, /live/{sportId} |
In-play, all sports or one (football, basketball, tennis) |
/sport/{sportId} |
A sport’s competitions and events |
/competition/{competitionId} |
One competition, e.g. /competition/br-serie-a |
/event/{eventId} |
An event with all its markets |
/my-bets |
The player’s open and settled bets (anonymous players: login_required) |
Open one at launch with path, later with navigate(path). The iframe reports
every navigation with a route event ({path, title}); mirror it in your own
URL to make sportsbook pages shareable, and pass it back as path on the next
load:
onRouteChange: ({ path, title }) => { const url = new URL(location.href); url.searchParams.set('sb', path); history.replaceState(null, '', url); document.title = `${title} · Your Casino`;},path: new URLSearchParams(location.search).get('sb') ?? undefined,Locale
Section titled “Locale”en and pt-BR ship today. Pass locale at mount, or switch at any time
with setLocale(); the sportsbook re-renders in place and keeps the route and
the bet slip. Without either, it uses the tenant’s default locale.
Allowed origins
Section titled “Allowed origins”Only your sites may frame the sportsbook and talk to it. List their exact
origins (scheme, host and port, no path, no wildcards) in your tenant’s
embedOrigins:
- The API sends
Content-Security-Policy: frame-ancestors 'self' <your origins>with every sportsbook page of your tenant ('self'alone when the list is empty), so browsers refuse to render it inside any other site. - The iframe reads the same list from
GET /api/v1/config(tenant.embedOrigins), only accepts commands from a parent at one of those origins, and only posts events to that verified origin, never to'*'. - The sportsbook’s own origin is always allowed (for previews and these docs).
Set them in the back office under Configurações → Integração, or with the API (staff token):
PUT /api/v1/admin/tenantAuthorization: Bearer <staff token>Content-Type: application/json
{"embedOrigins": ["https://www.your-casino.example", "https://m.your-casino.example"]}Your page’s own CSP
Section titled “Your page’s own CSP”If your site sends a Content Security Policy, allow the sportsbook origin for the script and the frame:
script-src 'self' https://sportsbook.example.com; frame-src https://sportsbook.example.comWhat changes in embed mode
Section titled “What changes in embed mode”| In the sportsbook | When embedded |
|---|---|
| Registration, login, account, deposits, withdrawals | Hidden: they are yours. |
| Header balance | Your wallet balance, labelled as such. |
| Deposit button | Sends deposit_requested; open your cashier. |
| Log in button, Log in to bet, My Bets when anonymous | Send login_required with a reason; open your login. |
| Session ends | session_expired; no dialog inside the iframe. |