Callback Specification
This page documents the four HTTP endpoints that your server must implement to support Seamless Wallet integration. Ruby calls these endpoints in real time for every game event.
Overview
All four endpoints share the following properties:
- Method:
POST - Base URL: your configured
callback_url - Content-Type:
application/json(request and response) - Authentication: HMAC-SHA256 signature headers (see Callback Authentication)
- Timeout: 5 seconds hard cutoff; target < 3 seconds
- Success criteria: HTTP 2xx plus the valid JSON response contract below
- Failure behavior: non-2xx, timeout, oversized body, malformed JSON, or an invalid response field → immediate error, no application-level automatic retry
URL Construction
Ruby strips any trailing slash from your callback_url before appending the path:
callback_url = "https://wallet.example.com/ruby"
→ POST https://wallet.example.com/ruby/balance
→ POST https://wallet.example.com/ruby/debit
→ POST https://wallet.example.com/ruby/credit
→ POST https://wallet.example.com/ruby/rollback
The URL must be absolute HTTPS and must not contain credentials, a query string, or a fragment. A trailing slash is accepted and removed when the Brand is saved.
Successful Response Contract
Every successful body must be an uncompressed JSON object no larger than 64 KiB;
Ruby sends Accept-Encoding: identity and rejects any response with a compressed
Content-Encoding. Every required balance or balance_before field is a strict, non-negative
DECIMAL(18,2) strings: up to 16 integer digits, with an optional one- or two-digit
fractional part. JSON numbers, signs, exponents, NaN, Infinity, missing fields,
and out-of-range values are rejected.
POST /balance
Query a player's current balance. This is a stateless read — no funds are moved and no side effects are expected.
Request
{
"player_id": 12345,
"username": "player_handle",
"external_player_id": "brand-player-guid-001",
"provider_code": "evo"
}
| Field | Type | Required | Description |
|---|---|---|---|
player_id | integer | Yes | Ruby's internal player identifier |
username | string | No | Player's display name, if available |
external_player_id | string | No | Your stable player identifier, if supplied during launch |
provider_code | string | Yes | Game provider code (e.g., "evo", "pg") |
Response
{
"balance": "1250.00"
}
| Field | Type | Description |
|---|---|---|
balance | string | Current player balance as a decimal string (e.g., "1250.00") |
Notes
balanceis a stateless query. There is notransaction_idand no idempotency requirement.- Always return the current real-time balance for the player.
POST /debit
Deduct funds from a player's wallet. Called when a player places a bet.
Request
{
"player_id": 12345,
"amount": "100.50",
"transaction_id": "txn_bet_abc123",
"username": "player_handle",
"external_player_id": "brand-player-guid-001",
"provider_code": "evo",
"round_id": "round_xyz",
"game_code": "baccarat_classic",
"ref_id": "external_ref_001",
"memo": "Bet on Baccarat round 7"
}
| Field | Type | Required | Description |
|---|---|---|---|
player_id | integer | Yes | Ruby's internal player identifier |
amount | string | Yes | Amount to deduct, as a decimal string (e.g., "100.50") |
transaction_id | string | Yes | Unique transaction identifier — use this for idempotency |
username | string | No | Player's display name |
external_player_id | string | No | Your stable player identifier, if supplied during launch |
provider_code | string | Yes | Game provider code |
round_id | string | No | Game round identifier |
game_code | string | No | Game identifier within the provider |
ref_id | string | No | External reference ID from the game provider |
memo | string | No | Human-readable description of the transaction |
Response
{
"balance": "1149.50",
"balance_before": "1250.00"
}
| Field | Type | Description |
|---|---|---|
balance | string | Player's balance after the debit |
balance_before | string | Player's balance before the debit |
Idempotency
If you receive a POST /debit request with a transaction_id you have already successfully processed, return the same response as the original call. Do not deduct funds again.
If the original transaction is still in progress (e.g., due to a race condition), serialize access to prevent double-debit.
Error Cases
| Scenario | Recommended HTTP Status |
|---|---|
| Insufficient funds | 402 Payment Required or 422 Unprocessable Entity |
| Player not found | 404 Not Found |
| Player account suspended | 403 Forbidden |
| Internal server error | 500 Internal Server Error |
Any non-2xx response is treated as a failure by Ruby.
POST /credit
Add funds to a player's wallet. Called when a player wins or a settlement occurs.
Request
{
"player_id": 12345,
"amount": "200.00",
"transaction_id": "txn_win_def456",
"username": "player_handle",
"external_player_id": "brand-player-guid-001",
"provider_code": "evo",
"round_id": "round_xyz",
"game_code": "baccarat_classic",
"bet_amount": "100.50",
"original_transaction_id": "txn_bet_abc123",
"ref_id": "external_ref_002",
"memo": "Win payout for Baccarat round 7"
}
| Field | Type | Required | Description |
|---|---|---|---|
player_id | integer | Yes | Ruby's internal player identifier |
amount | string | Yes | Amount to credit, as a decimal string |
transaction_id | string | Yes | Unique transaction identifier — use this for idempotency |
username | string | No | Player's display name |
external_player_id | string | No | Your stable player identifier, if supplied during launch |
provider_code | string | Yes | Game provider code |
round_id | string | No | Game round identifier |
game_code | string | No | Game identifier within the provider |
bet_amount | string | No | Original stake amount when this credit settles a prior debit |
original_transaction_id | string | No | Transaction ID of the original debit/reserve being settled or refunded |
standalone_credit | boolean | No | Present as true when the credit has no matching debit, such as a provider bonus; omitted otherwise |
ref_id | string | No | External reference ID from the game provider |
memo | string | No | Human-readable description of the transaction |
Response
{
"balance": "1349.50",
"balance_before": "1149.50"
}
| Field | Type | Description |
|---|---|---|
balance | string | Player's balance after the credit |
balance_before | string | Player's balance before the credit |
Idempotency
If you receive a POST /credit request with a transaction_id you have already successfully processed, return the same response as the original call. Do not credit funds again.
POST /rollback
Reverse a prior debit transaction. Called when a round is voided or cancelled after a bet was already placed.
Request
{
"player_id": 12345,
"amount": "100.50",
"transaction_id": "txn_rollback_ghi789",
"username": "player_handle",
"external_player_id": "brand-player-guid-001",
"provider_code": "evo",
"round_id": "round_xyz",
"ref_id": "external_ref_003"
}
| Field | Type | Required | Description |
|---|---|---|---|
player_id | integer | Yes | Ruby's internal player identifier |
amount | string | Yes | Amount to restore, as a decimal string |
transaction_id | string | Yes | Unique transaction identifier for this rollback — use this for idempotency |
username | string | No | Player's display name |
external_player_id | string | No | Your stable player identifier, if supplied during launch |
provider_code | string | Yes | Game provider code |
round_id | string | No | Game round identifier |
ref_id | string | No | External reference ID from the game provider |
Note: game_code and memo are not included in rollback requests.
Response
{
"balance": "1250.00"
}
| Field | Type | Description |
|---|---|---|
balance | string | Player's balance after the rollback |
Note: balance_before is not included in the rollback response.
Idempotency
If you receive a POST /rollback request with a transaction_id you have already successfully processed, return the same response as the original call. Do not restore funds again.
Field Types Reference
| Field | Type | Notes |
|---|---|---|
player_id | integer | Always present in all four callbacks |
amount | string | Decimal representation, e.g. "100.50". Parse with Decimal or equivalent — do not use floating-point |
balance | string | Decimal representation of balance after transaction |
balance_before | string | Decimal representation of balance before transaction (debit/credit only) |
transaction_id | string | Opaque unique string; use as a deduplication key |
username | string | May be absent; do not depend on it for player lookup |
external_player_id | string | Stable brand-side player identifier when supplied during launch; prefer it over username for wallet lookup |
provider_code | string | Short code identifying the game provider |
round_id | string | Game-level round identifier |
game_code | string | Provider-specific game identifier (not present in rollback) |
bet_amount | string | Original stake amount for settlement credits |
original_transaction_id | string | Original debit/reserve transaction ID linked to a settlement or refund credit |
standalone_credit | boolean | Marks credit-only events that have no prior debit |
ref_id | string | External reference from the game provider; may be absent |
memo | string | Human-readable note; may be absent (not present in rollback) |
Idempotency Summary
| Endpoint | transaction_id | Idempotency Required |
|---|---|---|
/balance | No | No — stateless read |
/debit | Yes | Yes |
/credit | Yes | Yes |
/rollback | Yes | Yes |
Ruby records a successful response for each transaction_id. If Ruby calls you again with the same transaction_id (due to network uncertainty), your server must return the original result. Ruby does not rely on re-processing to be safe — but your server must defend against it independently.
Bind each key to the complete first request. An exact retry returns the stored result; reuse with a different player, provider, amount, or context must be rejected and alerted.
Retry Policy
There is currently no application-level automatic retry for failed callbacks. If a callback returns a non-2xx status code, is invalid, or times out (> 5 seconds), Ruby propagates the error immediately back to the game provider. A game provider may independently retry its original event; such a retry carries the same stable wallet transaction_id, so your idempotency guarantee remains mandatory.
Plan your error handling accordingly:
- Return 5xx only for transient errors you expect to be retried manually
- Return 4xx for permanent business errors (insufficient funds, player suspended)
- Ensure your server can respond within 5 seconds under load