3 Oaks Integration Guide
Panduan Integrasi 3 Oaks
Connect an operator, create players, launch 3 Lucky Sparks, and integrate either a transfer or seamless wallet using the production API contract.
Hubungkan operator, buat pemain, launch 3 Lucky Sparks, dan integrasikan transfer atau seamless wallet menggunakan kontrak API production.
Overview
Gambaran Umum
The authenticated operator determines wallet mode, currency, player ownership, and access. Game launch and wallet endpoints never accept a client-selected wallet strategy.
Operator yang terautentikasi menentukan mode wallet, currency, kepemilikan pemain, dan akses. Endpoint game launch dan wallet tidak menerima strategi wallet pilihan client.
Transfer wallet: this backend owns the playable balance and updates it atomically in PostgreSQL alongside a per-round ledger.
Transfer wallet: backend ini menjadi pemilik saldo bermain dan memperbaruinya secara atomik di PostgreSQL bersama ledger per-round.
Seamless wallet: the operator owns the balance. This backend sends signed callbacks to the operator's configured callback URL on every debit, credit, and rollback.
Seamless wallet: operator menjadi pemilik saldo. Backend ini mengirim callback bertanda tangan ke callback URL operator setiap debit, credit, dan rollback.
Native gameplay: after launch, the 3 Oaks (goreel) game client speaks directly with this backend over the session endpoint POST /gs/{game}/{platform}/{session}/prod?gsc=... using JSON commands login, start, sync, and play. Each play spin settles the bet and win through the same wallet gateway.
Gameplay native: setelah launch, client game 3 Oaks (goreel) berkomunikasi langsung dengan backend ini melalui endpoint sesi POST /gs/{game}/{platform}/{session}/prod?gsc=... dengan perintah JSON login, start, sync, dan play. Setiap spin play menyelesaikan bet dan win melalui wallet gateway yang sama.
Authentication
Autentikasi
All operator endpoints under /api/v1/users, /api/v1/game, and /api/v1/wallet require an active API token as a bearer token.
Semua endpoint operator di bawah /api/v1/users, /api/v1/game, dan /api/v1/wallet memerlukan API token aktif sebagai bearer token.
Keep API tokens and secret keys on a trusted server. Never embed them in browser JavaScript, mobile applications, launch URLs, logs, or public repositories.
Simpan API token dan secret key di server tepercaya. Jangan menaruhnya di JavaScript browser, aplikasi mobile, launch URL, log, atau repository publik.
If an operator has an IP allowlist, requests must arrive from an allowed address. X-Request-ID is optional and is returned in the response headers.
Jika operator memiliki IP allowlist, request harus berasal dari alamat yang diizinkan. X-Request-ID opsional dan dikembalikan pada header respons.
The native session endpoint POST /gs/{game}/{platform}/{session}/prod does not use an operator bearer token; it is authenticated by the opaque session token embedded in the launch URL as the token query parameter and reused as the {session} path segment. The session is issued by POST /api/v1/game/launch.
Endpoint sesi native POST /gs/{game}/{platform}/{session}/prod tidak memakai bearer token operator; autentikasi dilakukan dengan session token opaque yang tertanam di launch URL sebagai query token dan dipakai ulang sebagai path {session}. Sesi diterbitkan oleh POST /api/v1/game/launch.
Authorization: Bearer <operator_api_token>
Content-Type: application/json
X-Request-ID: 6ca6a797-a9d6-4b16-ae51-f4184472ba23
https://3oaks.ohmybet.online/game/?profile=default&wl=prod&token=<session_token_hex32>&game=3_lucky_sparks&lang=en&sound=1&ts=<unix>&mobile=1
Common Response Format
Format Respons Umum
Every operator API outcome uses HTTP 200 OK. Read the JSON status and code; do not infer the result from HTTP status.
Semua hasil operator API menggunakan HTTP 200 OK. Baca status dan code pada JSON; jangan menentukan hasil dari HTTP status.
A success response has data and no error. An error response has error and no data.
Respons sukses memiliki data tanpa error. Respons gagal memiliki error tanpa data.
Native gameplay responses use a goreel JSON body with command, status.code, session_id, user, and context. They are documented separately under Gameplay Protocol.
Respons gameplay native memakai body JSON goreel dengan command, status.code, session_id, user, dan context. Didokumentasikan terpisah di Protokol Gameplay.
{
"status": true,
"code": "SUCCESS",
"data": {}
}{
"status": false,
"code": "VALIDATION_ERROR",
"error": {}
}Integer Money Rules
Aturan Nominal Integer
All wallet amounts are signed 64-bit JSON integers in minor units. Never send floating-point values or numeric strings. A mutation amount must be from 1 through 1000000000000.
Semua nominal wallet adalah integer JSON 64-bit dalam minor unit. Jangan mengirim floating-point atau string angka. Amount mutasi harus dari 1 sampai 1000000000000.
- IDR: denominator is
1(whole rupiah, NOT cents).100000000minor units means Rp 100,000,000. - USD: denominator is
100.10000minor units means USD 100.00. - Currency: exactly three uppercase ASCII letters and must match the player.
- Overflow: credits that exceed integer balance capacity are rejected with
BALANCE_OVERFLOW. - Gameplay: the native
playbet isbet_per_line * linesin minor units. The wallet sees the same integer amount.
- IDR: denominator
1(rupiah utuh, BUKAN sen).100000000minor unit berarti Rp 100.000.000. - USD: denominator
100.10000minor unit berarti USD 100.00. - Currency: tepat tiga huruf ASCII kapital dan harus sama dengan currency pemain.
- Overflow: credit yang membuat saldo melewati kapasitas integer ditolak dengan
BALANCE_OVERFLOW. - Gameplay: bet
playnative adalahbet_per_line * linesdalam minor unit. Wallet menerima amount integer yang sama.
Idempotency
reference_id is the operator-scoped idempotency key for debit, credit, deposit, and withdraw. rollback_reference_id is the key for rollback. The native game engine reflects every play spin as a debit (bet) and an optional credit (win) in the same wallet ledger.
reference_id adalah idempotency key per operator untuk debit, credit, deposit, dan withdraw. rollback_reference_id adalah key untuk rollback. Game engine native mencatat setiap spin play sebagai debit (bet) dan opsional credit (win) di ledger wallet yang sama.
- Retry an identical request with the same key to receive the original result.
- Reusing a key with different user, amount, currency, or operation returns
IDEMPOTENCY_CONFLICT. - One completed transaction can be rolled back only once.
- A seamless mutation timeout is an unknown outcome. Reconcile it before retrying.
- Retry request identik dengan key yang sama untuk menerima hasil awal.
- Memakai ulang key dengan user, amount, currency, atau operasi berbeda menghasilkan
IDEMPOTENCY_CONFLICT. - Satu transaksi completed hanya dapat di-rollback satu kali.
- Timeout mutasi seamless adalah hasil yang belum diketahui. Lakukan rekonsiliasi sebelum retry.
Games: Hosts & Base URLs
Game: Host & Base URL
Two production hosts share the integration: the application host serves the operator API, the launch URL, and the native gameplay endpoint; a separate CDN host serves the static game client bundle. The documentation host serves this guide and its protected trial launcher.
Dua host production terlibat: host aplikasi melayani operator API, launch URL, dan endpoint gameplay native; host CDN terpisah melayani bundle static game client. Host dokumentasi melayani panduan ini dan trial launcher yang terlindungi.
Operator API https://3oaks.ohmybet.online
Game launch https://3oaks.ohmybet.online/game/
Native gameplay https://3oaks.ohmybet.online/gs/{game}/{platform}/{session}/prod
Static CDN https://cdn-3oaks.ohmybet.online
Documentation https://3oaks-docs.pages.devCreate a Player
Buat Pemain
Create the player before launch. external_user_id is the stable identity within one operator. A duplicate identity returns USER_ALREADY_EXISTS.
Buat pemain sebelum launch. external_user_id adalah identitas stabil di dalam satu operator. Identitas duplikat menghasilkan USER_ALREADY_EXISTS.
Request fields
Field request
operator_id string required Authenticated operator UUID
external_user_id string required Stable player identity
username string optional Display name
currency string required Exact uppercase currencyoperator_id string wajib UUID operator terautentikasi
external_user_id string wajib Identitas pemain yang stabil
username string opsional Nama tampilan
currency string wajib Currency kapital yang tepat{
"operator_id": "<operator_uuid>",
"external_user_id": "player-1001",
"username": "Player 1001",
"currency": "IDR"
}{
"status": true,
"code": "SUCCESS",
"data": {
"id": "7cf96ba7-9bca-4eb8-9823-65423fdc32f1",
"operator_id": "<operator_uuid>",
"external_user_id": "player-1001",
"username": "Player 1001",
"currency": "IDR",
"balance_amount": 0,
"status": "active",
"created_at": "2026-06-15T12:00:00Z",
"updated_at": "2026-06-15T12:00:00Z"
}
}Launch a Game
Launch Game
Request a fresh launch URL from your trusted backend, then redirect or open it in the player's browser. The launch session expires after 30 minutes by default.
Minta launch URL baru dari backend tepercaya Anda, lalu redirect atau buka URL tersebut di browser pemain. Sesi launch default berakhir setelah 30 menit.
Request fields
Field request
game_code string required Canonical game code, e.g. "3_lucky_sparks"
external_user_id string required Existing active player
currency string optional Must match player currency; defaults to operator currency
platform string optional "mobile" or "desktop"; defaults to "mobile"
language string optional ISO language; defaults to "en"
exit_url string optional URL the client redirects to on exit
cashier_url string optional URL the client opens for the cashier actiongame_code string wajib Kode game kanonik, mis. "3_lucky_sparks"
external_user_id string wajib Pemain aktif yang sudah ada
currency string opsional Harus sama dengan currency pemain; default currency operator
platform string opsional "mobile" atau "desktop"; default "mobile"
language string opsional Bahasa ISO; default "en"
exit_url string opsional URL redirect saat keluar dari client
cashier_url string opsional URL yang dibuka client untuk aksi cashierUnknown players return NOT_FOUND. Invalid or missing required fields return VALIDATION_ERROR. Invalid currency returns INVALID_CURRENCY.
Pemain tidak dikenal menghasilkan NOT_FOUND. Field wajib hilang atau salah menghasilkan VALIDATION_ERROR. Currency tidak valid menghasilkan INVALID_CURRENCY.
{
"game_code": "3_lucky_sparks",
"external_user_id": "player-1001",
"currency": "IDR",
"platform": "mobile",
"language": "en"
}{
"status": true,
"code": "SUCCESS",
"data": {
"launch_url": "https://3oaks.ohmybet.online/game/?profile=default&wl=prod&token=1ac5834a5fe949228c57bbfeb8e6faf6&game=3_lucky_sparks&lang=en&sound=1&ts=1781860676&title=3_lucky_sparks&mobile=1&exit_url=https%3A%2F%2Fwww.google.com&cashier_url=https%3A%2F%2Fwww.google.com&history_url=&incognito="
}
}curl -sS https://3oaks.ohmybet.online/api/v1/game/launch \
-H "Authorization: Bearer <operator_api_token>" \
-H "Content-Type: application/json" \
--data '{
"game_code":"3_lucky_sparks",
"external_user_id":"player-1001",
"currency":"IDR",
"platform":"mobile",
"language":"en"
}'Native Gameplay Protocol
Protokol Gameplay Native
After launch, the 3 Oaks (goreel) HTML5 client posts JSON requests directly to POST /gs/{game}/{platform}/{session}/prod?gsc=<command>. Operators should not call this endpoint; the client drives it. Responses are goreel JSON bodies with command, status.code, session_id, user, and (for play/start) a context object.
Setelah launch, client HTML5 3 Oaks (goreel) mengirim request JSON langsung ke POST /gs/{game}/{platform}/{session}/prod?gsc=<command>. Operator tidak perlu memanggil endpoint ini; client yang menjalankannya. Respons memakai body JSON goreel dengan command, status.code, session_id, user, dan (untuk play/start) objek context.
gsc=login— first call after launch. Returns available modes, session ID, and the player wallet snapshot (balance,currency,huid).gsc=start— loads the opening board state and current bet context.gsc=sync— re-reads the authoritative balance and session state, e.g. after reconnect.gsc=play— one spin. The body carriesaction.name(spin,buy_spin,freespin_init,freespin,freespin_stop) andaction.paramswithbet_per_lineandlines. The betbet_per_line * linesis debited and any win is credited in the same wallet ledger.
gsc=login— panggilan pertama setelah launch. Mengembalikan mode tersedia, session ID, dan snapshot wallet pemain (balance,currency,huid).gsc=start— memuat state board awal dan konteks bet saat ini.gsc=sync— membaca ulang balance utama dan state sesi, mis. setelah reconnect.gsc=play— satu spin. Body membawaaction.name(spin,buy_spin,freespin_init,freespin,freespin_stop) danaction.paramsdenganbet_per_linedanlines. Betbet_per_line * linesdidebit dan win dikredit di ledger wallet yang sama.
A rejected session command returns the standard {"status":false,"code":"NOT_FOUND"} envelope. The balance returned in user.balance is always the authoritative server-side balance in minor units.
Perintah sesi yang ditolak mengembalikan envelope standar {"status":false,"code":"NOT_FOUND"}. Balance pada user.balance selalu balance otoritatif sisi server dalam minor unit.
POST /gs/3_lucky_sparks/mobile/<session_token>/prod?gsc=login
Content-Type: application/json
{"command":"login"}{
"command": "login",
"modes": ["auto","play","freebet"],
"session_id": "3cb01bb3db584e6cb7dc3f3173ec4cc6",
"status": {"code": "OK"},
"user": {
"balance": 100000000,
"balance_version": 1,
"currency": "IDR",
"huid": "trial_user_01",
"is_test": false,
"show_balance": true
}
}POST /gs/3_lucky_sparks/mobile/<session_token>/prod?gsc=play
Content-Type: application/json
{
"command": "play",
"action": {
"name": "spin",
"params": {"bet_per_line": 10, "lines": 25}
}
}{
"command": "play",
"status": {"code": "OK"},
"user": {"balance": 100000180, "balance_version": 2, "currency": "IDR", "huid": "trial_user_01"},
"context": {
"spins": {
"round_bet": 100,
"round_win": 280,
"total_win": 280,
"lines": 25,
"bet_per_line": 10
}
}
}Supported Games
Game Tersedia
The production catalog currently exposes one 3 Oaks (goreel) title with the goreel native protocol and supported play actions including spin, buy_spin, freespin_init, freespin, and freespin_stop.
Katalog production saat ini menyediakan satu judul 3 Oaks (goreel) dengan protokol native goreel dan aksi play yang didukung mencakup spin, buy_spin, freespin_init, freespin, dan freespin_stop.
game_code 3_lucky_sparks
name 3 Lucky Sparks
vendor kendoo
provider 3oaks
protocol goreel
currencies IDR (denominator 1), USD (denominator 100)
play actions spin, buy_spin, freespin_init, freespin, freespin_stop
bet shape bet_per_line * lines (in minor units)
bet_per_line range 1 .. 500 (per game seed)
launch path /game/?profile=default&wl=prod&token=...&game=3_lucky_sparks
session path POST /gs/3_lucky_sparks/{platform}/{session}/prod?gsc=...Live Trial
Coba Langsung
This demo button calls the operator launch API directly and opens the game in a new tab. Production integrations must keep the operator token server-side instead.
Tombol demo ini memanggil API launch operator secara langsung dan membuka game di tab baru. Integrasi production wajib menyimpan token operator di server.
game 3 Lucky Sparks
game_code 3_lucky_sparks
currency IDR
player trial_user_013 Lucky Sparks
slotProduction trial using a dedicated transfer-wallet player on the live VPS.
Trial production menggunakan pemain transfer-wallet khusus di VPS live.
Transfer Wallet Flow
Alur Transfer Wallet
Transfer mode stores the authoritative balance locally. Create the player, deposit funds, launch the game, and use the ledger endpoints for audit or controlled corrections. Every play spin reflects the bet as a debit row and any win as a credit row in the wallet ledger.
Mode transfer menyimpan saldo utama secara lokal. Buat pemain, deposit dana, launch game, lalu gunakan endpoint ledger untuk audit atau koreksi terkontrol. Setiap spin play mencatat bet sebagai row debit dan kemenangan sebagai row credit di ledger wallet.
- Create the player with
POST /api/v1/users. - Fund it with
POST /api/v1/wallet/deposit. - Launch game
3_lucky_sparks. - Use balance and transactions for reconciliation.
- Rollback one completed debit or credit only when required.
- Buat pemain dengan
POST /api/v1/users. - Isi dana dengan
POST /api/v1/wallet/deposit. - Launch game
3_lucky_sparks. - Gunakan balance dan transactions untuk rekonsiliasi.
- Rollback satu debit atau credit completed hanya bila diperlukan.
Balance
Returns the current authoritative local balance. This read does not create a ledger row.
Mengembalikan saldo lokal utama saat ini. Operasi baca ini tidak membuat row ledger.
external_user_id string required
currency string requiredGET /api/v1/wallet/balance?external_user_id=player-1001¤cy=IDR{
"status": true,
"code": "SUCCESS",
"data": {
"balance_amount": 10000000,
"currency": "IDR",
"timestamp": "2026-06-15T12:00:00Z"
}
}Deposit
Credits a transfer player's local balance and creates one completed credit ledger row.
Menambah saldo lokal pemain transfer dan membuat satu row ledger credit berstatus completed.
operator_id string required
external_user_id string required
reference_id string required, unique per operator
amount integer required, 1..1000000000000
currency string required{
"operator_id": "<operator_uuid>",
"external_user_id": "player-1001",
"reference_id": "deposit-20260615-0001",
"amount": 10000000,
"currency": "IDR"
}id, operator_id, user_id, external_user_id,
wallet_type, type, amount, currency,
balance_before, balance_after, reference_id,
status, failure_code, metadata,
created_at, completed_atWithdraw
Debits the local balance for an operator-controlled transfer-out. Insufficient funds do not change the balance.
Mengurangi saldo lokal untuk transfer-out yang dikontrol operator. Saldo tidak cukup tidak mengubah balance.
The body shape is identical to deposit. Use a new reference_id for every distinct transfer.
Bentuk body sama dengan deposit. Gunakan reference_id baru untuk setiap transfer yang berbeda.
{
"operator_id": "<operator_uuid>",
"external_user_id": "player-1001",
"reference_id": "withdraw-20260615-0001",
"amount": 1000000,
"currency": "IDR"
}Debit & Credit
These provider-neutral wallet operations are used for game financial movement. Debit removes funds; credit adds funds. The native game engine uses the same wallet gateway internally with reference IDs of the form round:<round_uuid>:debit and round:<round_uuid>:credit.
Operasi wallet netral-provider ini digunakan untuk pergerakan finansial game. Debit mengurangi dana; credit menambah dana. Game engine native memakai wallet gateway yang sama secara internal dengan reference ID berbentuk round:<round_uuid>:debit dan round:<round_uuid>:credit.
Unlike deposit and withdraw, these bodies do not contain operator_id.
Berbeda dari deposit dan withdraw, body ini tidak memiliki operator_id.
{
"external_user_id": "player-1001",
"reference_id": "round:7cf96ba7-9bca-4eb8-9823-65423fdc32f1:debit",
"amount": 200000,
"currency": "IDR"
}{
"transaction_id": "659c881f-8afd-44f5-b35e-57f75dd07aa2",
"balance_after": 9800000,
"currency": "IDR",
"timestamp": "2026-06-15T12:00:00Z"
}Rollback
Reverses one eligible completed debit or credit. The backend derives the original amount and currency; do not send them.
Membalik satu debit atau credit completed yang memenuhi syarat. Backend mengambil amount dan currency dari transaksi awal; jangan mengirimkannya.
{
"external_user_id": "player-1001",
"original_reference_id": "round:7cf96ba7-9bca-4eb8-9823-65423fdc32f1:debit",
"rollback_reference_id": "round:7cf96ba7-9bca-4eb8-9823-65423fdc32f1:rollback"
}TRANSACTION_NOT_FOUND
TRANSACTION_NOT_ROLLBACKABLE
TRANSACTION_ALREADY_ROLLED_BACK
IDEMPOTENCY_CONFLICTTransactions
Lists operator-owned ledger rows. Filters are optional. Default limit is 20; maximum limit is 100; maximum offset is 10000.
Menampilkan row ledger milik operator. Filter bersifat opsional. Limit default 20; limit maksimum 100; offset maksimum 10000.
external_user_id optional
type optional: credit, debit, rollback
status optional: pending, completed, failed,
reversed, mismatch
reference_id optional
limit optional: 1..100
offset optional: 0..10000GET /api/v1/wallet/transactions?external_user_id=player-1001&status=completed&limit=20&offset=0{
"status": true,
"code": "SUCCESS",
"data": {
"items": [],
"limit": 20,
"offset": 0
}
}Transfer Wallet Test Checklist
Checklist Pengujian Transfer Wallet
- Create one active IDR player and verify duplicate creation returns
USER_ALREADY_EXISTS. - Deposit, balance-check, withdraw, debit, credit, and rollback.
- Launch
3_lucky_sparks, perform aplayspin that wins, and verify both the debit and credit rows exist in the ledger. - Verify the spin debit equals
bet_per_line * linesin minor units and the credit equals the engine win. - Repeat identical idempotent requests concurrently and verify one financial effect.
- Reuse a key with a different amount and expect
IDEMPOTENCY_CONFLICT. - Verify insufficient debit leaves balance unchanged.
- Buat satu pemain IDR aktif dan pastikan duplikat menghasilkan
USER_ALREADY_EXISTS. - Uji deposit, balance, withdraw, debit, credit, dan rollback.
- Launch
3_lucky_sparks, lakukan satuplayspin yang menang, lalu pastikan row debit dan credit muncul di ledger. - Pastikan debit spin sama dengan
bet_per_line * linesdalam minor units dan credit sama dengan win dari engine. - Ulangi request idempotent identik secara concurrent dan pastikan hanya ada satu efek finansial.
- Pakai ulang key dengan amount berbeda dan harapkan
IDEMPOTENCY_CONFLICT. - Pastikan debit dengan saldo tidak cukup tidak mengubah balance.
Seamless Wallet Flow
Alur Seamless Wallet
The operator remains the balance authority. Configure an HTTPS callback base URL and secret key. This backend appends the endpoint path and signs every raw JSON request. Every play spin delivers a signed debit callback for the bet and, when the spin wins, a signed credit callback for the win in real time.
Operator tetap menjadi pemilik saldo. Konfigurasikan base callback URL HTTPS dan secret key. Backend ini menambahkan path endpoint dan menandatangani setiap raw JSON request. Setiap spin play mengirim callback debit untuk bet dan, jika spin menang, callback credit untuk win secara real-time.
Every callback response must use HTTP 200 OK and the same success/error envelope documented above. A non-200 response is treated as unavailable for reads or an unknown outcome for mutations.
Setiap respons callback wajib memakai HTTP 200 OK dan envelope sukses/error yang sama seperti di atas. Respons non-200 dianggap unavailable untuk operasi baca atau unknown outcome untuk mutasi.
- Implement all callback endpoints below.
- Verify signature before parsing or processing the request.
- Deduplicate debit, credit, and rollback by
reference_id. - Return the exact original result for identical duplicates.
- Support transaction status reconciliation for unknown outcomes.
- Implementasikan semua endpoint callback di bawah.
- Verifikasi signature sebelum parsing atau memproses request.
- Deduplicate debit, credit, dan rollback berdasarkan
reference_id. - Kembalikan hasil awal yang sama untuk duplikat identik.
- Dukung rekonsiliasi transaction status untuk hasil yang belum diketahui.
Public API Triggers
Pemicu API Publik
The operator-facing balance, debit, credit, and rollback routes keep the same request contract as transfer mode. The authenticated operator's stored wallet type causes the gateway to call your callback service. The same gateway is used by the native play spin flow.
Route balance, debit, credit, dan rollback yang menghadap operator memakai kontrak request yang sama dengan mode transfer. Wallet type operator terautentikasi membuat gateway memanggil layanan callback Anda. Gateway yang sama dipakai oleh alur native play spin.
GET /api/v1/wallet/balance
POST /api/v1/wallet/debit
POST /api/v1/wallet/credit
POST /api/v1/wallet/rollbackdeposit and withdraw are transfer-only and return WALLET_TYPE_NOT_SUPPORTED for a seamless operator.
deposit dan withdraw hanya untuk transfer dan menghasilkan WALLET_TYPE_NOT_SUPPORTED pada operator seamless.
Callback Signing
Signature Callback
Every callback is an HTTPS POST with compact JSON. Sign the exact raw request body bytes; parsing and re-serializing JSON can change the signature input.
Setiap callback adalah POST HTTPS dengan JSON compact. Verifikasi menggunakan raw body persis; parsing lalu serialisasi ulang JSON dapat mengubah input signature.
operation = "POST\n" + path + "\n" + X-Timestamp
message = operation + "\n" + raw_json_body
signature = hex(HMAC-SHA256(secret_key, message))Compare signatures in constant time. Resolve the secret by X-Key-Version. Validate that X-Timestamp equals the body timestamp and reject stale or replayed requests.
Bandingkan signature secara constant-time. Ambil secret berdasarkan X-Key-Version. Pastikan X-Timestamp sama dengan timestamp body dan tolak request stale atau replay.
Content-Type: application/json
X-Signature: <lowercase_hex_hmac_sha256>
X-Timestamp: 2026-06-15T12:00:00Z
X-Key-Version: <credential_version_uuid>operator_code string
external_user_id string
currency string
request_id UUID correlation ID
timestamp RFC3339
transaction_id mutation callback only
reference_id mutation/status callback
original_reference_id rollback callback
amount integer mutation callback
metadata optional JSON objectPOST /balance
Return the current authoritative operator balance for the requested player and currency.
Kembalikan saldo utama operator saat ini untuk pemain dan currency yang diminta.
{
"operator_code": "YOUR_OPERATOR",
"external_user_id": "player-1001",
"currency": "IDR",
"request_id": "97fdf0cb-c451-4bc2-8c9b-e923a3dfd45d",
"timestamp": "2026-06-15T12:00:00Z"
}{
"status": true,
"code": "SUCCESS",
"data": {
"balance": 10000000,
"currency": "IDR"
}
}POST /debit & POST /credit
Debit and credit have the same body shape. Apply the mutation atomically with its idempotency record. Return the exact request fields plus the resulting balance.
Debit dan credit memiliki bentuk body yang sama. Terapkan mutasi secara atomik bersama idempotency record. Kembalikan field request yang sama beserta saldo akhir.
For an identical duplicate, return the original successful response. For mismatched immutable fields, return IDEMPOTENCY_CONFLICT.
Untuk duplikat identik, kembalikan respons sukses awal. Untuk immutable field berbeda, kembalikan IDEMPOTENCY_CONFLICT.
{
"operator_code": "YOUR_OPERATOR",
"external_user_id": "player-1001",
"currency": "IDR",
"request_id": "e0741886-797d-48bc-b1f4-953ca3627a40",
"timestamp": "2026-06-15T12:00:00Z",
"transaction_id": "c941df2c-df11-4918-8ce2-acac6160a3c1",
"reference_id": "round:7cf96ba7-9bca-4eb8-9823-65423fdc32f1:debit",
"amount": 200000
}{
"status": true,
"code": "SUCCESS",
"data": {
"transaction_id": "c941df2c-df11-4918-8ce2-acac6160a3c1",
"reference_id": "round:7cf96ba7-9bca-4eb8-9823-65423fdc32f1:debit",
"amount": 200000,
"balance_after": 9800000,
"currency": "IDR"
}
}POST /rollback
Reverse the referenced completed mutation once. Validate the supplied original reference, amount, currency, and player against the stored original transaction.
Balikkan mutasi completed yang direferensikan satu kali. Validasi original reference, amount, currency, dan pemain terhadap transaksi awal yang tersimpan.
{
"operator_code": "YOUR_OPERATOR",
"external_user_id": "player-1001",
"currency": "IDR",
"request_id": "6e25d5c2-fe52-46aa-941c-1120e8bcbd30",
"timestamp": "2026-06-15T12:05:00Z",
"transaction_id": "ab7f211a-36b9-42f6-986b-bb4d05025fc5",
"reference_id": "round:7cf96ba7-9bca-4eb8-9823-65423fdc32f1:rollback",
"original_reference_id": "round:7cf96ba7-9bca-4eb8-9823-65423fdc32f1:debit",
"amount": 200000
}{
"status": true,
"code": "SUCCESS",
"data": {
"transaction_id": "ab7f211a-36b9-42f6-986b-bb4d05025fc5",
"reference_id": "round:7cf96ba7-9bca-4eb8-9823-65423fdc32f1:rollback",
"original_reference_id": "round:7cf96ba7-9bca-4eb8-9823-65423fdc32f1:debit",
"amount": 200000,
"balance_after": 10000000,
"currency": "IDR"
}
}POST /transaction-status
Used to reconcile a seamless mutation after an unknown outcome. Return completed, failed, or not_found.
Digunakan untuk rekonsiliasi mutasi seamless setelah hasil tidak diketahui. Kembalikan completed, failed, atau not_found.
When available, return the original amount, currency, reference, and transaction type so the backend can detect mismatches.
Jika tersedia, kembalikan amount, currency, reference, dan transaction type awal agar backend dapat mendeteksi mismatch.
{
"operator_code": "YOUR_OPERATOR",
"external_user_id": "player-1001",
"currency": "IDR",
"request_id": "a09ef2c7-dfb8-46df-a9fd-d310205d4262",
"timestamp": "2026-06-15T12:06:00Z",
"reference_id": "round:7cf96ba7-9bca-4eb8-9823-65423fdc32f1:debit"
}{
"status": true,
"code": "SUCCESS",
"data": {
"transaction_status": "completed",
"operator_transaction_id": "operator-tx-99881",
"transaction_type": "debit",
"reference_id": "round:7cf96ba7-9bca-4eb8-9823-65423fdc32f1:debit",
"amount": 200000,
"currency": "IDR"
}
}Seamless Wallet Test Checklist
Checklist Pengujian Seamless Wallet
- Reject missing, malformed, stale, replayed, or wrong-version signatures.
- Verify raw-body signing with non-ASCII and optional metadata.
- Run concurrent duplicate debit, credit, and rollback calls.
- Simulate timeout after committing a mutation, then reconcile it as completed.
- Return mismatched amount or currency and verify it becomes an unknown outcome.
- Launch
3_lucky_sparksand verify a winningplayspin produces one debit callback for the bet then one credit callback for the win. - Ensure logs never contain secrets, signatures, or full sensitive payloads.
- Tolak signature yang hilang, malformed, stale, replay, atau salah versi.
- Uji raw-body signing dengan non-ASCII dan metadata opsional.
- Jalankan debit, credit, dan rollback duplikat secara concurrent.
- Simulasikan timeout setelah mutasi committed, lalu rekonsiliasi sebagai completed.
- Kembalikan amount atau currency berbeda dan pastikan menjadi unknown outcome.
- Launch
3_lucky_sparksdan pastikan satuplayspin yang menang menghasilkan satu callback debit untuk bet lalu satu callback credit untuk win. - Pastikan log tidak menyimpan secret, signature, atau payload sensitif lengkap.
Error Codes
Kode Error
Handle errors by stable code. Do not depend on a human-readable message because public API errors intentionally do not include one.
Tangani error berdasarkan code yang stabil. Jangan bergantung pada pesan untuk manusia karena error API publik memang tidak menyertakannya.
- VALIDATION_ERRORMalformed JSON, unknown field, or missing required value.JSON malformed, field tidak dikenal, atau nilai wajib kosong.
- UNAUTHORIZEDMissing, invalid, revoked, or expired operator credential.Credential operator hilang, salah, revoked, atau expired.
- FORBIDDENAuthenticated operator is not permitted to access the resource.Operator terautentikasi tidak diizinkan mengakses resource.
- NOT_FOUNDRoute, game, or generic resource was not found.Route, game, atau resource umum tidak ditemukan.
- RATE_LIMITEDRequest rate exceeded the configured limit.Laju request melewati limit.
- USER_NOT_FOUNDPlayer does not exist for this operator.Pemain tidak ada untuk operator ini.
- USER_ALREADY_EXISTSThe operator already owns this external user identity.Operator sudah memiliki external user identity ini.
- USER_INACTIVEPlayer status does not allow wallet operations.Status pemain tidak mengizinkan operasi wallet.
- OPERATOR_MISMATCHRequest identity is owned by another operator.Identitas request dimiliki operator lain.
- INVALID_CURRENCYCurrency is not exactly three uppercase ASCII letters.Currency bukan tepat tiga huruf ASCII kapital.
- CURRENCY_MISMATCHCurrency differs from player or wallet currency.Currency berbeda dari currency pemain atau wallet.
- INVALID_AMOUNTAmount is zero or negative.Amount nol atau negatif.
- AMOUNT_LIMIT_EXCEEDEDAmount exceeds 1000000000000 minor units.Amount melewati 1000000000000 minor unit.
- INSUFFICIENT_BALANCEBalance cannot cover the requested debit.Saldo tidak cukup untuk debit.
- BALANCE_OVERFLOWCredit would overflow the integer balance.Credit akan membuat saldo integer overflow.
- IDEMPOTENCY_CONFLICTA key was reused with different immutable fields.Key dipakai ulang dengan immutable field berbeda.
- WALLET_TYPE_NOT_SUPPORTEDOperation is not supported by this operator wallet mode.Operasi tidak didukung mode wallet operator ini.
- TRANSACTION_NOT_FOUNDReferenced ledger transaction does not exist.Transaksi ledger yang direferensikan tidak ada.
- TRANSACTION_NOT_ROLLBACKABLEThe referenced transaction cannot be reversed.Transaksi yang direferensikan tidak dapat dibalik.
- TRANSACTION_ALREADY_ROLLED_BACKThe original transaction was already reversed.Transaksi awal sudah pernah dibalik.
- TRANSACTION_STATUS_UNKNOWNSeamless mutation outcome requires reconciliation.Hasil mutasi seamless memerlukan rekonsiliasi.
- PROVIDER_UNAVAILABLESeamless callback service is unavailable.Layanan callback seamless tidak tersedia.
- UPSTREAM_TIMEOUTSeamless balance or status check timed out.Pemeriksaan balance atau status seamless timeout.
- INTERNAL_ERRORUnexpected internal failure; use the request ID when reporting it.Kegagalan internal tak terduga; sertakan request ID saat melaporkan.