← ChiefGaming

Operator Integration Guide

Plug a ChiefGaming title into your casino.

This is the contract your backend codes against to embed a ChiefGaming title and settle every spin against your own player wallet. We never touch your money database — we call four endpoints you host, every call signed and safe to retry.

Questions about integrating? Email chiefgaming@mystudio.tech — we read every message ourselves.

01 Overview

Your backend calls one endpoint on us before ever showing the player anything. From there, the game itself talks to our RGS, and our RGS is the only thing that ever calls your wallet — always signed, always over plain HTTPS.

  1. Your server calls POST /api/launch with a bearer token we issue you, and gets back a launchUrl.
  2. Your browser puts launchUrl in an iframe. It carries a signed, short-lived session token — the raw player id never crosses the browser.
  3. Every spin, our RGS calls your wallet: a signed bet, then a signed win if the spin paid. You never call us — we call you.
The player id, currency and game code all travel server-to-server at launch. The browser only ever sees an opaque session token, not the identifiers behind it.

02 Launch API

POST /api/launch — server-to-server. Call this before rendering the game; the response is a URL you put straight into an iframe.

Auth

Authorization: Bearer <LAUNCH_SECRET — issued to you> — a shared secret we issue out of band. A missing or incorrect bearer is rejected before anything else is validated.

Request body

{
  "playerId": "string",
  "currency": "string",
  "gameCode": "string",
  "lang": "string (optional)",
  "mode": "real (optional, default)"
}

Response — 200

{ "launchUrl": "string" }

Errors

StatusMeaning
401Missing or invalid bearer token.
400Unknown game code, game not allowlisted for your account, or currency doesn’t match your configured currency.

Example

curl -s -X POST "$STUDIO/api/launch" \
  -H "Authorization: Bearer $LAUNCH_SECRET" -H 'content-type: application/json' \
  -d '{"playerId":"player-1","currency":"USD","gameCode":"gilded-reel"}'
Session TTL: 1 hour. The token in launchUrl expires 3,600,000ms after it’s issued. Once it does, spin and balance calls return 401 — just call launch again for a fresh token. lang and mode are accepted but currently ignored; there is no returnUrl and no demo mode — every launched session settles real wallet callbacks from the first spin.

03 Wallet API you implement

You host four endpoints; we call them, signed, over HTTPS POST. Every one returns HTTP 200 with the outcome carried in the response body — see Status Codes before you wire up error handling.

POST /api/wallet/user/balance

Request

{ "user": "string", "currency": "string", "supplier_user": "string" }

Response — 200

{ "status": "RS_OK", "user": "string", "balance": 125000 }

POST /api/wallet/transaction/bet

Request

{
  "user": "string",
  "currency": "string",
  "amount": 125000,
  "transaction_uuid": "string",
  "round_id": "string",
  "game_code": "string",
  "supplier_user": "string"
}

Response — 200

{ "status": "RS_OK", "user": "string", "balance": 125000, "transaction_uuid": "string" }

POST /api/wallet/transaction/win

Same request and response shape as bet above.

POST /api/wallet/transaction/rollback

Request

{ "user": "string", "transaction_uuid": "string", "supplier_user": "string" }

Response — 200

{ "status": "RS_OK", "user": "string", "balance": 125000, "transaction_uuid": "string" }
supplier_user identifies us to you on every call — use it to look up our registered public key (see Signing). currency is required on every call but fixed to one value per operator account.

04 Signing

Every wallet request we send is signed with RSA-SHA256 over the raw request body bytes, base64-encoded, carried in the X-Hub88-Signature header. Verify it against our registered public key before trusting the request.

Verify against the exact bytes on the wire — read the raw body (e.g. req.text()), never a parsed-and-re-serialized copy. Key order and whitespace are part of what was signed; re-stringifying the parsed JSON will not reproduce the same signature.

Worked example

Body (the literal bytes that were signed)

{"user":"player-1","currency":"USD","amount":125000,"transaction_uuid":"11111111-1111-1111-1111-111111111111","round_id":"22222222-2222-2222-2222-222222222222","game_code":"gilded-reel","supplier_user":"studio-demo"}

ChiefGaming public key (PEM, SPKI)

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtRHBX3K1QN8VvTqZFcbT
eqOnBCBTPm0KM+sEP/nPkkI26ngzzzcGwNqg5xx/vb0Q3oxsm0YDCKuGARS4YXWw
nU+XiTwue5l2cHkFMSodbZ2cWruf4eCLI/2bhJzabMX9Xmdsl3pvSqKIFzZ+CZrR
+YeahFbB7gLzhLHO5Mt5aVdnnyzdt1MLxBNQMkN6o+Uah4NKRS8GSiU65aDN/yMb
yrHsyBaWAEogQlyk4a/i9A92lC1Ye4+q/6ep7gZqttfFxm2FVLb4dXI4SYKOqyUt
KUTFaFkeosHJVoAhpI5+YuC1a2ePFlWJhwIw3/ZOXD07+uLzmoPJyJi5T6z3al4Q
ywIDAQAB
-----END PUBLIC KEY-----

Resulting X-Hub88-Signature (base64)

ppA+Nd2Qsjst+VucojeHEnmecjpttIj0s/FKiEaM5Qt1pl0ydwmR5M7pDtsvGb9sl08isG2ShUWI0aMxD1p6NjdaFgASUcgWDBlf3QozkuELQEzojVA3F7kNIHPWEePyXZjCm614d3m6R2+I0q9Hi0n8LAv1ad9ZZ0xBUMwc8GagA9W2q9nSSFpanD/G5jjZ/Tl23mXpPi61vpzMVgRS6Xlvfd3A2I3MKQpYPWg7nYTZVgN3RDpD1SSArqU2mQ2nQelyWUGJDn8WtA/pIuHz35fRxt5rl/2hi5yMnYg1deOhtHXRggszvcWNDMTPhmh7AqbUkYvvS/2U7uFxm2AQNA==

This exact triple is a pinned test vector: reproduce the signature from the body above against the public key and it must verify; tamper with a single byte of the body and it must fail. Your own private key never leaves us — you only ever need the public key, to verify.

05 The closed status set

Every wallet response’s status field must be exactly one of these ten values — a closed set, not an example to extend:

RS_OKRS_ERROR_NOT_ENOUGH_MONEYRS_ERROR_UNKNOWN_USERRS_ERROR_INVALID_SIGNATURERS_ERROR_INVALID_PARTNERRS_ERROR_INVALID_AMOUNTRS_ERROR_TRANSACTION_ROLLED_BACKRS_ERROR_DUPLICATE_TRANSACTIONRS_ERROR_WRONG_CURRENCYRS_ERROR_UNKNOWN
Failures are reported in the body at HTTP 200. A non-2xx response, a non-JSON body, or a body with no recognizable status is not a rejection — it’s indeterminate: we can’t tell whether you applied the operation, and we treat it as distinct from a definite RS_ERROR_* result. Always respond 200 and put the outcome in status — never encode it as an HTTP status code.

06 Idempotency

The same transaction_uuid must apply at most once.

  • A retried bet or win with a transaction_uuid you’ve already processed must return RS_OK with the original resulting balance — never RS_ERROR_DUPLICATE_TRANSACTION for a same-uuid replay.
  • rollback reverses a specific bet by its transaction_uuid and is itself idempotent — rolling back the same uuid more than once must be safe and must never double-reverse the balance.
  • The bet-after-rollback rule. If a rollback for a transaction_uuid arrives before the matching bet ever does (out of order), the late bet must respond RS_OK with the balance unchanged — not RS_ERROR_TRANSACTION_ROLLED_BACK. The only invariant we require is that the bet never applies after a rollback of the same uuid; an unchanged-balance RS_OK satisfies that.

07 Money & sessions

Money scale

Every amount on the wire is an integer, scaled ×100,000. 1.25 units of currency is transmitted as the integer 125000. Range-guard every conversion at the JSON boundary — reject non-integers and anything outside a safe integer range on your side, exactly as we do on ours.

Session TTL

The session token in a launchUrl is valid for 1 hour from issuance. After that, spin and balance calls return 401 — call /api/launch again for a new session; there is nothing to refresh in-place.

Still stuck on something? Email chiefgaming@mystudio.tech — a worked signing example and a reference wallet implementation are both in the repo we send.

Ready to see it end to end?

Play the live demo, or get in touch to have a launch secret issued.

Play the Demo →