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: any HTTP 2xx response code
- Failure behavior: non-2xx or timeout → immediate error, no 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
It is recommended to register your callback_url without a trailing slash, though either form will work.
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",
"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 |
provider_code | string | No | 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",
"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 |
provider_code | string | No | 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",
"provider_code": "evo",
"round_id": "round_xyz",
"game_code": "baccarat_classic",
"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 |
provider_code | string | No | 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": "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",
"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 |
provider_code | string | No | 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 |
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) |
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.
Retry Policy
There is currently no automatic retry for failed callbacks. If a callback returns a non-2xx status code, or if the request times out (> 5 seconds), Ruby propagates the error immediately back to the game provider. No re-attempt is made.
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