Wallet API Integration Guide
Panduan Integrasi Wallet API
Use this guide to connect an operator to the backend using either a backend-managed transfer wallet or an operator-managed seamless wallet.
Gunakan panduan ini untuk menghubungkan operator ke backend dengan transfer wallet yang dikelola backend atau seamless wallet yang dikelola operator.
Overview
Gambaran Umum
The wallet API authenticates an operator, resolves its stored wallet type, validates the player and currency, and records financial activity in a provider-neutral ledger.
Wallet API mengautentikasi operator, membaca tipe wallet yang tersimpan, memvalidasi pemain dan mata uang, lalu mencatat aktivitas finansial pada ledger yang netral terhadap provider.
Transfer wallet is used when this backend owns the playable balance. Use deposit and withdraw to move funds, and use balance, rollback, and transaction history for operations and audit.
Transfer wallet digunakan ketika backend ini menjadi pemilik saldo bermain. Gunakan deposit dan withdraw untuk memindahkan dana, serta balance, rollback, dan riwayat transaksi untuk operasi dan audit.
Seamless wallet is used when the operator keeps the authoritative balance. The backend sends signed requests to the operator's wallet service for balance, debit, credit, rollback, and transaction-status checks.
Seamless wallet digunakan ketika operator tetap menjadi pemilik saldo utama. Backend mengirim request bertanda tangan ke layanan wallet operator untuk balance, debit, credit, rollback, dan pemeriksaan status transaksi.
Authentication
Autentikasi
All public /api/v1/wallet/* routes require the active operator API token as a bearer token. POST requests also require JSON content type.
Semua route publik /api/v1/wallet/* memerlukan API token operator aktif sebagai bearer token. Request POST juga wajib menggunakan content type JSON.
If the operator has an IP allowlist, the request must come from one of those exact IP addresses. Proxy headers are trusted only when the backend is configured with trusted proxy CIDRs.
Jika operator memiliki daftar IP yang diizinkan, request harus berasal dari salah satu IP tersebut. Header proxy hanya dipercaya jika backend dikonfigurasi dengan CIDR proxy tepercaya.
Public wallet requests are not HMAC-signed. HMAC signing applies only to outbound seamless wallet callbacks.
Request wallet publik tidak menggunakan HMAC. HMAC hanya digunakan untuk callback seamless wallet yang dikirim keluar oleh backend.
Authorization: Bearer <operator_api_token>
Content-Type: application/json
X-Request-ID: request-unique-id
X-Request-ID is optional. When omitted or invalid, the backend creates one and returns it in the response header.
X-Request-ID bersifat opsional. Jika tidak dikirim atau tidak valid, backend akan membuatnya dan mengembalikannya pada header respons.
Common Response Format
Format Respons Umum
Every application outcome uses HTTP 200 OK. Always check the JSON status and code. Success contains data only; failure contains error only.
Semua hasil aplikasi menggunakan HTTP 200 OK. Selalu periksa status dan code pada JSON. Respons sukses hanya memiliki data; respons gagal hanya memiliki error.
{
"status": true,
"code": "SUCCESS",
"data": {}
}
{
"status": false,
"code": "VALIDATION_ERROR",
"error": {}
}
Decimal Money Rules
Aturan Nominal Desimal
Money is decimal 1:1 with a maximum of two decimal places. Do not convert to cents, sen, or other minor units. Amounts must be greater than zero and no greater than 1000000000000.00.
Nominal uang menggunakan desimal 1:1 dengan maksimal dua angka di belakang koma. Jangan mengubah nominal menjadi cents, sen, atau minor unit lainnya. Amount harus lebih besar dari nol dan tidak lebih dari 1000000000000.00.
- IDR 100000.00 is sent as
"100000.00". - USD 1.00 is sent as
"1.00". "1.001", zero, and negative values are rejected.
- IDR 100000.00 dikirim sebagai
"100000.00". - USD 1.00 dikirim sebagai
"1.00". "1.001", nol, dan nilai negatif akan ditolak.
The current API serializes decimal response fields as JSON strings and may omit trailing zeros, for example "100000". Treat that value as the same decimal amount as 100000.00.
API saat ini menulis field desimal pada respons sebagai string JSON dan dapat menghilangkan nol di belakang, misalnya "100000". Nilai tersebut tetap sama dengan desimal 100000.00.
Idempotency
reference_id is the idempotency key for debit, deposit, and withdraw. rollback_reference_id is the idempotency key for rollback. Keys are unique per operator.
reference_id adalah idempotency key untuk debit, deposit, dan withdraw. rollback_reference_id adalah idempotency key untuk rollback. Key harus unik per operator.
For seamless callbacks, request_id is a per-call correlation UUID generated by the backend. It can change between calls and must not be used as the financial idempotency key. Deduplicate mutations by reference_id.
Pada callback seamless, request_id adalah UUID korelasi per pemanggilan yang dibuat oleh backend. Nilainya dapat berubah antar pemanggilan dan tidak boleh digunakan sebagai idempotency key finansial. Deduplicate mutasi berdasarkan reference_id.
- Retry the same financial intent with the same reference and identical fields.
- An identical completed duplicate returns the original successful result without changing the balance again.
- Reusing a reference with different user, amount, currency, or operation returns
IDEMPOTENCY_CONFLICT. - After
TRANSACTION_STATUS_UNKNOWN, never create a new reference for the same intent.
- Ulangi transaksi yang sama dengan reference dan field yang sama.
- Duplikat identik yang sudah selesai mengembalikan hasil awal tanpa mengubah saldo lagi.
- Penggunaan ulang reference dengan user, amount, currency, atau operasi berbeda menghasilkan
IDEMPOTENCY_CONFLICT. - Setelah
TRANSACTION_STATUS_UNKNOWN, jangan membuat reference baru untuk transaksi yang sama.
Transfer Wallet
Transfer mode stores the authoritative playable balance in this backend. A common integration flow is: check balance, deposit funds, withdraw funds when required, and use rollback only to reverse one completed transaction.
Mode transfer menyimpan saldo bermain utama di backend ini. Alur integrasi umumnya: periksa saldo, lakukan deposit, lakukan withdraw bila diperlukan, dan gunakan rollback hanya untuk membalik satu transaksi yang sudah selesai.
https://api-nlc.ohmybet.online/api/v1
Replace the example host with the base URL assigned to your environment.
Ganti host contoh dengan base URL yang diberikan untuk environment Anda.
Balance
Returns the current local balance for an active transfer-wallet player. This endpoint does not create a ledger transaction.
Mengembalikan saldo lokal saat ini untuk pemain transfer wallet yang aktif. Endpoint ini tidak membuat transaksi ledger.
Headers: Authorization: Bearer <operator_api_token>
Request body: None. Send the fields as query parameters.
Body request: Tidak ada. Kirim field sebagai query parameter.
Query fields
Field query
Field Type Requirement Description Example
external_user_id string Required Player ID owned by the operator. player-1001
currency string Required Exact uppercase 3-letter currency. IDRField Tipe Kebutuhan Deskripsi Contoh
external_user_id string Wajib ID pemain milik operator. player-1001
currency string Wajib Mata uang 3 huruf kapital persis. IDRNotes: Currency must match the player currency. Unknown users return USER_NOT_FOUND.
Catatan: Currency harus sama dengan currency pemain. User yang tidak ditemukan menghasilkan USER_NOT_FOUND.
GET /api/v1/wallet/balance?external_user_id=player-1001¤cy=IDR
Authorization: Bearer <operator_api_token>{
"status": true,
"code": "SUCCESS",
"data": {
"balance_amount": "100000",
"currency": "IDR",
"timestamp": "2026-06-12T00:00:00Z"
}
}{
"status": false,
"code": "CURRENCY_MISMATCH",
"error": {}
}Deposit
Credits the transfer-wallet player's local balance and writes one completed ledger row.
Menambah saldo lokal pemain transfer wallet dan menulis satu row ledger berstatus completed.
Headers: Authorization: Bearer <operator_api_token>, Content-Type: application/json
Request fields
Field request
Field Type Requirement Description Example
operator_id string Required Must equal the authenticated operator. 7fe0...2b1
external_user_id string Required Active player external ID. player-1001
reference_id string Required Unique idempotency key per operator. deposit-0001
amount decimal Required Positive, max 2 decimals. 100000.00
currency string Required Exact player currency. IDRField Tipe Kebutuhan Deskripsi Contoh
operator_id string Wajib Harus sama dengan operator terautentikasi. 7fe0...2b1
external_user_id string Wajib External ID pemain aktif. player-1001
reference_id string Wajib Idempotency key unik per operator. deposit-0001
amount decimal Wajib Positif, maksimal 2 desimal. 100000.00
currency string Wajib Harus sama dengan currency pemain. IDRNotes: Unknown JSON fields are rejected. A different operator_id returns FORBIDDEN.
Catatan: Field JSON yang tidak dikenal akan ditolak. operator_id yang berbeda menghasilkan FORBIDDEN.
{
"operator_id": "7fe0c978-3eb2-4e19-bc69-e2cdb74a12b1",
"external_user_id": "player-1001",
"reference_id": "deposit-0001",
"amount": "100000.00",
"currency": "IDR"
}{
"status": true,
"code": "SUCCESS",
"data": {
"id": "c2ea59c1-d143-46d1-bff4-6e302038849b",
"operator_id": "7fe0c978-3eb2-4e19-bc69-e2cdb74a12b1",
"user_id": "2ff15b51-a20a-4694-a01a-f1ad77ec34d7",
"external_user_id": "player-1001",
"wallet_type": "transfer",
"type": "credit",
"amount": "100000",
"currency": "IDR",
"balance_before": "0",
"balance_after": "100000",
"reference_id": "deposit-0001",
"status": "completed",
"failure_code": null,
"metadata": {},
"created_at": "2026-06-12T00:00:00Z",
"completed_at": "2026-06-12T00:00:00Z"
}
}{
"status": false,
"code": "IDEMPOTENCY_CONFLICT",
"error": {}
}Withdraw
Debits the transfer-wallet player's local balance. The operation fails without changing the balance when funds are insufficient.
Mengurangi saldo lokal pemain transfer wallet. Operasi gagal tanpa mengubah saldo jika saldo tidak mencukupi.
Headers: Authorization: Bearer <operator_api_token>, Content-Type: application/json
Request fields
Field request
Field Type Requirement Description Example
operator_id string Required Must equal the authenticated operator. 7fe0...2b1
external_user_id string Required Active player external ID. player-1001
reference_id string Required Unique idempotency key per operator. withdraw-0001
amount decimal Required Positive, max 2 decimals. 25000.00
currency string Required Exact player currency. IDRField Tipe Kebutuhan Deskripsi Contoh
operator_id string Wajib Harus sama dengan operator terautentikasi. 7fe0...2b1
external_user_id string Wajib External ID pemain aktif. player-1001
reference_id string Wajib Idempotency key unik per operator. withdraw-0001
amount decimal Wajib Positif, maksimal 2 desimal. 25000.00
currency string Wajib Harus sama dengan currency pemain. IDRNotes: An insufficient attempt is recorded as a failed debit and returns INSUFFICIENT_BALANCE.
Catatan: Percobaan dengan saldo tidak cukup dicatat sebagai debit gagal dan menghasilkan INSUFFICIENT_BALANCE.
{
"operator_id": "7fe0c978-3eb2-4e19-bc69-e2cdb74a12b1",
"external_user_id": "player-1001",
"reference_id": "withdraw-0001",
"amount": "25000.00",
"currency": "IDR"
}{
"status": true,
"code": "SUCCESS",
"data": {
"id": "52406740-b02d-4ffc-955f-598cc7f013d7",
"operator_id": "7fe0c978-3eb2-4e19-bc69-e2cdb74a12b1",
"user_id": "2ff15b51-a20a-4694-a01a-f1ad77ec34d7",
"external_user_id": "player-1001",
"wallet_type": "transfer",
"type": "debit",
"amount": "25000",
"currency": "IDR",
"balance_before": "100000",
"balance_after": "75000",
"reference_id": "withdraw-0001",
"status": "completed",
"failure_code": null,
"metadata": {},
"created_at": "2026-06-12T00:01:00Z",
"completed_at": "2026-06-12T00:01:00Z"
}
}{
"status": false,
"code": "INSUFFICIENT_BALANCE",
"error": {}
}Rollback
Reverses one completed credit or debit. The amount and currency are loaded from the original ledger row and must not be sent by the client.
Membalik satu credit atau debit yang sudah completed. Amount dan currency dibaca dari ledger transaksi awal dan tidak boleh dikirim oleh client.
Headers: Authorization: Bearer <operator_api_token>, Content-Type: application/json
Request fields
Field request
Field Type Requirement Description Example
external_user_id string Required Owner of the original transaction. player-1001
original_reference_id string Required Reference of completed credit/debit. withdraw-0001
rollback_reference_id string Required Unique idempotency key for rollback. rollback-0001Field Tipe Kebutuhan Deskripsi Contoh
external_user_id string Wajib Pemilik transaksi awal. player-1001
original_reference_id string Wajib Reference credit/debit yang completed. withdraw-0001
rollback_reference_id string Wajib Idempotency key unik untuk rollback. rollback-0001Notes: Only one rollback is allowed per original transaction. A completed rollback marks the original transaction as reversed.
Catatan: Hanya satu rollback diperbolehkan untuk setiap transaksi awal. Rollback yang selesai mengubah status transaksi awal menjadi reversed.
{
"external_user_id": "player-1001",
"original_reference_id": "withdraw-0001",
"rollback_reference_id": "rollback-0001"
}{
"status": true,
"code": "SUCCESS",
"data": {
"transaction_id": "bbd69c4f-7bc6-46d2-b6ce-3bdbf927d75a",
"balance_after": "99000",
"currency": "IDR",
"timestamp": "2026-06-12T00:03:00Z"
}
}{
"status": false,
"code": "TRANSACTION_ALREADY_ROLLED_BACK",
"error": {}
}Transactions
Returns ledger rows owned by the authenticated operator. Results are ordered newest first.
Mengembalikan row ledger milik operator terautentikasi. Hasil diurutkan dari yang terbaru.
Headers: Authorization: Bearer <operator_api_token>
Request body: None. All filters are query parameters.
Body request: Tidak ada. Semua filter dikirim sebagai query parameter.
Query fields
Field query
Field Type Requirement Description Example
external_user_id string Optional Filter by player external ID. player-1001
type string Optional credit, debit, or rollback. debit
status string Optional pending, completed, failed, reversed, mismatch. completed
reference_id string Optional Exact reference filter. withdraw-0001
limit integer Optional Default 20; minimum 1; maximum 100. 20
offset integer Optional Default 0; range 0 through 10000. 0Field Tipe Kebutuhan Deskripsi Contoh
external_user_id string Opsional Filter berdasarkan external ID pemain. player-1001
type string Opsional credit, debit, atau rollback. debit
status string Opsional pending, completed, failed, reversed, mismatch. completed
reference_id string Opsional Filter reference persis. withdraw-0001
limit integer Opsional Default 20; minimum 1; maksimum 100. 20
offset integer Opsional Default 0; rentang 0 sampai 10000. 0Notes: This is ledger history, not a single-purpose transaction-status endpoint.
Catatan: Endpoint ini adalah riwayat ledger, bukan endpoint khusus status satu transaksi.
GET /api/v1/wallet/transactions?external_user_id=player-1001&limit=20&offset=0
Authorization: Bearer <operator_api_token>{
"status": true,
"code": "SUCCESS",
"data": {
"items": [
{
"id": "52406740-b02d-4ffc-955f-598cc7f013d7",
"operator_id": "7fe0c978-3eb2-4e19-bc69-e2cdb74a12b1",
"user_id": "2ff15b51-a20a-4694-a01a-f1ad77ec34d7",
"external_user_id": "player-1001",
"wallet_type": "transfer",
"type": "debit",
"amount": "25000",
"currency": "IDR",
"balance_before": "100000",
"balance_after": "75000",
"reference_id": "withdraw-0001",
"status": "completed",
"failure_code": null,
"metadata": {},
"created_at": "2026-06-12T00:01:00Z",
"completed_at": "2026-06-12T00:01:00Z"
}
],
"limit": 20,
"offset": 0
}
}{
"status": false,
"code": "INVALID_TRANSACTION_STATUS",
"error": {}
}Testing Transfer Wallet
Pengujian Transfer Wallet
- Start with a player balance of 0.00.
- Deposit 100000.00. Expected balance: 100000.00.
- Withdraw 25000.00. Expected balance: 75000.00.
- Rollback the withdraw. Expected balance: 100000.00.
- Repeat the same request with the same reference. The balance must not change twice.
- Mulai dengan saldo pemain 0.00.
- Deposit 100000.00. Saldo yang diharapkan: 100000.00.
- Withdraw 25000.00. Saldo yang diharapkan: 75000.00.
- Rollback withdraw. Saldo yang diharapkan: 100000.00.
- Ulangi request yang sama dengan reference yang sama. Saldo tidak boleh berubah dua kali.
BASE_URL="https://api.example.com"
TOKEN="replace-with-operator-token"
OPERATOR_ID="7fe0c978-3eb2-4e19-bc69-e2cdb74a12b1"
curl -sS -X POST "$BASE_URL/api/v1/wallet/deposit" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"operator_id\":\"$OPERATOR_ID\",\"external_user_id\":\"player-1001\",\"reference_id\":\"test-deposit-1\",\"amount\":\"100000.00\",\"currency\":\"IDR\"}"
curl -sS -X POST "$BASE_URL/api/v1/wallet/withdraw" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"operator_id\":\"$OPERATOR_ID\",\"external_user_id\":\"player-1001\",\"reference_id\":\"test-withdraw-1\",\"amount\":\"25000.00\",\"currency\":\"IDR\"}"
curl -sS -X POST "$BASE_URL/api/v1/wallet/rollback" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"external_user_id":"player-1001","original_reference_id":"test-withdraw-1","rollback_reference_id":"test-rollback-1"}'
curl -sS "$BASE_URL/api/v1/wallet/balance?external_user_id=player-1001¤cy=IDR" \
-H "Authorization: Bearer $TOKEN"Seamless Wallet
In seamless mode, the operator wallet remains the balance authority. Configure the operator with wallet_type = seamless, an HTTPS callback_url, and an active credential. The backend records mutation state locally, then calls the operator wallet service.
Pada mode seamless, wallet operator tetap menjadi pemilik saldo utama. Konfigurasikan operator dengan wallet_type = seamless, callback_url HTTPS, dan credential aktif. Backend mencatat status mutasi secara lokal, lalu memanggil layanan wallet operator.
- The backend receives or creates a game wallet operation.
- For debit, credit, and rollback, it writes a pending ledger row.
- It sends a signed POST request to the operator callback URL.
- A valid success response completes the local ledger row.
- An unknown mutation outcome remains pending and is checked through transaction status.
- Backend menerima atau membuat operasi wallet game.
- Untuk debit, credit, dan rollback, backend menulis row ledger pending.
- Backend mengirim POST bertanda tangan ke callback URL operator.
- Respons sukses yang valid menyelesaikan row ledger lokal.
- Hasil mutasi yang belum pasti tetap pending dan diperiksa melalui transaction status.
Public API Triggers
Pemicu API Publik
The operator-facing backend API exposes the routes below. They use bearer authentication described above.
API backend yang digunakan operator menyediakan route berikut. Semua route menggunakan bearer authentication yang dijelaskan sebelumnya.
Method Path Request fields Notes
GET /api/v1/wallet/balance external_user_id (Required), currency (Required) Calls callback /balance.
POST /api/v1/wallet/debit external_user_id, reference_id, amount, currency (all Required) Calls callback /debit.
POST /api/v1/wallet/rollback external_user_id, original_reference_id, rollback_reference_id Calls callback /rollback.
GET /api/v1/wallet/transactions All query fields Optional; same filters as transfer documentation Reads local audit ledger.Method Path Field request Catatan
GET /api/v1/wallet/balance external_user_id (Wajib), currency (Wajib) Memanggil callback /balance.
POST /api/v1/wallet/debit external_user_id, reference_id, amount, currency (semua Wajib) Memanggil callback /debit.
POST /api/v1/wallet/rollback external_user_id, original_reference_id, rollback_reference_id Memanggil callback /rollback.
GET /api/v1/wallet/transactions Semua field query Opsional; filter sama dengan dokumentasi transfer Membaca audit ledger lokal.There is no public /api/v1/wallet/credit route. Credit is initiated by internal game settlement. There is also no public transaction-status route; reconciliation calls the operator callback directly.
Tidak ada route publik /api/v1/wallet/credit. Credit dibuat oleh settlement game internal. Tidak ada route publik transaction-status; proses rekonsiliasi memanggil callback operator secara langsung.
{
"external_user_id": "player-1001",
"reference_id": "bet-round-0001",
"amount": "1000.00",
"currency": "IDR"
}{
"status": true,
"code": "SUCCESS",
"data": {
"transaction_id": "7d64613c-4e45-4bc5-9fea-2ac0cf51cadd",
"balance_after": "99000",
"currency": "IDR",
"timestamp": "2026-06-12T01:00:00Z"
}
}{
"status": false,
"code": "TRANSACTION_STATUS_UNKNOWN",
"error": {}
}Callback Authentication and Signing
Autentikasi dan Signature Callback
The backend sends all seamless callbacks as POST JSON requests to the configured callback base URL. The operator must verify the HMAC before processing the request.
Backend mengirim semua callback seamless sebagai request POST JSON ke callback base URL yang dikonfigurasi. Operator wajib memverifikasi HMAC sebelum memproses request.
Header Requirement Description
Content-Type Required application/json
X-Signature Required Lowercase hex HMAC-SHA256.
X-Timestamp Required RFC3339; also included in the JSON body.
X-Key-Version Required Credential key version used for signing.Header Kebutuhan Deskripsi
Content-Type Wajib application/json
X-Signature Wajib HMAC-SHA256 dalam hex lowercase.
X-Timestamp Wajib RFC3339; juga terdapat pada body JSON.
X-Key-Version Wajib Versi credential yang digunakan untuk signature.Notes: Verify the exact raw JSON bytes received. Do not parse and re-encode the body before signature verification.
Catatan: Verifikasi byte JSON mentah yang diterima. Jangan parse lalu encode ulang body sebelum memverifikasi signature.
POST
/debit
2026-06-12T01:00:00Z
{"operator_code":"OPERATOR_A",...}operation = METHOD + "\n" + PATH + "\n" + X-Timestamp
message = operation + "\n" + raw_json_body
signature = hex(HMAC-SHA256(secret_key, message))Callback: Balance
The operator implements this endpoint to return the authoritative player balance.
Operator mengimplementasikan endpoint ini untuk mengembalikan saldo utama pemain.
Headers: Content-Type, X-Signature, X-Timestamp, X-Key-Version
Request fields
Field request
Field Type Requirement Description Example
operator_code string Required Canonical authenticated operator code. OPERATOR_A
external_user_id string Required Player external ID. player-1001
currency string Required Exact uppercase currency. IDR
request_id UUID Required Unique outbound request correlation ID. 4d33...78a2
timestamp string Required RFC3339 signing timestamp. 2026-06-12T01:00:00Z
metadata object Conditional Sent only when internal context exists. {"game_id":"duckhunters"}Field Tipe Kebutuhan Deskripsi Contoh
operator_code string Wajib Kode operator terautentikasi. OPERATOR_A
external_user_id string Wajib External ID pemain. player-1001
currency string Wajib Currency kapital persis. IDR
request_id UUID Wajib ID korelasi request outbound yang unik. 4d33...78a2
timestamp string Wajib Timestamp RFC3339 untuk signature. 2026-06-12T01:00:00Z
metadata object Kondisional Dikirim hanya jika konteks internal ada. {"game_id":"duckhunters"}Notes: Return HTTP 200. The response currency must exactly match the request. A timeout maps to UPSTREAM_TIMEOUT on the public API.
Catatan: Kembalikan HTTP 200. Currency pada respons harus sama persis dengan request. Timeout dipetakan menjadi UPSTREAM_TIMEOUT pada API publik.
{
"operator_code": "OPERATOR_A",
"external_user_id": "player-1001",
"currency": "IDR",
"request_id": "4d33e43a-85d9-4986-b96a-5c3a523d78a2",
"timestamp": "2026-06-12T01:00:00Z"
}{
"status": true,
"code": "SUCCESS",
"data": {
"balance": "100000.00",
"currency": "IDR"
}
}{
"status": false,
"code": "USER_NOT_FOUND",
"error": {}
}Callback: Debit
Deducts money from the operator-owned player wallet. The operator must process reference_id idempotently.
Mengurangi saldo wallet pemain milik operator. Operator wajib memproses reference_id secara idempotent.
Headers: Content-Type, X-Signature, X-Timestamp, X-Key-Version
Request fields
Field request
Field Type Requirement Description Example
operator_code string Required Canonical operator code. OPERATOR_A
external_user_id string Required Player external ID. player-1001
currency string Required Exact uppercase currency. IDR
request_id UUID Required Unique request correlation ID. 4d33...78a2
timestamp string Required RFC3339 signing timestamp. 2026-06-12T01:00:00Z
transaction_id UUID Required Backend outbound transaction ID. 9d1a...e042
reference_id string Required Stable financial idempotency key. bet-round-0001
amount decimal Required Positive decimal, max 2 places. 1000.00
metadata object Conditional Sent only when internal context exists. {"round_id":"round-1"}Field Tipe Kebutuhan Deskripsi Contoh
operator_code string Wajib Kode operator. OPERATOR_A
external_user_id string Wajib External ID pemain. player-1001
currency string Wajib Currency kapital persis. IDR
request_id UUID Wajib ID korelasi request yang unik. 4d33...78a2
timestamp string Wajib Timestamp RFC3339 untuk signature. 2026-06-12T01:00:00Z
transaction_id UUID Wajib ID transaksi outbound dari backend. 9d1a...e042
reference_id string Wajib Idempotency key finansial yang stabil. bet-round-0001
amount decimal Wajib Desimal positif, maksimal 2 angka. 1000.00
metadata object Kondisional Dikirim hanya jika konteks internal ada. {"round_id":"round-1"}Notes: On success, return matching reference_id, amount, and currency. Any HTTP non-200, malformed body, or mismatched success data becomes an unknown outcome.
Catatan: Pada respons sukses, kembalikan reference_id, amount, dan currency yang sama. HTTP selain 200, body rusak, atau data sukses yang berbeda dianggap sebagai hasil belum pasti.
{
"operator_code": "OPERATOR_A",
"external_user_id": "player-1001",
"currency": "IDR",
"request_id": "4d33e43a-85d9-4986-b96a-5c3a523d78a2",
"timestamp": "2026-06-12T01:00:00Z",
"transaction_id": "9d1af7a2-b8aa-4d54-a25b-f0f062c9e042",
"reference_id": "bet-round-0001",
"amount": "1000.00"
}{
"status": true,
"code": "SUCCESS",
"data": {
"transaction_id": "operator-debit-10001",
"reference_id": "bet-round-0001",
"amount": "1000.00",
"balance_after": "99000.00",
"currency": "IDR"
}
}{
"status": false,
"code": "INSUFFICIENT_BALANCE",
"error": {}
}Callback: Credit
Adds a game settlement or win to the operator-owned wallet. This callback is initiated internally; there is no public credit route.
Menambahkan settlement atau kemenangan game ke wallet milik operator. Callback ini dibuat secara internal; tidak ada route credit publik.
Headers: Content-Type, X-Signature, X-Timestamp, X-Key-Version
Request fields
Field request
Field Type Requirement Description Example
operator_code string Required Canonical operator code. OPERATOR_A
external_user_id string Required Player external ID. player-1001
currency string Required Exact uppercase currency. IDR
request_id UUID Required Unique request correlation ID. 6fb8...80dc
timestamp string Required RFC3339 signing timestamp. 2026-06-12T01:01:00Z
transaction_id UUID Required Backend outbound transaction ID. d1ae...f393
reference_id string Required Stable credit idempotency key. win-round-0001
amount decimal Required Positive decimal, max 2 places. 2500.00
metadata object Conditional Sent only when internal context exists. {"round_id":"round-1"}Field Tipe Kebutuhan Deskripsi Contoh
operator_code string Wajib Kode operator. OPERATOR_A
external_user_id string Wajib External ID pemain. player-1001
currency string Wajib Currency kapital persis. IDR
request_id UUID Wajib ID korelasi request yang unik. 6fb8...80dc
timestamp string Wajib Timestamp RFC3339 untuk signature. 2026-06-12T01:01:00Z
transaction_id UUID Wajib ID transaksi outbound dari backend. d1ae...f393
reference_id string Wajib Idempotency key credit yang stabil. win-round-0001
amount decimal Wajib Desimal positif, maksimal 2 angka. 2500.00
metadata object Kondisional Dikirim hanya jika konteks internal ada. {"round_id":"round-1"}Notes: Duplicate delivery with identical fields must return the original result and must not credit twice.
Catatan: Pengiriman ulang dengan field identik harus mengembalikan hasil awal dan tidak boleh menambah saldo dua kali.
{
"operator_code": "OPERATOR_A",
"external_user_id": "player-1001",
"currency": "IDR",
"request_id": "6fb82253-0602-4a31-b776-cf7a04cb80dc",
"timestamp": "2026-06-12T01:01:00Z",
"transaction_id": "d1ae0e35-9bc7-4059-a2e7-5fcc1a51f393",
"reference_id": "win-round-0001",
"amount": "2500.00"
}{
"status": true,
"code": "SUCCESS",
"data": {
"transaction_id": "operator-credit-10001",
"reference_id": "win-round-0001",
"amount": "2500.00",
"balance_after": "101500.00",
"currency": "IDR"
}
}{
"status": false,
"code": "IDEMPOTENCY_CONFLICT",
"error": {}
}Callback: Rollback
Reverses one completed debit or credit in the operator wallet.
Membalik satu debit atau credit yang sudah completed pada wallet operator.
Headers: Content-Type, X-Signature, X-Timestamp, X-Key-Version
Request fields
Field request
Field Type Requirement Description Example
operator_code string Required Canonical operator code. OPERATOR_A
external_user_id string Required Player external ID. player-1001
currency string Required Copied from original ledger row. IDR
request_id UUID Required Unique request correlation ID. 1a4b...da31
timestamp string Required RFC3339 signing timestamp. 2026-06-12T01:02:00Z
transaction_id UUID Required Backend outbound rollback ID. 7560...4900
reference_id string Required Rollback idempotency key. rollback-0001
original_reference_id string Required Original debit or credit reference. bet-round-0001
amount decimal Required Copied from original ledger row. 1000.00
metadata object Conditional Sent only when internal context exists. {"round_id":"round-1"}Field Tipe Kebutuhan Deskripsi Contoh
operator_code string Wajib Kode operator. OPERATOR_A
external_user_id string Wajib External ID pemain. player-1001
currency string Wajib Disalin dari ledger transaksi awal. IDR
request_id UUID Wajib ID korelasi request yang unik. 1a4b...da31
timestamp string Wajib Timestamp RFC3339 untuk signature. 2026-06-12T01:02:00Z
transaction_id UUID Wajib ID rollback outbound dari backend. 7560...4900
reference_id string Wajib Idempotency key rollback. rollback-0001
original_reference_id string Wajib Reference debit atau credit awal. bet-round-0001
amount decimal Wajib Disalin dari ledger transaksi awal. 1000.00
metadata object Kondisional Dikirim hanya jika konteks internal ada. {"round_id":"round-1"}Notes: The success response must return matching rollback reference, original reference, amount, and currency.
Catatan: Respons sukses harus mengembalikan rollback reference, original reference, amount, dan currency yang sama.
{
"operator_code": "OPERATOR_A",
"external_user_id": "player-1001",
"currency": "IDR",
"request_id": "1a4b1805-cd06-47ec-9f2a-b78a8799da31",
"timestamp": "2026-06-12T01:02:00Z",
"transaction_id": "75604c6c-d4db-432e-9330-a09850324900",
"reference_id": "rollback-0001",
"original_reference_id": "bet-round-0001",
"amount": "1000.00"
}{
"status": true,
"code": "SUCCESS",
"data": {
"transaction_id": "operator-rollback-10001",
"reference_id": "rollback-0001",
"original_reference_id": "bet-round-0001",
"amount": "1000.00",
"balance_after": "100000.00",
"currency": "IDR"
}
}{
"status": false,
"code": "TRANSACTION_ALREADY_ROLLED_BACK",
"error": {}
}Callback: Transaction Status
Used by the reconciliation worker to resolve a pending seamless mutation after an unknown outcome.
Digunakan oleh worker rekonsiliasi untuk menyelesaikan mutasi seamless yang pending setelah hasil belum pasti.
Headers: Content-Type, X-Signature, X-Timestamp, X-Key-Version
Request fields
Field request
Field Type Requirement Description Example
operator_code string Required Canonical operator code. OPERATOR_A
external_user_id string Required Player external ID. player-1001
currency string Required Original transaction currency. IDR
request_id UUID Required Unique request correlation ID. 34ed...c71d
timestamp string Required RFC3339 signing timestamp. 2026-06-12T01:05:00Z
reference_id string Required Original mutation reference to resolve. bet-round-0001Field Tipe Kebutuhan Deskripsi Contoh
operator_code string Wajib Kode operator. OPERATOR_A
external_user_id string Wajib External ID pemain. player-1001
currency string Wajib Currency transaksi awal. IDR
request_id UUID Wajib ID korelasi request yang unik. 34ed...c71d
timestamp string Wajib Timestamp RFC3339 untuk signature. 2026-06-12T01:05:00Z
reference_id string Wajib Reference mutasi awal yang akan diperiksa. bet-round-0001Notes: transaction_status must be completed, failed, or not_found. Optional comparison fields, when returned, must match the original transaction.
Catatan: transaction_status harus completed, failed, atau not_found. Field pembanding opsional harus sama dengan transaksi awal jika dikembalikan.
{
"operator_code": "OPERATOR_A",
"external_user_id": "player-1001",
"currency": "IDR",
"request_id": "34ed7529-f847-4910-91b0-f19bf7c7c71d",
"timestamp": "2026-06-12T01:05:00Z",
"reference_id": "bet-round-0001"
}{
"status": true,
"code": "SUCCESS",
"data": {
"transaction_status": "completed",
"operator_transaction_id": "operator-debit-10001",
"transaction_type": "debit",
"amount": "1000.00",
"currency": "IDR",
"reference_id": "bet-round-0001"
}
}{
"status": false,
"code": "TRANSACTION_NOT_FOUND",
"error": {}
}Testing Seamless Wallet
Pengujian Seamless Wallet
- Configure a seamless operator callback URL and active credential secret.
- Start the operator wallet at 100000.00.
- Call the public debit API for 1000.00. The operator callback receives
/debit; expected balance is 99000.00. - Return a credit of 2500.00 from a game settlement; expected balance is 101500.00.
- Rollback the debit; expected balance is 102500.00.
- Simulate a debit timeout, then answer
/transaction-statuswith the actual result.
- Konfigurasikan callback URL operator seamless dan secret credential aktif.
- Mulai dengan saldo wallet operator 100000.00.
- Panggil API debit publik sebesar 1000.00. Callback operator menerima
/debit; saldo yang diharapkan 99000.00. - Kembalikan credit settlement game sebesar 2500.00; saldo yang diharapkan 101500.00.
- Rollback debit; saldo yang diharapkan 102500.00.
- Simulasikan timeout debit, lalu jawab
/transaction-statusdengan hasil sebenarnya.
BASE_URL="https://api.example.com"
TOKEN="replace-with-seamless-operator-token"
curl -sS -X POST "$BASE_URL/api/v1/wallet/debit" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"external_user_id":"player-1001","reference_id":"seamless-test-debit-1","amount":"1000.00","currency":"IDR"}'SECRET="replace-with-operator-secret"
TIMESTAMP="2026-06-12T01:00:00Z"
PATH_VALUE="/debit"
BODY='{"operator_code":"OPERATOR_A","external_user_id":"player-1001","currency":"IDR","request_id":"4d33e43a-85d9-4986-b96a-5c3a523d78a2","timestamp":"2026-06-12T01:00:00Z","transaction_id":"9d1af7a2-b8aa-4d54-a25b-f0f062c9e042","reference_id":"seamless-test-debit-1","amount":"1000.00"}'
printf 'POST\n%s\n%s\n%s' "$PATH_VALUE" "$TIMESTAMP" "$BODY" |
openssl dgst -sha256 -hmac "$SECRET" -hexKnown Error Codes
Kode Error yang Dikenal
The codes below are returned by the public wallet handlers or accepted from the seamless operator wallet service.
Kode berikut dikembalikan oleh handler wallet publik atau diterima dari layanan seamless wallet operator.
Code Meaning
SUCCESS Request completed successfully.
VALIDATION_ERROR Missing field, invalid JSON, wrong content type, or unknown field.
UNAUTHORIZED Bearer token is missing, invalid, expired, revoked, or operator is inactive.
FORBIDDEN Client IP or request operator identity is not allowed.
RATE_LIMITED Request rate limit was exceeded.
USER_NOT_FOUND Player was not found for the authenticated operator.
USER_INACTIVE Player status is not active.
INVALID_CURRENCY Currency is not exactly three uppercase ASCII letters.
CURRENCY_MISMATCH Request currency differs from player or upstream currency.
INVALID_AMOUNT Amount is zero, negative, or has more than two decimal places.
AMOUNT_LIMIT_EXCEEDED Amount is greater than 1000000000000.00.
BALANCE_OVERFLOW Balance mutation failed its safety checks.
INSUFFICIENT_BALANCE Wallet cannot cover the debit.
WALLET_TYPE_NOT_SUPPORTED Route cannot run for the authenticated wallet type.
IDEMPOTENCY_CONFLICT Reference was reused with different immutable fields.
TRANSACTION_NOT_FOUND Original transaction or requested transaction does not exist.
TRANSACTION_NOT_ROLLBACKABLE Original transaction is not completed or cannot be reversed.
TRANSACTION_ALREADY_ROLLED_BACK Original transaction was already reversed.
INVALID_TRANSACTION_TYPE Transaction list type filter is invalid.
INVALID_TRANSACTION_STATUS Transaction list status filter is invalid.
INVALID_PAGINATION limit or offset is outside the accepted range.
PROVIDER_UNAVAILABLE Seamless wallet provider is unavailable.
UPSTREAM_TIMEOUT Seamless balance or status request timed out.
TRANSACTION_STATUS_UNKNOWN Mutation result is uncertain and requires reconciliation.
UNSUPPORTED_OPERATION Wallet strategy does not support the requested operation.
INTERNAL_ERROR Unexpected internal or configuration failure.Code Arti
SUCCESS Request berhasil.
VALIDATION_ERROR Field kurang, JSON invalid, content type salah, atau field tidak dikenal.
UNAUTHORIZED Bearer token tidak ada/tidak valid, credential kedaluwarsa/dicabut, atau operator tidak aktif.
FORBIDDEN IP client atau identitas operator pada request tidak diizinkan.
RATE_LIMITED Batas jumlah request terlampaui.
USER_NOT_FOUND Pemain tidak ditemukan untuk operator terautentikasi.
USER_INACTIVE Status pemain tidak aktif.
INVALID_CURRENCY Currency bukan tiga huruf ASCII kapital.
CURRENCY_MISMATCH Currency request berbeda dari pemain atau upstream.
INVALID_AMOUNT Amount nol, negatif, atau lebih dari dua desimal.
AMOUNT_LIMIT_EXCEEDED Amount lebih besar dari 1000000000000.00.
BALANCE_OVERFLOW Mutasi saldo gagal pada pemeriksaan keamanan.
INSUFFICIENT_BALANCE Saldo tidak cukup untuk debit.
WALLET_TYPE_NOT_SUPPORTED Route tidak dapat dijalankan untuk tipe wallet operator.
IDEMPOTENCY_CONFLICT Reference digunakan ulang dengan field penting yang berbeda.
TRANSACTION_NOT_FOUND Transaksi awal atau transaksi yang diminta tidak ditemukan.
TRANSACTION_NOT_ROLLBACKABLE Transaksi awal belum completed atau tidak dapat dibalik.
TRANSACTION_ALREADY_ROLLED_BACK Transaksi awal sudah dibalik.
INVALID_TRANSACTION_TYPE Filter type pada daftar transaksi tidak valid.
INVALID_TRANSACTION_STATUS Filter status pada daftar transaksi tidak valid.
INVALID_PAGINATION limit atau offset di luar rentang.
PROVIDER_UNAVAILABLE Provider seamless wallet tidak tersedia.
UPSTREAM_TIMEOUT Request balance atau status seamless mengalami timeout.
TRANSACTION_STATUS_UNKNOWN Hasil mutasi belum pasti dan memerlukan rekonsiliasi.
UNSUPPORTED_OPERATION Strategi wallet tidak mendukung operasi.
INTERNAL_ERROR Kegagalan internal atau konfigurasi yang tidak terduga.Seamless upstream code mapping
Pemetaan kode upstream seamless
The operator wallet may also return DUPLICATE_TRANSACTION (mapped to IDEMPOTENCY_CONFLICT), OPERATOR_SUSPENDED (mapped to provider unavailable), and INVALID_SIGNATURE or INVALID_TIMESTAMP (mapped to INTERNAL_ERROR). A mutation response with INTERNAL_ERROR is treated as an unknown outcome.
Wallet operator juga dapat mengembalikan DUPLICATE_TRANSACTION (dipetakan ke IDEMPOTENCY_CONFLICT), OPERATOR_SUSPENDED (dipetakan ke provider unavailable), serta INVALID_SIGNATURE atau INVALID_TIMESTAMP (dipetakan ke INTERNAL_ERROR). Respons mutasi dengan INTERNAL_ERROR dianggap sebagai hasil belum pasti.