Integration Docs
Getting started

Introduction

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 cents100 = $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.

BASEhttps://api.iop.global
Getting started

How it works

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.
Getting started

Credentials

Everything you need is on the Integration → Config page of your backoffice.

CredentialDescription
apiKeyYour server-side secret. Sent in the launch request body. Never expose it to a browser.
webhookUrlThe 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.

Launch & catalog

Launch a game

A server-to-server call. The response contains game_url for iframe delivery and provably-fair seeds for native/SDK delivery — same call, both modes.

POST/gw/launch

The gameId prefix routes it: bare ids are Originals, nux: / stakes: are aggregated, stakes:sports:betby is the sportsbook.

Body parameters

FieldTypeDescription
apiKeyrequiredstringYour secret key. In the body, not a header.
gameIdrequiredstringGame identifier, e.g. "2" or "nux:1000827".
loginrequiredstringYour unique player id. Creates the player on first use.
currencystringDisplay currency; settlement is always USD cents.
countrystringISO-3166 alpha-2. Needed to satisfy per-studio geo rules.
tokenoptionalstringOpaque session token; echoed back in every webhook call.
curl -X POST https://api.iop.global/gw/launch \
  -H "Content-Type: application/json" \
  -d '{
    "apiKey": "YOUR_API_KEY",
    "gameId": "2",
    "login":  "player-123",
    "currency": "USD",
    "country":  "DE",
    "token":  "session-abc"
  }'
{
  "success": true,
  "launch_options": {
    "game_url": "https://play.iop.global/game/play?...",
    "producer": "ORIGINALS"
  },
  "game":  { "title": "Dice", "minBet": 10, "maxBet": 100000, "rtp": 95 },
  "seeds": { "clientSeed": "...", "hashedServerSeed": "...", "nonce": 0 }
}
i

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.

Launch & catalog

Deliver the game

Embed the returned URL full-viewport. The page is responsive and mobile-ready, and arrives skinned in your brand colours.

<iframe
  src="{game_url}"
  style="width:100%;height:100%;border:0"
  allow="autoplay"
></iframe>

Brand colours are set in Integration → Config → Originals Color Scheme — changes apply on the next launch, no cache to wait out.

Launch & catalog

Game catalog

Browse the games available to your plan. Third-party catalog calls send your apiKey in the x-api-key header.

GET/gw/catalog/providers
GET/gw/catalog/games?providerId=&search=&page=&country=

Returned gameIds are already namespaced (nux:1000827) — pass them to /gw/launch verbatim. The country filter omits studios restricted in that market.

IOP Originals

gameIdGameType
1PlinkoMulti-drop, two-phase settlement
2DiceSingle-shot
3KenoTwo-phase settlement
4LimboSingle-shot
5MinesStateful (reveal / cash-out)
6WheelTwo-phase settlement
nux:…Aggregated slots & liveThousands of titles
stakes:…Aggregated casinoPragmatic, Evolution, Hacksaw…
stakes:sports:betbySportsbook (BetBy)Bets/settlements arrive as wallet calls

All Originals: 95% RTP, min bet $0.10, max bet $100.00.

Seamless wallet

Wallet webhook overview

One HTTPS endpoint, three actions, a 5-second timeout. We POST JSON; you respond HTTP 200 with the shapes below.

ActionWhenYou must
getBalanceOn every game launchReturn the player's current balance in cents.
processGameResultOn every settled round (each phase of multi-phase games)Apply net = winAmount − betAmount atomically, then return the new balance.
rollbackWhen a round is voided or a win reversedReverse 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 replay
      return 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 negative
      return 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, idempotent
    if (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 })
  }
})
Seamless wallet

getBalance

We call this on every launch to sync the player's balance before any round.

HOOKPOST {your_webhook_url}
{
  "action": "getBalance",
  "userId": "player-123",
  "currency": "USD",
  "token": "session-abc"
}
{ "status": "success", "balance": 250000 }   // integer cents
Seamless wallet

processGameResult

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
{ "status": "error", "error": "Insufficient funds" }

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.

Seamless wallet

rollback

Contract v2. Reverses a previously settled movement — required before you can launch aggregated (nux:, stakes:) games.

HOOKPOST {your_webhook_url}
{
  "action": "rollback",
  "userId": "player-123",
  "rollbackOf": "bet",       // "bet" = refund stake | "win" = claw win back
  "betAmount": 100,
  "winAmount": 200,
  "event_id": "round-1",
  "TransactionId": "tx-9"
}
{ "status": "success", "balance": 250100 }   // balance AFTER the reversal
  • rollbackOf: "bet" → credit betAmount back to the player (a round was voided).
  • rollbackOf: "win" → debit winAmount (a mis-credited win reversed), floored at zero.
  • Idempotent by TransactionId — a retried rollback must not double-reverse.
Seamless wallet

Integration flow

The complete happy path for one session. The bet debit is settled before the result is shown — your wallet answer is authoritative.

your server                 IOP gateway                    your wallet webhook
     |                            |                              |
     |  POST /gw/launch           |                              |
     | --------------------------->|                              |
     |                            |   getBalance                 |
     |                            | ---------------------------->|
     |                            |<---- { success, balance } ---|
     |<-- { game_url, seeds } ----|                              |
     |                            |                              |
     |  <iframe src={game_url}>    |                              |
     |  player spins...           |                              |
     |                            |   processGameResult (bet)    |
     |                            | ---------------------------->|
     |                            |<---- { success, balance } ---|
     |                            |   processGameResult (win)    |
     |                            | ---------------------------->|
     |                            |<---- { success, balance } ---|
     |                            |   rollback (only if a round  |
     |                            | ---- must be voided) ------->|
Reference

Money & settlement rules

RuleDetail
UnitsInteger USD cents, everywhere. 100 = $1.00. No floats, no dollars.
event_idGroups every settlement call of one round (multi-phase games settle the stake first, wins later).
TransactionIdUnique per settlement call — your idempotency key. We may retry; you must not double-apply.
TimeoutAnswer within 5 seconds. Timeouts and errors fail safe: the round is cancelled and reverted.
tokenThe 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.

Reference

Billing & GGR limit

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.

Reference

Error reference

Launch API — POST /gw/launch

HTTP status + { "success": false, "error": "..." }.

HTTPErrorMeaning / fix
400gameId / login requiredMissing field — login is required for real-money launches.
400country must be alpha-2Send two-letter codes. Alpha-3 and full names are rejected.
401Invalid apiKeyWrong or regenerated key. Keys are shown once at creation.
403Client suspendedUsually the prepaid GGR limit ran out. Contact your manager.
403… not enabledThe vendor is not on your contract.
403game_disabledThe game/studio is excluded from your plan (also hidden from catalog).
403geo_restrictedThe studio is not licensed for the country you passed.
409rollback requiredAggregated games need wallet contract v2 — implement rollback, enable the flag.

Wallet webhook — we call you

Your responseWhat we do
{ status:"success", balance }Accepted. The balance you return is shown to the player.
{ status:"error", "Insufficient funds" }Bet rejected cleanly — nothing settles.
{ status:"error", ... } / non-200 / timeoutFail-safe: round cancelled and reverted. No auto-retry on settled money.
Duplicate TransactionIdDo NOT re-apply — answer success with the current balance.
Reference

Performance & timeouts

The bet debit is awaited inside the player's spin — your wallet latency is player-facing UX.

ParameterValue
Recommended response timeunder 200ms from EU (the player is waiting mid-spin)
Hard timeout5 seconds — after that the round fails safe (cancelled + reverted)
RetriesNone 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.

Reference

Security

ControlStatus
HTTPSRequired on your webhook URL — we refuse plain http in production.
apiKeyServer-side secret. Launch and catalog calls only ever from your backend.
Webhook URL secrecyTreat the URL as a credential — use an unguessable path.
IP allowlistingAvailable on request — we give you our egress IPs.
Request signing (HMAC)On the roadmap: an X-Signature header, opt-in before ever enforced.
Resources

Verification tests

Run these before going live and confirm both sides reconcile.

#ScenarioExpected
1Launch, then getBalance firesWallet answers the true balance in integer cents
2One winning and one losing roundBalance moves by exactly win − bet; ledgers agree
3Replay a settled TransactionIdNo double-apply; success + current balance
4Bet larger than the balanceExact error shape; round does not settle
5Rollback a settled bet, then repeatStake refunded once; replay does not double-refund
6Rollback a settled winWin debited back, floored at zero
7Respond in 6+ seconds onceRound cancelled + reverted; nothing settles
8Multi-phase: bet call then win callBoth share one event_id; two distinct TransactionIds
Resources

Go-live checklist

  • Wallet webhook deployed over HTTPS, responding in under 5 seconds
  • getBalance returns the real balance in integer cents
  • processGameResult applies net atomically and is idempotent by TransactionId
  • rollback implemented (bet refund + win clawback), idempotent by TransactionId
  • Insufficient funds rejected with the exact error shape
  • Launch calls made server-side only — apiKey never in browser code
  • game_url embedded and tested on desktop and mobile
  • Brand colours configured (Originals games)
  • Rollback flag enabled in Config before launching aggregated games
  • Test rounds reconciled: your wallet log matches the Finance → Transactions report
Resources

Glossary

TermMeaning
Seamless walletYou keep the only player balance; games settle against it through your webhook.
GGRGross Gaming Revenue = bets − wins. Positive GGR consumes your prepaid allowance.
Prepaid allowanceThe GGR budget your fee buys. At 100% usage, launches suspend until topped up.
Round / event_idOne game round. Multi-phase games settle it across several calls sharing the event_id.
TransactionIdOne money movement — your idempotency key in both directions.
Wallet contract v2Contract v1 + the rollback action. Required for aggregated games.
Provably-fair seedsclientSeed / hashedServerSeed / nonce returned at launch to verify Originals outcomes.
Resources

FAQ

Which currencies are supported?

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.

Resources

Changelog

DateChange
2026-08-13API Log filtering · per-studio geo restrictions (geo_restricted + optional country filter) · per-call wallet latency · GGR top-ups moved to your IOP manager (prepaid, manual crediting).
2026-08-09Wallet contract v2 (rollback action) · aggregated third-party catalog (nux:) via /gw/catalog/* · unified /gw/launch for all providers.
2026-08-06Initial release: launch API, wallet contract v1 (getBalance / processGameResult), prepaid GGR limit, brand theming, llms.txt.

Ready to integrate? Back to the Quickstart · Request access · feed llms.txt to your coding agent.