Skip to Content
Getting Started

Getting Started

Integrate Maktub games into your platform in 3 steps: create a session, embed an iframe, and handle webhooks.

Step 1 — Create a Session (Server-Side)

Your backend calls POST https://server.maktub.bet/session to get a game URL. Webhook-wallet operators authenticate this server-side request with the webhook secret provided during onboarding; never expose that secret in client-side code.

// Your backend endpoint app.post('/api/game-session', async (req, res) => { const response = await fetch('https://server.maktub.bet/session', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-operator-secret': process.env.MAKTUB_WEBHOOK_SECRET, }, body: JSON.stringify({ clientId: process.env.MAKTUB_CLIENT_ID, userId: req.user.id, currency: { code: 'BRL', prefix: 'R$', rate: 5.5 }, language: 'pt', theme: { buttonColor: '#22c993', darkBackground: '#101a18', darkLightBackground: '#1b2825', }, showBalance: true, showRules: true, }), }) const { token, gameUrl } = await response.json() // gameUrl bakes your session theme/lang/currency as query params, e.g.: // https://play.maktub.bet?token=abc123&bg=%23101a18&accent=%2322c993&lang=pt&cur=BRL&curPrefix=R%24 res.json({ token, gameUrl }) })

Session Parameters

FieldTypeRequiredDescription
clientIdstringYour customer identifier provided by Maktub
userIdstringUnique identifier for the end user in your system
currencyobject{ code: 'USD', prefix: '$', rate: 1 } — currency display config
languagestringLocale code (e.g. 'en', 'pt', 'es'). Default: 'en'
themeobjectColor overrides (see Theming). Overrides customer defaults.
logoUrlstringURL to your logo image. Overrides customer default.
cardLogoUrlstringURL to a logo shown on the back of playing cards (card games only — Blackjack, Baccarat, Hi-Lo, Video Poker). Overrides logoUrl there. Falls back to logoUrl when omitted.
tokenstringOpaque per-session token (max 512 chars) echoed on every webhook as sessionToken (see Webhooks). Disambiguates concurrent sessions for the same userId. Also accepted as sessionToken.
showBalancebooleanShow balance in game UI. Default: false
showRulesbooleanShow game rules button. Default: false
userNamestringDisplay name for multiplayer games (e.g. Crash)

Response

{ "token": "abc123...", "gameUrl": "https://play.maktub.bet?token=abc123…&bg=%23101a18&accent=%2322c993&bgLight=%231b2825&lang=pt&cur=BRL&curPrefix=R%24" }

The gameUrl bakes your session’s theme, language and currency as query params (bg, accent, bgLight, lang, cur, curPrefix), so an iframe pointed at it paints themed and localized from the first frame — no pop. It is game-agnostic: insert the game path to choose a game, e.g. https://play.maktub.bet/dice?token=…&bg=…&lang=…. The embed options below use the token (shown as TOKEN_FROM_STEP_1) plus those same params.

Step 2 — Embed the Game

You set the width; you never manage the height. The game measures itself and tells the embed how tall it needs to be, so it grows to fit — switching Manual ↔ Auto/Advanced never clips or scrolls. Pick the path that fits your stack.

Paste a placeholder and our one script. You write no JavaScript: the script builds the iframe and keeps its height glued to the game’s content.

<div data-maktub-game="dice" data-token="TOKEN_FROM_STEP_1" data-bg="#101a18" data-accent="#22c993" data-bg-light="#1b2825" data-lang="pt" data-currency="BRL" data-currency-prefix="R$" ></div> <script src="https://play.maktub.bet/embed.js" async></script>
  • Swap data-maktub-game for any game: mines, crash, blackjack, plinko, futures, … (see Available Games).

  • data-token is the token from your Step 1 session.

  • Theme attributes (optional but recommended) — pass your session’s colors so the game paints in your palette from the very first frame:

    • data-bg — main dark background (the game surface).
    • data-accent — button / highlight color.
    • data-bg-light — secondary (lighter) dark background.

    These should match the theme you set when creating the session. Omit them and the game falls back to a neutral dark theme, then snaps to your session theme once it loads — so passing them avoids a brief color pop.

  • Locale & currency attributes (optional but recommended) — pass your session’s language and currency so the game’s text and money prefix are correct from the very first frame, with no flash:

    • data-langen, pt, or es.
    • data-currency — currency code (e.g. BRL).
    • data-currency-prefix — symbol shown before amounts (e.g. R$).

    These should match the session you created. Omit them and the game starts in English / $, then snaps to your session’s language and currency once it loads — so passing them avoids a brief text/currency pop.

  • Optional data-initial-height="600" sets the placeholder height shown before the game reports its own (avoids a layout jump).

  • Multiple games per page and SPAs work out of the box — the script watches for placeholders added later.

The script is served from play.maktub.bet, validates the message origin for you, and resizes the iframe as the game grows. Nothing else to wire up.

Instant load. No blank or loading wall: when you pass your theme colors the script paints a fully-styled, themed snapshot of the game over the frame while the live game loads behind it, then reveals the live, interactive game in place the moment it’s ready. (Pass no theme colors and you get a neutral loader instead, so a default palette never flashes.) You get this automatically; there’s nothing to configure.

Without our script (manual)

If your CSP forbids third-party scripts — or you’d just rather not load ours — embed a plain iframe and add the listener yourself. The game posts { type: 'maktub:resize', height }; you set the iframe height.

<!-- In your page <head>: open the connection early so the iframe loads faster. --> <link rel="preconnect" href="https://play.maktub.bet"> <iframe id="maktub-game" src="https://play.maktub.bet/dice?token=TOKEN_FROM_STEP_1&bg=%23101a18&accent=%2322c993&bgLight=%231b2825&lang=pt&cur=BRL&curPrefix=R%24" style="width:100%;height:600px;border:0;display:block" scrolling="no" allow="clipboard-write; fullscreen" ></iframe> <script> var frame = document.getElementById('maktub-game'); window.addEventListener('message', function (e) { if (e.origin !== 'https://play.maktub.bet') return; // origin check (security) if (e.source !== frame.contentWindow) return; if (e.data && e.data.type === 'maktub:resize' && e.data.height) { frame.style.height = Math.ceil(e.data.height) + 'px'; } }); </script>
  • Game route — point the src at https://play.maktub.bet/<game>?token=… (e.g. /dice, /mines, /crash). The game server-renders its themed UI, so it appears styled in your colors as soon as the document loads, then becomes interactive once it finishes booting — one iframe, no second request, no swap.

  • Theme — pass bg, accent and bgLight as URL-encoded query params (e.g. #101a18%23101a18) matching your session theme, so the game paints in your colors from the first frame — no color pop.

  • Locale & currency — pass lang (en/pt/es), cur (currency code, e.g. BRL) and curPrefix (symbol, URL-encoded — e.g. R$R%24) matching your session, so text and money are correct from the first frame — no language/currency pop.

  • Faster first load — add <link rel="preconnect" href="https://play.maktub.bet"> to your page <head> so the browser opens the connection (DNS + TLS) before the iframe needs it. This is the single biggest win for embed load time.

  • Fullscreen — the game’s bottom bar has a fullscreen button. Browsers only let an iframe go fullscreen when its tag opts in, so include fullscreen in the allow attribute (allow="clipboard-write; fullscreen"). Without it the button hides itself automatically.

No listener at all? A plain <iframe> at a fixed height still works — the game scales to fit the box you give it and any extra controls scroll inside. You just don’t get the auto-grow.

Token not ready yet? — instant shell, then upgrade

If you can’t put the token in the iframe src at embed time — because your backend is still creating the session (Step 1) when the page renders — point the iframe at the game without a token. The game server-renders its themed, disabled loading shell on the first byte (no blank frame, no spinner), firing no authenticated requests. The moment your token arrives, postMessage it to the frame and the game boots live in place — same iframe, no reload, no flash.

<link rel="preconnect" href="https://play.maktub.bet"> <iframe id="maktub-game" src="https://play.maktub.bet/dice?bg=%23101a18&accent=%2322c993&bgLight=%231b2825&lang=pt&cur=BRL&curPrefix=R%24" style="width:100%;height:600px;border:0;display:block" scrolling="no" allow="clipboard-write; fullscreen" ></iframe> <script> var frame = document.getElementById('maktub-game') // Resize handshake (same as the manual embed above). window.addEventListener('message', function (e) { if (e.origin !== 'https://play.maktub.bet') return if (e.source !== frame.contentWindow) return if (e.data && e.data.type === 'maktub:resize' && e.data.height) { frame.style.height = Math.ceil(e.data.height) + 'px' } }) // Create the session (Step 1) however you do it, then hand the token to the // already-painted shell. The game boots live in the SAME frame — no reload. fetch('/api/game-session').then(function (r) { return r.json() }).then(function (s) { frame.contentWindow.postMessage( { type: 'maktub:upgrade', token: s.token }, 'https://play.maktub.bet', ) }) </script>
  • Use the same theme / locale / currency params as a normal embed (bg, accent, bgLight, lang, cur, curPrefix) so the shell already matches your palette and locale — the live game then swaps in over an identical paint.
  • The upgrade message is { type: 'maktub:upgrade', token }. Post it to frame.contentWindow with the target origin https://play.maktub.bet. (A search string — e.g. '?token=…' — is accepted in place of token.)
  • Tip: start the iframe height close to the game’s natural height (a fixed 600/700 is fine). On mobile a box much shorter than the game makes the first paint scale down, then grow once the resize handshake runs.
  • Works for every game — this is the iframe-only path: one bare <iframe>, no second frame, no embed.js.

For aggregators — bundle the resize helper

If you ship your own JS bundle to operators (or render games inside your own app), drop this self-contained helper into your bundle instead of loading our script. It depends on nothing but the maktub:resize message.

// Attach Maktub auto-resize to an iframe you created. Returns a cleanup fn. export function attachMaktubResize(iframe, origin = 'https://play.maktub.bet') { function onMessage(e) { if (e.origin !== origin) return; if (e.source !== iframe.contentWindow) return; const d = e.data; if (d && d.type === 'maktub:resize' && typeof d.height === 'number' && d.height > 0) { iframe.style.height = Math.ceil(d.height) + 'px'; } } window.addEventListener('message', onMessage); return () => window.removeEventListener('message', onMessage); }
const iframe = document.createElement('iframe'); iframe.src = `https://play.maktub.bet/dice?token=${token}`; iframe.style.cssText = 'width:100%;height:600px;border:0;display:block'; iframe.setAttribute('allow', 'clipboard-write; fullscreen'); // enables the in-game fullscreen button container.appendChild(iframe); const detach = attachMaktubResize(iframe); // call detach() on unmount

Step 3 — Handle Webhooks

When a bet is placed or resolved, our server calls your webhook endpoint. The easiest way to handle this is with the @maktubbet/webhook helper library:

yarn add @maktubbet/webhook
import { createWebhookHandler, WebhookError } from '@maktubbet/webhook' export default createWebhookHandler({ secret: process.env.MAKTUB_WEBHOOK_SECRET, expectedCustomerId: process.env.MAKTUB_CUSTOMER_ID, onBalanceCheck: async ({ userId, customerId }) => { return { balance: await db.getBalance(userId, customerId) } }, onBet: async ({ userId, customerId, amount, betId, event }) => { const result = await db.atomicDebit({ userId, customerId, amount, betId, event }) if (!result.ok) throw new WebhookError(result.reason, result.status) return { balance: result.balance } }, onWin: async ({ userId, customerId, amount, betId, event }) => { const result = await db.atomicCredit({ userId, customerId, amount, betId, event }) if (!result.ok) throw new WebhookError(result.reason, result.status) return { balance: result.balance } }, })

See the full Webhooks guide → for event details and manual implementation.

Step 4 (Optional) — Listen for Events

The iframe communicates with your page via postMessage. This is optional but useful for updating your UI in real time:

window.addEventListener('message', (e) => { if (e.origin !== 'https://play.maktub.bet') return switch (e.data.type) { case 'balance_update': updateBalanceUI(e.data.balance) break case 'auth_required': redirectToLogin() break case 'bet_result': trackAnalytics(e.data) break } })

See the full postMessage API → for all event types.

Theming

Theme colors can be set on your customer account (via admin panel) or overridden per-session:

// In your POST /session request body: { theme: { buttonColor: '#E6007A', // Bet / Cashout button darkBackground: '#1D121F', // Main background darkLightBackground: '#1A0F1C' // Sidebar background }, logoUrl: 'https://yoursite.com/logo.svg', cardLogoUrl: 'https://yoursite.com/card-logo.svg' // optional — overrides logoUrl on card backs (card games only) }

All theme properties are optional. Session values override customer-level defaults.

Accepted color formats: #RGB, #RRGGBB, or #RRGGBBAA (hex only — named colors and rgb() are not accepted). An invalid value is dropped for that key only: the key falls back to its own customer/built-in default and the other keys are unaffected.

Resolution order, per key: session value (if present and valid) → customer-level default → built-in default.

Built-in defaults:

KeyDefault
buttonColor#FFFFFF
darkBackground#0D1117
darkLightBackground#161B22

cardLogoUrl only affects the back of playing cards in card games (Blackjack, Baccarat, Hi-Lo, Video Poker); the bottom-bar logo always uses logoUrl. When cardLogoUrl is omitted, card backs fall back to logoUrl.

Demo Mode

Demo mode is automatically enabled when users place a bet with a $0 amount. This allows players to try games without risking real funds. No special configuration is needed — demo mode works with any valid session.

To run a game entirely client-side without a backend session, pass the isDemo prop:

<DiceGameSDK isDemo accessToken={null} user={{ betCount: 0, isAuthenticated: false }} updateBalance={(b) => console.log(b)} onAuthRequired={() => {}} />

Next Steps

Last updated on