Node.js integration
Connect your website with Node.js + PostgreSQL. Follow the setup, then integrate player sessions, wallets and bet results.
{
"username": "player_example",
"currency": "TRY",
"language": "en",
"origin": "https://customer.example"
}
Start with Node.js + PostgreSQL#
Import the SQL, set your connection details and sign in.
- Download the Node.js + PostgreSQL demo below.
- Import
demo.sqlinto an empty PostgreSQL database. - Enter the API and database credentials in
.env. - Open the application and log in with
test / 123.
| Demo account | Value |
|---|---|
| Username | test |
| Password | 123 |
| Wallet balance | 100.00 in each of 16 currencies |
The demo covers login, currency selection, sportsbook, bet history, design controls and home page widgets. For a live integration, connect the player lookup to your existing member session.
Connection settings#
Use separate addresses for server requests and the player interface.
| Setting | Value / purpose |
|---|---|
| API | https://admin.sportsbookapi.work |
| Sportsbook | https://sports.sportsbookapi.work |
| site_key | Your integration identifier. |
| site_secret | Server-side HMAC credential. |
| origin |
https://customer.example — your authorized website origin, without
a path.
|
| callback_url | Your public HTTPS endpoint on port 443. |
Send signed API requests from your server. Do not send a browser Origin header to the server API. The launch payload’s origin identifies the website that hosts the iframe. If an IP allowlist is enabled, your outbound server IP must be authorized.
Node.js setup#
Node.js 22.9+ · PostgreSQL 14+ · HTTPS
-
Create an empty PostgreSQL database. Import
demo.sqlusing psql or pgAdmin. -
Copy
.env.exampleto.envand fill in your API, domain and PostgreSQL connection details. -
Run
npm ci, thennpm start. Forward your HTTPS domain to127.0.0.1:3000;nginx.confincludes a proxy example.
psql -h 127.0.0.1 -U YOUR_DATABASE_USER -d YOUR_DATABASE_NAME -f demo.sql
cp .env.example .env
# Fill in .env, then:
npm ci
npm start
SPORTS_SITE_KEY=YOUR_SITE_KEY
SPORTS_SITE_SECRET=YOUR_SITE_SECRET
CUSTOMER_ORIGIN=https://customer.example
DB_HOST=127.0.0.1
DB_PORT=5432
DB_NAME=YOUR_DATABASE_NAME
DB_USER=YOUR_DATABASE_USER
DB_PASSWORD=YOUR_DATABASE_PASSWORD
The SQL creates test / 123 and 16 wallets. Login sessions are stored in
PostgreSQL. The HTTPS proxy must preserve the Host header.
| File | Purpose |
|---|---|
| server.mjs | Application, login, page routing and launch endpoint |
| config.mjs / .env | Connection settings |
| client.mjs | Signed server API requests |
| callback.mjs | Wallet callbacks and duplicate protection |
| session.mjs / pages.mjs | Login sessions and page templates |
| demo.sql | Tables, test account and 16 wallets |
| public/ | Browser scripts and styles |
| Address | Page |
|---|---|
| /sports | Sportsbook and login |
| /account | Account and independent modals |
| /widgets | Live, upcoming and popular leagues |
| /integration/launch | Fresh launch URL |
| /integration/callback | Callback URL |
Choose your wallet model#
Your site is configured for seamless or ledger mode.
| Model | Balance owner | Integration |
|---|---|---|
| Seamless | Your wallet | The sportsbook requests the balance and sends signed wallet callbacks. The included applications implement this flow. |
| Ledger | Sportsbook wallet | Transfer funds with the wallet API. Coupon events report results; do not apply their payout a second time. |
Site betting credit and a player’s wallet balance are separate accounts.
Sign API requests#
Every server request carries an HMAC-SHA256 signature.
| Header | Value |
|---|---|
| X-Site-Key | YOUR_SITE_KEY |
| X-Timestamp | UTC Unix seconds; clock difference <120 seconds. |
| X-Nonce | A fresh 16–128 character value for every HTTP attempt. |
| X-Signature | Lowercase hex HMAC; no sha256= prefix. |
timestamp + "\n" +
nonce + "\n" +
METHOD + "\n" +
path_and_query + "\n" +
SHA256(raw_body)
Use the uppercase method and the exact path, including a query string when present. Sign and send the same UTF-8 body bytes. A GET request uses an empty body. Generate a fresh nonce when retrying, while keeping the financial idempotency key unchanged.
import { createHash, createHmac, randomBytes } from 'node:crypto';
const body = JSON.stringify(payload);
const timestamp = String(Math.floor(Date.now() / 1000));
const nonce = randomBytes(24).toString('hex');
const hash = createHash('sha256').update(body).digest('hex');
const message = [timestamp, nonce, 'POST', path, hash].join('\n');
const signature = createHmac('sha256', siteSecret).update(message).digest('hex');
Create a player session#
POST /api/integration/launch
Create a fresh launch when a player opens the sportsbook. The returned link lasts 60 seconds and can be used once. Member identity comes from your authenticated server session.
{
"guest": false,
"username": "player_example",
"external_user_id": "42",
"currency": "TRY",
"language": "en",
"origin": "https://customer.example",
"callback_url": "https://customer.example/integration/callback"
}
| Field | Required | Meaning |
|---|---|---|
| guest | No | true for browsing without a member; no betting. Defaults to false. |
| username | Member | 1–80 letters, digits, spaces or _.@-. |
| external_user_id | No | Your own member identifier. |
| currency | No | Selected member wallet; defaults to site currency. |
| language | No | Interface language. Send en or tr explicitly. |
| origin | Yes | Authorized HTTPS website origin. |
| callback_url | Conditional | Required for seamless members if not configured. This address is shared by the entire site. |
{
"ok": true,
"data": {
"url": "https://sports.sportsbookapi.work/?site_key=YOUR_SITE_KEY&lang=en#/home?launch=LAUNCH_TOKEN",
"launch_token": "LAUNCH_TOKEN",
"site_key": "YOUR_SITE_KEY",
"expires_at": "2026-09-14T12:01:00.000Z"
}
}
For a guest launch, set guest:true and omit the member fields. Keep one
stable callback URL for the site. Never reuse a launch link for a second iframe.
Send an API request#
Use the included signing function or implement the HTTP contract yourself.
import { sportsbookClient } from './client.mjs';
const api = sportsbookClient({
siteKey: process.env.SPORTS_SITE_KEY,
siteSecret: process.env.SPORTS_SITE_SECRET,
});
const launch = await api.launch(payload);
const iframeUrl = launch.url;
Use your authenticated member identity to build the payload. The member password is never sent to the sportsbook API.
Display the sportsbook#
The demo renders the main iframe automatically.
To embed it in another page, load the SDK and obtain a fresh launch from your server:
<div id="sportsbook" style="height:85vh;min-height:640px"></div>
<script src="/assets/sportsbookapi.js"></script>
const response = await fetch("/integration/launch", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({ action: "launch", csrf: csrfToken }),
});
const result = await response.json();
if (!response.ok || !result.ok) throw new Error("Launch failed");
const sportsbook = SportsbookAPI.mount("#sportsbook", {
sportsOrigin: "https://sports.sportsbookapi.work",
launchUrl: result.url,
language: "en",
});
csrfToken comes from the authenticated host session. Without the SDK, set
iframe src to the returned URL and implement the message contract below.
Home page widgets#
Place live matches, upcoming matches and popular leagues anywhere on your website.
const widget = SportsbookAPI.mountWidget("#live-matches", {
section: "live",
sportsPage: "/sports",
launchEndpoint: "/integration/launch",
csrfToken,
language: "en",
});
// Remove the widget when its host component is removed.
// widget.destroy();
| Option | Meaning |
|---|---|
| section |
live, upcoming, popular-leagues,
all
|
| sportsPage | Your full sportsbook page on the same HTTPS origin. |
| launchEndpoint | Your server endpoint; receives action=widget-launch. |
| getLaunchUrl | Optional async function returning a fresh guest launch URL instead of launchEndpoint. |
| onNavigate | Receives {url,path,section}. Return false synchronously to handle navigation in your router. |
Match, odds and league clicks open the matching detail on your full sports page. The widget itself does not add a selection to a betslip. Height adjusts to content. Every widget needs its own guest launch.
The destination receives a validated sports_path query parameter. The
provided SDK and host script open this route. For a custom router, use
SportsbookAPI.routeFromLocation() and
sportsbook.navigateTo(path).
Bet history and design controls#
Open either modal on a page without the main sportsbook iframe.
const options = {
launchEndpoint: "/integration/launch",
csrfToken,
language: "en",
};
// Use each call in its own button handler.
SportsbookAPI.openBets(options);
SportsbookAPI.openDesign(options);
openBets requires member login and sends
action=player-bets-launch. openDesign sends
action=design-launch. Each opening obtains its own fresh launch.
Browser message contract#
For integrations using their own iframe and modal components.
Validate both event.origin and event.source. Host commands use
source:"sportsbook-host"; iframe events use
source:"sportsbook-iframe".
| Command → iframe | Payload |
|---|---|
| host-context | {url: location.href, viewportWidth: innerWidth} |
| navigate | {page: "home" | "sports" | "live" | "esports" | "results"} |
| navigate-route | {path: "/sports/live?sport=1&game=123"} |
| set-language | {language: "en"} |
| set-design | {settings: {...}} |
| refresh-balance | {} |
| relaunch-accepted | Acknowledge request-relaunch, then obtain a fresh launch. |
| Event ← iframe | Action |
|---|---|
| ready / request-host-context | Send host-context. |
| view | Update navigation with page and language. |
| balance | Display balance and currency; do not alter your wallet from a browser message. |
| coupon-status | Refresh the member view if needed. |
| bets-ready / design-ready / widget-ready | The standalone view is ready. |
| close-bets / close-design | Close your modal. |
| design-change | Pass settings to your main iframe if present. |
| open-match | Navigate to the supplied sports route on your full page. |
| widget-resize | Apply height in pixels. |
| widget-navigate | Navigate to your full sports page with path. |
| request-relaunch | The visitor pressed Refresh page. Reload the host page or request a fresh launch. |
Standalone views use view=player-bets, view=design or
view=home-widget in the launch URL query. Widgets also use
section=live|upcoming|popular-leagues|all. Keep the launch token in the URL
fragment.
Receive callbacks#
Server-to-server messages keep your wallet and bet records up to date.
| Header | Meaning |
|---|---|
| X-Site-Key | Your site identifier. |
| X-Callback-ID | Matches event_id. |
| X-Callback-Timestamp | UTC Unix seconds for this delivery attempt. |
| X-Coupon-Signature | sha256= + HMAC-SHA256(secret, timestamp + "." + raw_body) |
- Read the original body bytes and verify the signature and timestamp.
- Validate site_key, username and currency.
- For a wallet change, lock the wallet and check idempotency_key within one database transaction.
- Apply amount once, store the response and commit; then return JSON.
const valid = verifyCallback({
rawBody,
timestamp: req.headers['x-callback-timestamp'],
signature: req.headers['x-coupon-signature'],
siteSecret,
});
if (!valid) return res.writeHead(401).end();
The callback handler receives the raw body before a JSON parser. Signature verification
uses the sha256= prefix here; API request signing does not.
Event reference#
Only wallet events change a seamless wallet balance.
What does coupon.settled.1 mean?
coupon identifies a bet notification, settled means a
settlement update, and 1 is the revision number. It does not mean won or
lost. The same event name can describe a won, lost or refunded bet. A later correction
may arrive as coupon.settled.2.
| Event example | raw_payload.status | What to display | Wallet action |
|---|---|---|---|
| coupon.accepted | open | Accepted · awaiting results | No change; stake was already reserved. |
| coupon.settled.1 | won | Won | No change; apply the paired wallet.settlement.1 amount only. |
| coupon.settled.1 | lost | Lost | No second stake debit. |
| coupon.settled.1 | refunded | Refunded | Return is applied through wallet.settlement.1. |
| coupon.settled.2 | open | Reopened after a correction · awaiting results | Apply the signed correction in wallet.settlement.2. |
| coupon.cashout.1 | open | Partial cashout · remaining bet is open | Credit wallet.cashout.1 amount once. |
| coupon.cashout.1 | cashout | Full cashout | Credit wallet.cashout.1 amount once. |
Settlement and cashout share the revision counter. The next settlement after a cashout
may therefore end in .2 or a higher number. Do not infer a result from event suffixes,
sequence or amount. Use raw_payload.status; apply money only from the
signed wallet event.
10.00 stake at odds of 1.55
| Stage | Event | amount | Meaning |
|---|---|---|---|
| Placement | wallet.reserve | -10.00 | Deduct the stake once. |
| Acceptance | coupon.accepted | 0.00 | status=open, total_stake=10.00, possible_win=15.50. |
| If won | wallet.settlement.1 → coupon.settled.1 | +15.50 → 0.00 | Credit 15.50, then record status=won. |
| If lost | wallet.settlement.1 → coupon.settled.1 | 0.00 → 0.00 | No further debit; record status=lost. |
| If voided | wallet.settlement.1 → coupon.settled.1 | +10.00 → 0.00 | Return 10.00; record status=refunded. |
These are alternative outcomes for a single bet with no previous cashout or settlement. Wallet settlement amounts are payout differences; after a correction they may be negative.
| Event | amount | Meaning |
|---|---|---|
| wallet.balance | — | Read current balance; no coupon snapshot or balance movement. |
| wallet.reserve | − stake | Reserve the stake before placement. A failed reservation prevents placement. |
| coupon.accepted | 0.00 | The bet was accepted. status=open; stake is in raw_payload.total_stake. |
| wallet.refund | + amount | Return a reserved stake when placement fails definitively. |
| placement.unknown | 0.00 | Placement outcome is uncertain and requires confirmation. Do not refund automatically. |
| wallet.settlement.N | +/−/0 | Apply the difference between the new cumulative payout and the previously recorded payout. |
| coupon.settled.N | 0.00 | Store the updated result and revision. The status can be won, lost, refunded or open after a correction. |
| wallet.cashout.N | + amount | Credit this cashout payment. |
| coupon.cashout.N | 0.00 | Full cashout: status=cashout. Partial cashout: status=open. |
N is the shared settlement/cashout revision number. Not every internal
placement transition creates a callback. There is no separate
coupon.rejected callback; a rejected reservation is acknowledged directly,
and a definitive failure after reservation produces wallet.refund.
Bet and placement statuses#
Read raw_payload.status for the result; use status_label only for display.
| status | Display | Meaning |
|---|---|---|
| open | Awaiting result | Accepted bet; the remaining stake is active. |
| won | Won | At least one payable line won. A system bet can still have a net loss. |
| lost | Lost | Remaining bet lost. Earlier partial cashout payments can keep payout above zero. |
| refunded | Refunded / void | The remaining bet was refunded. |
| cashout | Cashed out | The full remaining position was closed by cashout. |
An accepted bet waiting for results uses open; it does not use a separate
pending status. Partial cashout also remains open.
Before acceptance
| status | Meaning |
|---|---|
| awaiting_wallet | Waiting for the reserve callback response. |
| queued | Waiting in the placement queue. |
| preparing | Selections, odds and limits are being validated. |
| processing | Placement is being processed. |
| failed | The placement failed. Check error_code and the corresponding refund event. |
| unknown | The placement outcome has not been confirmed. Do not place the same bet again automatically. |
These are placement states, not won/lost results. Callback snapshots show the state at the time of that event. A result correction can reopen or change a settled bet; always process the revision and wallet delta.
Coupon payload fields#
One event envelope and one raw_payload snapshot.
| Field | Meaning |
|---|---|
| schema_version | 2 |
| event_id | Unique event identifier. |
| idempotency_key | Deduplication key for this site and event. |
| bet_id | Groups all events belonging to a bet. |
| sequence | Ordering within one bet, not across the entire site. |
| occurred_at | UTC event time. |
| amount | Wallet delta of this event; accepted/settled notifications use zero. |
| raw_payload.total_stake | Original total stake. |
| raw_payload.possible_win | Potential payout at placement, not confirmed winnings. |
| raw_payload.payout | Cumulative payout, including cashout. Do not credit it again. |
| raw_payload.bet_type | single / combo / system |
| raw_payload.revision | Result/cashout revision. |
| raw_payload.remaining_ratio | Fraction of original stake still active. |
| raw_payload.total_odds | Combined decimal odds of the selected outcomes. |
| raw_payload.odds_format / odds_format_code / odds_format_label | Saved odds display format, numeric code and display label. |
| raw_payload.displayed_total_odds | Total odds formatted for display. |
| raw_payload.selection_count / cashout_count | Number of selections and completed cashouts. |
| raw_payload.client_reference | Reference associated with the original placement request. |
| raw_payload.settlement | Settlement source, selection results, note and revision; null before settlement. |
| raw_payload.settlement_locked | Whether the result is held by an administrative settlement lock. |
| raw_payload.error_details | Additional odds-change information for ODDS_CHANGED; otherwise null. |
| raw_payload.lines | Selection IDs, stake, odds and possible_win per line. |
| raw_payload.cashout_summary | Total paid, remaining stake and cashout history. |
| raw_payload.wallet_state | Wallet workflow state at snapshot time; separate from the bet result. |
| raw_payload.error_code / error_message | Failure information when applicable. |
| created_at / updated_at / settled_at | UTC timestamps; settled_at is null while open. |
Accepted bet · complete example
{
"event": "coupon.accepted",
"amount": "0.00",
"bet_id": "cpn_example",
"currency": "TRY",
"event_id": "evt_example_coupon_accepted",
"sequence": 10,
"site_key": "YOUR_SITE_KEY",
"username": "player_example",
"occurred_at": "2026-09-14T08:38:51.276Z",
"raw_payload": {
"lines": [
{
"odds": "2",
"stake": "10.00",
"possible_win": "20.00",
"selection_ids": [
"301"
]
}
],
"payout": "0.00",
"status": "open",
"bet_type": "single",
"revision": 0,
"created_at": "2026-09-14T08:38:51.200Z",
"error_code": null,
"selections": [
{
"line": null,
"scope": "sports",
"esports": false,
"is_live": false,
"visible": true,
"event_id": "101",
"sport_id": "1",
"start_at": "2026-09-15T08:38:50.000Z",
"away_team": "Away 1",
"home_team": "Home 1",
"league_id": "3",
"market_id": "201",
"country_id": "2",
"sport_name": "Football",
"is_finished": false,
"league_name": "Premier League",
"market_name": "Match Result",
"market_type": "P1XP2",
"sport_alias": "Soccer",
"country_name": "England",
"placed_score": null,
"selection_id": "301",
"current_score": null,
"displayed_odds": "2.00",
"effective_odds": "2",
"selection_name": "Home",
"full_time_score": null,
"score_updated_at": null,
"current_game_time": null,
"current_game_state": null
}
],
"settled_at": null,
"settlement": null,
"total_odds": "2.00",
"updated_at": "2026-09-14T08:38:51.270Z",
"odds_format": "decimal",
"total_stake": "10.00",
"possible_win": "20.00",
"status_label": "Open",
"wallet_state": "debited",
"cashout_count": 0,
"error_details": null,
"error_message": null,
"cashout_summary": {
"history": [],
"total_amount": "0.00",
"remaining_stake": "10.00",
"remaining_possible_win": "20.00"
},
"remaining_ratio": "1.000000000000",
"selection_count": 1,
"client_reference": "example-placement-reference",
"odds_format_code": 0,
"odds_format_label": "Decimal",
"settlement_locked": false,
"displayed_total_odds": "2.00"
},
"schema_version": 2,
"idempotency_key": "cpn_example:coupon.accepted"
}
Settled bet · complete example
{
"event": "coupon.settled.1",
"amount": "0.00",
"bet_id": "cpn_example",
"currency": "TRY",
"event_id": "evt_example_coupon_settled_1",
"sequence": 103,
"site_key": "YOUR_SITE_KEY",
"username": "player_example",
"occurred_at": "2026-09-14T08:38:51.292Z",
"raw_payload": {
"lines": [
{
"odds": "2",
"stake": "10.00",
"possible_win": "20.00",
"selection_ids": [
"301"
]
}
],
"payout": "20.00",
"status": "won",
"bet_type": "single",
"revision": 1,
"created_at": "2026-09-14T08:38:51.200Z",
"error_code": null,
"selections": [
{
"line": null,
"scope": "sports",
"esports": false,
"is_live": false,
"visible": true,
"event_id": "101",
"sport_id": "1",
"start_at": "2026-09-15T08:38:50.000Z",
"away_team": "Away 1",
"home_team": "Home 1",
"league_id": "3",
"market_id": "201",
"country_id": "2",
"sport_name": "Football",
"is_finished": false,
"league_name": "Premier League",
"market_name": "Match Result",
"market_type": "P1XP2",
"sport_alias": "Soccer",
"country_name": "England",
"placed_score": null,
"selection_id": "301",
"current_score": null,
"displayed_odds": "2.00",
"effective_odds": "2",
"selection_name": "Home",
"full_time_score": null,
"score_updated_at": null,
"current_game_time": null,
"current_game_state": null
}
],
"settled_at": "2026-09-14T08:38:51.283Z",
"settlement": {
"note": "",
"source": "provider",
"results": {
"301": 3
},
"revision": 1
},
"total_odds": "2.00",
"updated_at": "2026-09-14T08:38:51.283Z",
"odds_format": "decimal",
"total_stake": "10.00",
"possible_win": "20.00",
"status_label": "Won",
"wallet_state": "confirmed",
"cashout_count": 0,
"error_details": null,
"error_message": null,
"cashout_summary": {
"history": [],
"total_amount": "0.00",
"remaining_stake": "10.00",
"remaining_possible_win": "20.00"
},
"remaining_ratio": "1.000000000000",
"selection_count": 1,
"client_reference": "example-placement-reference",
"odds_format_code": 0,
"odds_format_label": "Decimal",
"settlement_locked": false,
"displayed_total_odds": "2.00"
},
"schema_version": 2,
"idempotency_key": "cpn_example:coupon.settled.1"
}
Match and selection details#
Each raw_payload.selections entry describes one selected outcome.
| Fields | Meaning |
|---|---|
| sport_id / sport_name / sport_alias | Sport identity and display name. |
| country_id / country_name | Competition region. |
| league_id / league_name | League identity and display name. |
| event_id / home_team / away_team | Match identity and teams. |
| market_id / market_name / market_type | Selected betting market. |
| selection_id / selection_name / line | Outcome identifier, label and handicap/total line when present. |
| effective_odds / displayed_odds | Accepted decimal price and formatted display price. |
| is_live / scope / start_at | Live/prematch context and UTC start time. |
| placed_score / current_score / full_time_score | Score snapshots when supplied. Missing scores may be null. |
| current_game_time / current_game_state / score_updated_at | Available game clock, period and last score update. |
Scores describe the match, not the settlement of an individual market. Use the settlement results and coupon status for betting outcomes.
| settlement.results[selection_id] | Settlement multiplier |
|---|---|
| 1 | Lost: 0 |
| 3 | Won: accepted selection odds |
| 2 / 4 | Stake returned: 1 |
The current settlement engine requires a supported result for every selection in the line. Do not derive a win/loss from an absent result.
Callback responses#
Acknowledge the recorded outcome of each request.
wallet.balance
{"ok":true,"balance":"100.00","currency":"TRY"}
Committed wallet or notification event
{"ok":true,"idempotency_key":"cpn_example:coupon.accepted","balance":"90.00","currency":"TRY"}
Insufficient funds for wallet.reserve
{"ok":false,"code":"INSUFFICIENT_BALANCE","idempotency_key":"cpn_example:wallet.reserve"}
Return HTTP 200 for a balance response. For durable events, return a successful 2xx
response with ok:true and the matching key after commit. A definitive
reserve rejection also includes the matching key. Network errors are not
insufficient-balance decisions.
Retries and ordering#
An event can be delivered more than once.
Store a unique (site_key,idempotency_key) record with the wallet update and
response in one transaction. An identical repeated body returns the stored response. If
the same key has a different body, return HTTP 409 without another balance change.
Verify the signature for every delivery.
Events are sent in sequence within the same bet. Different bets may arrive concurrently. The callback timeout is 10 seconds. Failed durable deliveries are retried with increasing delays, up to 20 attempts; unresolved deliveries require reconciliation. These retries are separate from a synchronous wallet.balance request.
Results and corrections#
Apply the wallet delta, then update the result record.
| Example: stake 10.00 at odds 2.00 | Event | amount | Status |
|---|---|---|---|
| 1 | wallet.reserve | -10.00 | awaiting_wallet |
| 2 | coupon.accepted | 0.00 | open |
| 3 | wallet.settlement.1 | +20.00 | won |
| 4 | coupon.settled.1 | 0.00 | won |
| If corrected to lost | wallet.settlement.2 | -20.00 | lost |
| Result notification | coupon.settled.2 | 0.00 | lost |
For a loss without prior payout, settlement amount is 0.00. For a full refund without prior cashout, the stake is returned. Corrections can increase or decrease a previous payout, or reopen the bet. Use the amount actually delivered rather than calculating from the status alone.
Full and partial cashout#
Cashout payments and remaining exposure are reported separately.
Apply wallet.cashout.N.amount once. Full cashout changes status to
cashout and remaining_ratio to zero. Partial cashout leaves status
open; remaining_ratio, remaining_stake and remaining_possible_win decrease.
Later settlement concerns the remaining portion.
payout includes cashout already paid. The following
coupon.cashout.N event has amount 0.00 and updates the bet record.
Currencies and amounts#
Money is a decimal string in both requests and responses.
| Currencies | Maximum fractional precision |
|---|---|
| TRY, USD, GBP, EUR, BRL, CAD, AUD, INR, BDT, MXN, ETB, IDR | 2 |
| KWD, TND, IQD | 3 |
| TRX | 8 |
These are precision limits, not a requirement to type that many digits.
10.50 TRX, 2.25 TRY and 3.00 KWD are valid amount
formats. Site stake and payout limits also apply.
Coupon events remove unnecessary trailing zeros and keep at least two decimal places.
Preserve meaningful fractions such as 1.001 and 0.00000001.
Use decimal arithmetic for balances and compare status codes rather than display labels.
Ledger transfers#
POST /api/integration/wallet
{
"username": "player_example",
"currency": "TRY",
"operation": "credit",
"amount": "100.00",
"idempotency_key": "transfer-example-0001"
}
credit adds funds to the sportsbook wallet; debit removes
them. This endpoint is for ledger sites. The member wallet must exist after iframe
bootstrap. Amount must be positive and match currency precision.
{"ok":true,"data":{"transaction_id":"txn_example","idempotency_key":"transfer-example-0001","balance":"100.00","currency":"TRY"}}
When a transfer times out, retry the same operation and financial key with a fresh signing nonce. Do not create a second transfer to compensate for an unknown result.
Site betting credit#
GET /api/integration/credit
{"ok":true,"data":{"site_key":"YOUR_SITE_KEY","credit":"1000.00","currency":"USD"}}
This returns your site’s betting credit, not a player balance. Sign the request with an empty body. The currency comes from your site configuration.
Error handling#
Use code for handling and request_id for support.
{"ok":false,"code":"SIGNATURE_INVALID","message":"Request signature is invalid.","request_id":"request_example"}
| code | Action |
|---|---|
| SIGNATURE_INVALID | Check the secret, canonical path and exact body bytes. |
| SIGNATURE_EXPIRED | Check the clock, timestamp and nonce format. |
| REPLAY | Generate a fresh signing nonce; keep the financial key. |
| ORIGIN_FORBIDDEN / IP_FORBIDDEN | Check authorized website origins and outbound server IPs. |
| CALLBACK_REQUIRED | Configure the HTTPS callback URL. |
| IDEMPOTENCY_CONFLICT | The key refers to a different operation. Reconcile before continuing. |
| INSUFFICIENT_BALANCE | The requested debit cannot be funded. |
| PLAYER_SESSION_REQUIRED / LAUNCH_EXPIRED / LAUNCH_USED | Request a fresh launch from the customer server. The Refresh page button renews through the integration page. |
A 503 or connection timeout does not prove that a financial operation failed. Preserve its reference and reconcile the result before retrying a different operation. Error message language may vary; code remains the integration key.
Downloads#
Node.js + PostgreSQL · application and API reference