Integrate the full IOP Global catalogue in under an hour — IOP Originals and every aggregated third-party vendor on your plan. It is one integration: a seamless wallet where you always own the player balance.
You call one endpoint on us (game launch); we call one endpoint on you (wallet settlement). The same webhook settles an Originals round and an aggregated slot round. All amounts everywhere are integer USD cents — 100 = $1.00, never floats.
i
In a hurry? The Quickstart has the four-step path, or feed llms.txt to your coding agent and let it implement the whole thing.
Base URL
All requests are made over HTTPS to your issued gateway. Requests over plain HTTP are rejected.
One apiKey, one webhook, two game families behind the same integration.
You (the operator) always own the player balance. IOP never holds player money.
You call one endpoint on IOP: game launch. IOP calls one endpoint on you: your wallet webhook (balance reads, per-round settlement, rollbacks).
IOP Originals use bare numeric gameIds like "2". Aggregated third-party games use namespaced ids like "nux:1000827" or "stakes:pragmatic/vs20bonzgold".
Aggregated games additionally require the rollback action (wallet contract v2). Beyond that, only the gameId prefix differs.
Everything you need is on the Integration → Config page of your backoffice.
Credential
Description
apiKey
Your server-side secret. Sent in the launch request body. Never expose it to a browser.
webhookUrl
The wallet endpoint you host and we call. Register and change it in Integration → Config.
!
At launch we immediately call your webhook with getBalance to sync the player's balance — your wallet endpoint must be live before the first launch or every bet will fail.
Pass the player's real ISO-3166 alpha-2 country — some studios are geo-restricted and answer 403 geo_restricted. Malformed codes answer 400; "UK" is auto-corrected to "GB".
!
Aggregated (nux:, stakes:) games are gated twice: the provider must be on your plan, and your webhook must implement rollback with the flag enabled in Config — otherwise real-money launches answer 409.
One HTTPS endpoint, three actions, a 5-second timeout. We POST JSON; you respond HTTP 200 with the shapes below.
Action
When
You must
getBalance
On every game launch
Return the player's current balance in cents.
processGameResult
On every settled round (each phase of multi-phase games)
Apply net = winAmount − betAmount atomically, then return the new balance.
rollback
When a round is voided or a win reversed
Reverse the movement (contract v2 — required for aggregated games).
i
The four rules that matter: (1) duplicate TransactionId → do not re-apply, answer with the current balance; (2) a net below zero → Insufficient funds and we cancel the round; (3) always return the balance after applying, in integer cents; (4) rollbacks are idempotent by TransactionId too.
Reference implementation (Node / Express)
const wallet = { balance: 250000 } // your balance store (integer USD cents)const seen = new Set(), rolledBack = new Set() // idempotency stores
app.post('/wallet/iop', (req, res) => {
const p = req.body
if (p.action === 'getBalance')
return res.json({ status: 'success', balance: wallet.balance })
if (p.action === 'processGameResult') {
if (seen.has(p.TransactionId)) // 1. idempotent replayreturn res.json({ status: 'success', balance: wallet.balance })
const net = Math.round(p.winAmount ?? 0) - Math.round(p.betAmount ?? 0)
if (wallet.balance + net < 0) // 2. never negativereturn res.json({ status: 'error', error: 'Insufficient funds' })
wallet.balance += net // 3. apply, then answer
seen.add(p.TransactionId)
return res.json({ status: 'success', balance: wallet.balance })
}
if (p.action === 'rollback') { // 4. reverse, idempotentif (rolledBack.has(p.TransactionId))
return res.json({ status: 'success', balance: wallet.balance })
if (p.rollbackOf === 'bet') wallet.balance += Math.round(p.betAmount ?? 0)
else wallet.balance = Math.max(0, wallet.balance - Math.round(p.winAmount ?? 0))
rolledBack.add(p.TransactionId)
return res.json({ status: 'success', balance: wallet.balance })
}
})
Settles one round. Multi-phase games send the stake first and the win later, sharing one event_id.
HOOKPOST {your_webhook_url}
{
"action": "processGameResult",
"userId": "player-123",
"betAmount": 100, // cents to debit (0 on win-only calls)"winAmount": 200, // cents to credit (0 on losses)"gameId": "2",
"event_id": "uuid", // groups all calls of one round"TransactionId": "uuid"// unique per call — idempotency key
}
{ "status": "success", "balance": 250100 } // balance AFTER applying
Apply net = winAmount − betAmount atomically. If the net would take the balance below zero, reject with the exact error shape — the round is then cancelled and reverted on our side.
Integer USD cents, everywhere. 100 = $1.00. No floats, no dollars.
event_id
Groups every settlement call of one round (multi-phase games settle the stake first, wins later).
TransactionId
Unique per settlement call — your idempotency key. We may retry; you must not double-apply.
Timeout
Answer within 5 seconds. Timeouts and errors fail safe: the round is cancelled and reverted.
token
The opaque session token from your launch call, echoed in every webhook call.
Multi-phase games (Keno, Wheel, Plinko, Mines) send the bet and win as separate processGameResult calls sharing one event_id: the bet call has winAmount: 0, the win call has betAmount: 0. Single-shot games (Dice, Limbo) may settle both in one call.
Prepaid GGR. Your positive GGR (bets − wins) consumes an allowance.
The GGR pill in your backoffice tracks usage; at 100% game launches suspend automatically. Top-ups are handled by your IOP manager — the allowance is credited within minutes and launches resume instantly once it covers usage. Usage is recomputed every 15 minutes and immediately after every allowance or config change; the month rolls over automatically and unused allowance carries into the new month.
The bet debit is awaited inside the player's spin — your wallet latency is player-facing UX.
Parameter
Value
Recommended response time
under 200ms from EU (the player is waiting mid-spin)
Hard timeout
5 seconds — after that the round fails safe (cancelled + reverted)
Retries
None on settled money calls — your idempotency must still hold
!
Wallets on serverless platforms with a remote database routinely add 300–500ms per call. Keep the wallet handler and its data store in the same region.
Settlement is always integer USD cents. If your site displays other currencies, convert at your own rate on your side — the wallet contract stays USD.
What happens if my wallet goes down?
Launches fail at the initial getBalance and in-flight rounds fail safe (cancelled + reverted). Players cannot bet while your wallet is unreachable — no money is lost, but no play happens either.
Can a player balance go negative?
No. You reject any net that would go below zero with the Insufficient funds error, and win-clawback rollbacks floor at zero.
How do I run several brands?
One apiKey per brand — each brand gets its own webhook URL, catalog scope, theming and GGR allowance.
Do demo launches move money?
No. Demo launches never call processGameResult. Vendor entitlements still apply to demo.
Why does a game appear in my catalog but fail to launch?
It should not — the catalog is filtered by your exact launch policy. If you see it, your catalog cache is stale (re-fetch /gw/catalog/games) or you are launching with a different apiKey than the catalog call used.