Authentication
Before your users can play real-money games, your server must create a session token by calling the Maktub API. This token is then passed to the SDK as the accessToken prop.
Important: This request must be made from your server side. Webhook-wallet operators authenticate with their webhook secret, which must never be exposed in client-side code.
Server URL
https://server.maktub.betAll API requests should be made to this base URL.
Create Session
POST https://server.maktub.bet/session
Content-Type: application/json
x-operator-secret: <your webhook secret>Webhook-wallet operators must send the same per-operator secret used for authenticated free-bet requests. An incorrect, empty, or unprovisioned secret returns 401 before Maktub checks the user or creates session state. During an explicitly coordinated migration, Maktub operations can temporarily accept a completely absent header for an exact legacy clientId; this exception does not change the integration contract, and a supplied invalid header never falls back to it. Existing bearer sessions and the response format are unchanged; provider-mode /session behavior is outside this webhook credential contract and remains unchanged.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
clientId | string | ✅ | Your unique customer/client identifier provided by Maktub |
userId | string | ✅ | The unique identifier for the end user in your system |
currency | object | — | { code, prefix?, rate? } — see Currency modes. Code alone = wallet-currency mode (wire amounts in the player’s currency); full triple = legacy display conversion (wire stays USD). |
language | string | — | Locale code (e.g. 'en', 'pt'). Default: 'en' |
theme | object | — | { buttonColor?, darkBackground?, darkLightBackground? } — overrides customer defaults |
logoUrl | string | — | URL to your logo image, shown in the game’s bottom bar. Overrides customer default. |
cardLogoUrl | string | — | URL to a logo shown on the back of playing cards (card games only: Blackjack, Baccarat, HiLo, Video Poker). Falls back to logoUrl when omitted. |
token | string | — | Opaque operator token (max 512 chars) echoed back on every webhook as sessionToken. Use it to map webhooks to a specific game session without overloading userId (which stays static, e.g. for free bets). Also accepted as sessionToken. |
showBalance | boolean | — | Show balance in game UI. Default: false |
showRules | boolean | — | Show game rules button. Default: false |
userName | string | — | Display name for multiplayer games (e.g. Crash) |
logoUrlvscardLogoUrl:logoUrlonly appears in the bottom bar. To brand the card backs in card games, setcardLogoUrl. For best results use a logo with enough contrast against yourtheme.buttonColor(the card-back tint) and avoid fully transparent/white-on-light marks, which can render invisible.
{
"clientId": "your-client-id",
"userId": "user-123",
"token": "sess-9f3c1a...",
"currency": { "code": "BRL", "prefix": "R$", "rate": 5.5 },
"language": "pt",
"showBalance": true,
"showRules": true
}The
tokenvalue above is your session identifier — it is sealed inside the returned (encrypted) session token and replayed to you assessionTokenon every webhook for this session. It is never exposed to the player.
Currency modes
The currency object selects one of two modes:
Wallet-currency mode — pass the code alone:
{ "currency": { "code": "INR" } }All wire amounts for this session (webhook actions, balance_check responses, API responses) are in the player’s currency — you debit and credit 1:1 with zero conversion on your side. The game UI shows the same numbers with a server-resolved symbol (₹, ₨, ৳, R$, …). Every webhook for the session carries currency: "INR" so your handler always knows the unit. This is the recommended mode for multi-currency operators: create each player’s session with that player’s own wallet currency; no conversion rates are ever needed.
Display-conversion mode (legacy) — pass the full triple:
{ "currency": { "code": "BRL", "prefix": "R$", "rate": 5.5 } }Wire amounts stay USD; the rate only converts numbers for display in the game UI. Webhooks for these sessions carry currency: "USD". Existing integrations keep working unchanged.
One player session has exactly one wire currency, fixed at creation. A player with wallets in several currencies should get a separate session per wallet.
Response
{
"token": "eyJhbGciOiJIUzI1NiIs...",
"gameUrl": "https://play.maktub.bet?token=eyJhbGciOiJIUzI1NiIs..."
}| Field | Type | Description |
|---|---|---|
token | string | An encrypted session token scoped to the given user |
gameUrl | string | Full URL for the iframe integration (append game name as path) |
Usage
The returned token must be prefixed with Bearer before being passed to the SDK:
accessToken = "Bearer " + tokenServer-Side Example (Node.js)
// Your backend endpoint that the frontend calls to get a session
app.post('/api/game-session', async (req, res) => {
const { userId } = req.body
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: userId,
}),
})
const data = await response.json()
res.json({ accessToken: `Bearer ${data.token}` })
})Frontend Integration (React)
'use client'
import { useEffect, useState } from 'react'
import { DiceGameSDK } from '@maktubbet/sdk/dice'
import '@maktubbet/sdk/dice/styles.css'
export default function GamePage() {
const [accessToken, setAccessToken] = useState<string | null>(null)
useEffect(() => {
fetch('/api/game-session', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ userId: 'user-123' }),
})
.then((res) => res.json())
.then((data) => setAccessToken(data.accessToken))
}, [])
return (
<DiceGameSDK
accessToken={accessToken}
user={{ betCount: 0, isAuthenticated: !!accessToken }}
updateBalance={(b) => console.log(b)}
onAuthRequired={() => console.log('Auth required')}
/>
)
}Frontend Integration (Vanilla JS)
<div id="game"></div>
<script src="https://unpkg.com/@maktubbet/sdk/dist/vanilla.global.js"></script>
<link rel="stylesheet" href="https://unpkg.com/@maktubbet/sdk/dist/dice/styles.css" />
<script>
fetch('/api/game-session', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ userId: 'user-123' }),
})
.then(function (res) { return res.json(); })
.then(function (data) {
MaktubSDK.dice('#game', {
accessToken: data.accessToken,
user: { betCount: 0, isAuthenticated: true },
updateBalance: function (b) { console.log('Balance:', b); },
onAuthRequired: function () { console.log('Auth required'); },
});
});
</script>Flow Overview
┌──────────┐ ┌──────────────┐ ┌──────────────┐
│ Browser │───1───▶│ Your Server │───2───▶│ Maktub API │
│ (Frontend)│ │ (Backend) │ │ │
│ │◀──4───│ │◀──3───│ │
└──────────┘ └──────────────┘ └──────────────┘
1. Frontend requests a game session from your server
2. Your server calls POST https://server.maktub.bet/session with clientId + userId + x-operator-secret
3. Maktub API returns { token }
4. Your server returns the Bearer token to the frontendThe frontend then passes the accessToken to the SDK and the user can start playing.