Authentication & signing
Operator API requests and wallet callbacks carry the same HMAC signature. It covers the timestamp, the method, the path, the query and the raw body, so a signed request cannot be moved to another endpoint or replayed after 30 seconds. With one key, both directions use its secret; with several, callbacks are signed with the signing key (below).
Credentials
| Item | Description |
|---|---|
| Operator ID | Your fixed identifier, e.g. op_meridyen. |
| API key | Prefixed by environment: Test (staging) pk_test_, Live (production) pk_live_. The first Test key is created automatically when your account opens; a Live key can be created after production approval. You can open several keys. A key bound to a site works only for that site and must be enabled for your account by Boss Hub. |
| Signing secret | Starts with sk_. Shown only once: on first view for the automatic Test key, at creation for the others. If you lose it, rotate it. Keep it out of your repository and logs. |
| Signing key | Callbacks are signed with the secret of the signing key of that environment: the newest active key when you first saved the wallet settings. If you rotate that key, signing moves to the new key. X-Api-Key on each callback names the key. The signing key cannot be revoked; rotate it instead. |
Headers
| Header | Description |
|---|---|
| X-Operator-Id | Your operator ID. Required. |
| X-Timestamp | Unix seconds. Required. Rejected outside ±30 s. |
| X-Signature | The signature, lowercase hex. Required. |
| X-Request-Id | Required on API write requests (POST, PUT, DELETE), at most 128 characters. Always present on callbacks; stays the same across bet, win and rollback retries. |
| X-Api-Key | Optional. Selects the right secret when you have several keys; without it, the active keys of that environment are tried. |
Calculating the signature
hex(HMAC_SHA256(secret, ts + "." + METHOD + "." + path?query + "." + body))
- ts: the same value as the X-Timestamp header.
- METHOD: the HTTP method in uppercase.
- path?query: the path and query as they appear on the request line, /v1 included, in encoded form. On a callback, the full path of your own address, base path included, e.g. /bossgaming/bet.
- body: the raw body. For requests without a body it is an empty string, so the signed text ends with a dot.
Code samples on the other pages use the signing and verification helpers shown here.
secret: sk_test_example
1762344000.POST./bossgaming/bet.{"transaction_id":"tx_01HF3K9QZ2YB","amount":2.00}
X-Signature: b0e89e3b7f1f07b0a54e36f262a5c5adb22dc556a8cd9cb41aac31e93adefb85secret: sk_test_example 1762344000.GET./v1/games?site_id=site_mrd01&lang=tr. X-Signature: 8c020cb9d6b2c7688bde1a53987cf957b6ad9632d0f5f113866d3f9a45ec6906
Callback retries
When Boss Hub retries a callback, the body is byte-for-byte the same and X-Request-Id does not change; only X-Timestamp and X-Signature are renewed. Recognise a retry by its transaction_id, not by its signature. Answer a request you cannot verify with HTTP 200 and {"status":"ERROR","code":"INVALID_SIGNATURE"}; a bare 401 counts as uncertain and fails scenario SIG-04 of the test suite. Ignore body fields you do not recognise.
API idempotency
Every write request to the operator API must carry X-Request-Id; if it is missing or longer than 128 characters, you get 422 VALIDATION_ERROR (details.field). IDs are scoped to your operator and environment and kept for 24 hours. This lets you retry safely after a network error without creating a second campaign.
- Same ID with the same method, path, query and body: the first response comes back as is, with the header X-Idempotent-Replay: true.
- Same ID with a different request: 409 REQUEST_ID_CONFLICT.
- While the first request is still running: 409 REQUEST_IN_PROGRESS; retry the same request shortly.
- 4xx responses are stored too. For example, a request that got GAME_DISABLED or SCOPE_MISSING and is retried with the same ID gets the same error. After fixing the cause, use a new X-Request-Id. Exception: INVALID_JSON and field validation (VALIDATION_ERROR with details.fields) happen before the ID is claimed and are not stored.
- 5xx responses are not stored; you can retry with the same ID. Signature, IP and rate limit errors (401, 403 IP_NOT_ALLOWED, 429) do not use up the ID.
Scopes
| Scope | Endpoints | Extra condition |
|---|---|---|
| games.read | GET /v1/games, GET /v1/games/{game_id}, GET /v1/limits, GET /v1/limits/rules, GET /v1/availability, GET /v1/jackpots, GET /v1/lobby/categories, GET /v1/lobby/recommendations | The jackpot endpoint needs the jackpot module enabled |
| game.launch | POST /v1/game/launch | |
| rounds.read | GET /v1/rounds, GET /v1/rounds/{round_id}, GET /v1/transactions, GET /v1/jackpots/{pool_id}/wins, GET /v1/live/stream | Win history needs the jackpot module, the live stream needs the live module enabled |
| freerounds.write | POST /v1/freerounds, GET and DELETE /v1/freerounds/{id}, POST and GET /v1/freerounds/{id}/players, DELETE /v1/freerounds/{id}/players/{player_id} | The promo module must be enabled |
| reports.read | GET /v1/reports/ggr | The api module must be enabled |
| limits.write | PUT /v1/limits/rules | |
| availability.write | PUT /v1/availability/rules | |
| lobby.write | PUT /v1/lobby/categories | |
| reconciliation.read | GET /v1/reconciliation/stuck-rounds, GET /v1/reconciliation/daily | |
| reconciliation.write | POST /v1/reconciliation/stuck-rounds/{round_id}/resolve, POST /v1/reconciliation/daily | |
| wallet_health.read | GET /v1/wallet-health | |
| marketing.read | GET /v1/marketing/assets, GET /v1/releases | |
| player_events.write | POST /v1/player-events |
Scopes belong to your operator, not to a key, and apply to all your keys. A new account has game.launch, games.read, rounds.read, reports.read and wallet.seamless (no endpoint needs it); Boss Hub enables the others (e.g. freerounds.write, reconciliation.write), so ask your account manager. A missing scope returns 403 SCOPE_MISSING (details.scope); a disabled module returns 403 MODULE_DISABLED. All codes: Error codes.
Rate limit
The limit applies per operator and environment. Unless a value is set for you, it is 3,000 requests per minute. Going over it returns 429 RATE_LIMITED; the Retry-After header and retry_after in the body tell you how many seconds to wait. On Live (production), requests must also pass the IP allowlist.
Error body
API errors return {code, message, request_id}, plus details and retry_after depending on the code. message is in English, or in Turkish if you send Accept-Language: tr; build your logic on code. request_id is the server’s ID and also comes in the X-Boss-Request-Id response header; it is not your X-Request-Id. Quote it in support requests.