S2S Protocol Overview
The S2S (Server-to-Server) protocol is Predictu's callback-based integration system for casino operators. It follows the same architectural pattern used by established gaming aggregators like Betby and SoftSwiss - Predictu calls the operator's backend for every wallet operation, and the operator remains the sole owner of player balances at all times.
How It Works
When a player interacts with the Predictu prediction market widget embedded on the casino site, every financial operation triggers an outbound HTTP POST from Predictu to the operator's registered callback_url. The operator processes the request, debits or credits the player's wallet, and returns a response.
BET_MAKE request is POSTed to the operator's callback_url containing the player ID, amount to debit, and full bet context (market, outcome, odds, shares).{ status: "OK", balance: 95000, transaction_id: "op-tx-123" }.OK, Predictu records the trade, updates the player's position, and adjusts casino exposure. Player (Browser) Predictu Platform Operator Backend
───────────────── ────────────────── ──────────────────
│ │ │
│ Click "Buy YES" │ │
├────────────────────────►│ │
│ │ │
│ │ POST /api/callback │
│ │ Authorization: Bearer │
│ │ { method: "BET_MAKE", │
│ │ params: { amount, │
│ │ player_id, bet } } │
│ ├─────────────────────────►│
│ │ │
│ │ { status: "OK", │
│ │ balance: 95000, │
│ │ transaction_id } │
│ │◄─────────────────────────┤
│ │ │
│ Trade confirmed + │ │
│ balance updated │ │
│◄────────────────────────┤ │
│ │ │Request Envelope
Every S2S callback from Predictu uses the same top-level JSON envelope. Themethod field determines the type of operation, and the paramsobject contains method-specific data.
{
"method": "BET_MAKE",
"timestamp": "2026-03-19T14:30:00.000Z",
"request_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"operator_id": "op_abc123",
"params": {
"player_id": "player_456",
"transaction_id": "tx_789",
"amount": 5200,
"currency": "USD",
"bet": {
"market_id": "mkt_001",
"market_title": "Will Bitcoin exceed $100k by June 2026?",
"outcome": "yes",
"odds": 1.92,
"shares": 100,
"execution_price": 52
}
}
}Envelope Fields
| Field | Type | Description |
|---|---|---|
method | string | The callback method being invoked (e.g. BET_MAKE, BALANCE). See supported methods below. |
timestamp | string | ISO 8601 timestamp of when Predictu generated the request. Example: 2026-03-19T14:30:00.000Z. Always UTC. |
request_id | string | UUID v4 - the idempotency key for this request. If you receive the same request_id twice, return DUPLICATE_TRANSACTION with the cached result. |
operator_id | string | Your operator UUID in Predictu's system. Use this to route multi-brand callbacks if you operate multiple skins. |
params | object | Method-specific parameters. Structure varies per method. See Callback Methods for full schemas. |
Response Envelope
The operator must respond with a JSON object containing at minimum a status field. Additional fields depend on the method.
{
"status": "OK",
"balance": 95000,
"transaction_id": "op-tx-abc-123"
}Response Fields
| Field | Type | Required | Description |
|---|---|---|---|
status | string | Yes | One of the response status codes listed below. |
balance | number | Recommended | Player's current balance in subunits after the operation. Used by Predictu to keep the UI in sync. |
transaction_id | string | Recommended | Your internal transaction ID for this operation. Stored by Predictu for reconciliation. |
error_message | string | No | Human-readable error description. Only used when status is not OK. |
Response Status Codes
Predictu recognizes five standard status codes in the response. Your callback endpoint must return one of these in the status field.
| Status | Meaning | Predictu Behavior |
|---|---|---|
OK | Operation succeeded. The wallet was updated. | Marks the S2S transaction as success. Proceeds with trade execution. |
INSUFFICIENT_FUNDS | Player does not have enough balance for the debit. | Marks as failed. No retry. Returns "Insufficient balance" to the player. |
PLAYER_NOT_FOUND | The player_id does not exist in the operator's system. | Marks as failed. No retry. This typically indicates a mapping issue. |
DUPLICATE_TRANSACTION | This request_id has already been processed. Return the cached result. | Treated as success. Predictu uses the cached balance and transaction ID. |
ERROR | Generic server error on the operator side. | Marks as retrying. Predictu will retry with exponential backoff up to the configured max_retries (default 3). |
INSUFFICIENT_FUNDS and PLAYER_NOT_FOUND are business-level errors that are never retried. Only ERROR and HTTP-level failures (timeouts, 5xx) trigger automatic retries.Monetary Amounts - Subunits
All monetary values in the S2S protocol are expressed in subunits (cents). This avoids floating-point precision issues and is standard practice in payment systems.
| Display Amount | Subunit Value | Field Example |
|---|---|---|
| $1.00 | 100 | "amount": 100 |
| $52.00 | 5200 | "amount": 5200 |
| $0.50 | 50 | "amount": 50 |
| $1,000.00 | 100000 | "balance": 100000 |
| $0.00 | 0 | "amount": 0 (used in BET_LOST) |
52.00 must be sent as 5200. Predictu's internal helpers toSubunits(52.00) and fromSubunits(5200) handle the conversion. Your operator backend should use the same pattern.// Conversion helpers (reference implementation)
function toSubunits(dollars) {
return Math.round(dollars * 100);
}
function fromSubunits(subunits) {
return Math.round(subunits) / 100;
}
// Examples:
toSubunits(1.00) // → 100
toSubunits(52.00) // → 5200
fromSubunits(100) // → 1.00
fromSubunits(5200) // → 52.00Supported Methods
The S2S protocol defines eight callback methods covering the full lifecycle of a prediction market bet - from placement through settlement or early sale.
| Method | Direction | Money Movement | Description |
|---|---|---|---|
PING | - | None | Health check. Operator should respond with OK. Used during onboarding and monitoring. |
BALANCE | - | None | Fetch a player's current balance. Called before every trade to validate funds. |
BET_MAKE | Debit | Player → Operator | Debit player for a new bet. Contains full bet context (market, outcome, odds, shares, execution price). |
BET_WIN | Credit | Operator → Player | Credit player for a winning bet after market resolution. Amount = shares × $1.00 (100 subunits per share). |
BET_LOST | - | None (amount: 0) | Notify operator of a losing bet after market resolution. No money movement - informational only for bookkeeping. |
BET_REFUND | Credit | Operator → Player | Full refund of the original bet cost. Triggered when a market is voided or cancelled. |
BET_ROLLBACK | Credit | Operator → Player | Reverse a BET_MAKE debit that failed downstream. Compensating transaction for atomicity. |
BET_SELL | Credit | Operator → Player | Credit player for selling a position before market resolution. Includes realized P&L data. |
Bet Lifecycle
A prediction market bet follows one of three paths after placement. Every path starts with BET_MAKE and ends with exactly one terminal callback.
Path A: Winning Bet
BET_MAKE (debit $52) → market resolves YES → BET_WIN (credit $100)
Player profits: $100 - $52 = $48Path B: Losing Bet
BET_MAKE (debit $52) → market resolves NO → BET_LOST (amount: 0)
Player loses: $52 (already debited)Path C: Early Sale
BET_MAKE (debit $52) → price moves to 65¢ → BET_SELL (credit $63.40)
Player profits: $63.40 - $52.00 = $11.40Path D: Market Void / Refund
BET_MAKE (debit $52) → market voided → BET_REFUND (credit $52)
Player is made whole: $0 net impactPath E: Failed Trade (Rollback)
BET_MAKE (debit $52) → trade recording fails → BET_ROLLBACK (credit $52)
Player is made whole: $0 net impact (compensating transaction)Retry Behavior
When a callback fails due to a network error, HTTP 5xx response, or ERROR status, Predictu automatically retries with exponential backoff.
| Config | Default | Description |
|---|---|---|
max_retries | 3 | Maximum number of retry attempts after the initial attempt. |
retry_backoff_ms | 1000 | Base backoff interval in milliseconds. Multiplied by 2^(attempt-1). |
timeout_ms | 10000 | HTTP request timeout per attempt. |
Attempt 1: immediate
Attempt 2: after 1,000ms (1s)
Attempt 3: after 2,000ms (2s)
Attempt 4: after 4,000ms (4s)
Total max wait: ~7 seconds before the transaction is marked as failed.request_id, your callback handler must be idempotent. If you see arequest_id you have already processed, return DUPLICATE_TRANSACTIONwith the cached balance and transaction ID.Request Headers
Every S2S callback includes these HTTP headers:
| Header | Example Value | Description |
|---|---|---|
Content-Type | application/json | Always JSON. |
Authorization | Bearer eyJhbGciOiJSUzI1NiIs... | Signed JWT. See JWT Authentication. |
X-Request-Id | a1b2c3d4-e5f6-7890-abcd-ef1234567890 | Same as request_id in the body. Convenience header for logging. |
X-Predictu-Method | BET_MAKE | Same as method in the body. Convenience header for routing. |
Operator Configuration
Each operator has an S2S configuration record stored in Predictu's operator_api_configtable. This is managed via the God Mode admin dashboard during operator onboarding.
| Field | Type | Description |
|---|---|---|
callback_url | string | HTTPS endpoint where Predictu will POST all S2S callbacks. |
public_key_pem | string | Public key in PEM format. Operator fetches this to verify JWT signatures. |
is_sandbox | boolean | Whether this config is for sandbox/testing or production. |
timeout_ms | number | HTTP request timeout per attempt (default 10000ms). |
max_retries | number | Maximum retry attempts (default 3). |
retry_backoff_ms | number | Base backoff interval in ms (default 1000). |
ip_whitelist | string[] | Optional list of allowed IPs. If set, Predictu only sends callbacks to these IPs. |
is_active | boolean | Kill switch. Set to false to immediately stop all S2S callbacks. |
Transaction Recording
Every S2S callback is recorded in two tables for full auditability:
s2s_transactions
The primary transaction record. One row per callback attempt. Tracks the full lifecycle from pending through success or failed.
| Column | Type | Description |
|---|---|---|
id | uuid | Primary key |
operator_id | uuid | FK to operators table |
request_id | uuid | Idempotency key (unique) |
method | text | S2S method name |
player_id | text | Operator's player ID |
user_id | uuid | Predictu's internal user ID |
amount_subunits | integer | Amount in cents |
currency | text | Currency code (e.g. USD) |
parent_transaction_id | text | Links to originating BET_MAKE |
status | text | pending | success | failed | retrying |
operator_status | text | Status returned by operator |
operator_transaction_id | text | Operator's transaction ID |
operator_balance_after | integer | Balance reported by operator |
attempt_count | integer | Number of delivery attempts |
last_error | text | Last error message if any |
market_id | text | Related market ID |
trade_id | uuid | Related trade ID |
is_sandbox | boolean | Sandbox vs production flag |
metadata | jsonb | Full params snapshot |
s2s_callback_log
Detailed HTTP-level log of every callback attempt. Includes the full request body, response status code, response body, and round-trip duration. Used for debugging and operator support.
Next Steps
- Callback Methods - Deep dive into each method's request/response schema
- JWT Authentication - Signature verification, key management, and replay prevention
- Reference Implementation - Working Express.js casino demo with SQLite wallets
- Trade Execution - How the trade executor orchestrates S2S callbacks
