Last updated: November 2026
Operator Integration Guide
A simple, step-by-step guide for backend and frontend engineers who want to embed Galaxy Games into their platform.
You only have to build three things: (1) a server endpoint that asks Galaxy Games for a game link, (2) a page with an iframe that shows that link, and (3) a webhook endpoint that moves money in your player's wallet when they bet or win. Everything else is optional or already handled for you.
1The big picture
Galaxy Games is a crash-game platform. You connect to it with a small server-to-server API. Your backend asks for a game link, your frontend shows the link in an iframe, and Galaxy Games tells your wallet service about every bet and win.
- Create sessionYour backend calls Galaxy Games with your API key.
- Get game linkYou receive a signed
gameUrl. - Player playsBrowser shows the game in an iframe.
- Wallet updatesGalaxy Games sends signed webhooks to you.
Available games
| gameType value | Game name |
|---|---|
aviator | Aviator |
jetx | JetX |
chicken_road | Chicken Road |
pilot_chicken | Pilot Chicken |
fast_keno | Fast Keno |
2Getting started
A provider admin creates your operator account. You will receive the items below. Save them safely.
| Item | What it is |
|---|---|
| API Key | 64-character secret. Shown only once. Store it in a secrets manager. |
| API Key Prefix | First 8 characters. Safe to log. Used for support. |
| Admin username / password | Login for your operator admin panel. |
| Webhook Secret | Shared secret used to verify webhook signatures. |
| Operator UUID | Your operator ID, used in dashboard URLs and reports. |
Default operator settings (all editable later in the admin panel):
| Setting | Default |
|---|---|
| RTP target | 97 % |
| Min / Max bet | 1 / 10,000 |
| Max win per bet | 500,000 |
| Currency | ETB |
| Revenue share | 20 % |
| Webhook URL | Your endpoint for bet/win events |
Ask a provider admin to rotate it (POST /api/admin/operators/:id/rotate-key). A new key is shown once and the old one stops working immediately.
3Authentication
There are two kinds of credentials. Do not mix them up.
| Operator API key | Player session token (JWT) | |
|---|---|---|
| Used by | Your backend | The player's browser |
| Sent as | Header X-Api-Key | Inside the gameUrl after the # sign |
| Lifetime | Until rotated | 4 hours |
| Rule | Never expose in a browser or app | Never send to your backend or log it |
- The token sits in the URL fragment (after
#). Browsers never send fragments to servers, so it stays out of logs. - The game reads the token once and removes it from the address bar.
- The token contains:
playerId,operatorId,externalPlayerId,currency,isDemo,gameTypeand a one-timejti(for instant revocation).
4Launching a game
Step 1: Create a session (from your backend)
POST/api/v1/session/create
Headers: Content-Type: application/json and X-Api-Key: <your key>
{
"externalPlayerId": "player_123",
"username": "JohnD",
"currency": "ETB",
"isDemo": false,
"gameType": "aviator",
"launch": "game"
}| Field | Required | Meaning |
|---|---|---|
externalPlayerId | Yes | Your own user ID for this player (max 128 chars). |
username | No | Display name in the game (max 64). |
currency | No | Currency code. Default ETB. |
isDemo | No | true = play money, no webhooks. Default false. |
gameType | No | One of the available games. Default aviator. |
launch | No | "game" opens the game, "lobby" opens the game picker. |
{
"token": "eyJhbGciOiJIUzI1NiIs...",
"playerId": "a1b2c3d4-...",
"expiresIn": 14400,
"gameUrl": "https://games.example.com/aviator#token=eyJ..."
}playerIdis the Galaxy Games ID. Keep it: you need it for free bets and reports.- New players are created automatically. Existing players get their username updated if it changed.
Step 2: Show the game in an iframe
<iframe
src="https://games.example.com/aviator#token=eyJ..."
width="800" height="600" allow="fullscreen"
sandbox="allow-scripts allow-same-origin allow-popups allow-forms">
</iframe>Your website's origin must be added to ALLOWED_ORIGINS on the Galaxy Games server. Ask your provider to add it.
Step 3 (optional): Let the player switch games
From inside the lobby, the game calls POST /api/v1/session/switch with the player token as Authorization: Bearer ... and body { "gameType": "fast_keno" }. You get a new token and gameUrl back. This is automatic. Player and operator status are re-checked every time, so a suspended player cannot sneak back in.
When session creation is refused
| HTTP | Message | What to do |
|---|---|---|
| 403 | Player account is suspended | Show a friendly message. Do not retry. |
| 403 | Player is self-excluded (with until date) | Show the date. After it passes, a new session works again. |
5Wallet webhooks
For every real-money action, Galaxy Games sends a signed POST request to your webhook URL. Your server must update the player's wallet. Demo sessions never send webhooks.
| Event | When it happens | What you do |
|---|---|---|
bet | Player places a bet | Debit the bet amount |
win | Player cashes out (manual or auto) | Credit the winnings |
refund | Bet refunded (e.g. round cancelled) | Credit the amount back |
Example payload
{
"type": "bet",
"playerId": "a1b2c3d4-e5f6-...",
"betId": "b7c8d9e0-...",
"roundId": "f1a2b3c4-...",
"amount": 50.00,
"currency": "ETB",
"multiplier": 2.50, // win events only
"trigger": "manual", // win events only: "auto" or "manual"
"gameType": "aviator",
"isDemo": false,
"timestamp": "2024-06-01T12:34:56.789Z"
}The event table says to credit payout, but the example payload only shows amount and multiplier. In the example code, payout = amount × multiplier. Please confirm with your provider whether a payout field is included, and how free-bet wins should be treated, before you go live.
Verify every webhook
Each request carries X-Galaxy-Signature: sha256=<hex> and X-Galaxy-Event. (The older X-Aviator-* headers are also sent.) Compute an HMAC-SHA256 of the raw body with your webhook secret and compare it using a constant-time check. Reject anything that does not match.
- Read the body as raw bytes, not parsed JSON. The signature is calculated over the exact raw text.
Delivery rules
| Rule | Value |
|---|---|
| Attempts | Up to 3 (waits 0.5 s, then 1.5 s, then 4 s) |
| Timeout per attempt | 5 seconds |
| Success means | Any HTTP 2xx response |
| URL requirements | Public HTTPS. Private/internal IPs are blocked. |
| Idempotency | Use betId to skip events you already processed |
The same event can arrive more than once. Save each processed betId and ignore repeats, so a player is never debited or credited twice.
6Free bets
Free bets are promotional bets you give to players. Placing one does not debit the wallet and sends no webhook. If it wins, you receive a normal win webhook.
| Field | Meaning |
|---|---|
amount | Value of the free bet |
wageringRequirement | Player must wager amount × this (1–20x) before winnings are released |
maxWinCap | Optional limit on winnings |
expiresAt | When the free bet expires |
bonusType | manual, welcome, consecutive_loss, deposit_match, bulk, custom |
gameType | Game it can be used in |
Grant to one player
POST/api/admin/free-bets
{
"playerId": "<player UUID>",
"amount": 10.00,
"currency": "ETB",
"wageringRequirement": 1,
"expiresInHours": 72,
"bonusType": "welcome",
"gameType": "aviator"
}Grant in bulk
POST/api/admin/free-bets/bulk
| target | Who gets it | Available to |
|---|---|---|
self_all | All your active players | Operator admins |
single | Players in a playerIds list | Operator admins |
operator | All players of one operator (operatorId) | Provider admins only |
all_platform | Every active player | Provider admins only |
- Auto bonus: the platform can grant a
consecutive_lossfree bet automatically after N losses in a row. Set it up in the admin panel; no API call needed. - Safe by design: when a free bet is used, the server checks it belongs to the player, game and operator, matches the amount and is not expired or used. It is marked used and the bet saved in one atomic step.
7Reports & dashboard
All endpoints live under /api/admin/operator-dashboard/ and need admin login. Provider admins add ?operatorId=<uuid>.
| Endpoint | What you get |
|---|---|
/summary | KPIs: players, bets, wagered, payout, GGR, RTP, and what you owe |
/financial?range=30&gameType=aviator | Daily financials. range: 7, 30, 90 or custom (from/to) |
/revenue-trend | 30 daily points of wagered, payout, GGR |
/game-history?page=1&limit=50 | Paginated rounds for your players |
/game-history/:roundId/bets | All bets inside one round |
/player-history?search=john | Players with lifetime stats |
/top-players | Top 20 players by volume |
/big-wins?limit=20 | Largest payouts, good for risk monitoring |
/hourly-heatmap | 7×24 grid of activity over the last 30 days |
/webhook-status | Delivery rate, failures and recent errors |
Check /webhook-status regularly. A delivery rate below 100 % usually means your endpoint is slow or returning errors.
8Player safety
Each player is identified by your externalPlayerId. The same ID at two different operators is two separate players.
| Status | Effect |
|---|---|
active | Plays normally |
suspended | No new sessions; open connections are cut immediately |
self_excluded | Cannot play until the end date; reactivated automatically afterwards |
Status is checked at: session create, every WebSocket connection, every bet, and every game switch. Suspending a player disconnects them instantly. Suspending an operator disconnects all its players.
Responsible-gambling fields per player: dailyDepositLimit, sessionTimeLimitMins, selfExcludedUntil. Manage them in the admin panel under /admin/players/:id.
9Custom client (WebSocket)
The embedded game already handles WebSockets. Read on only if you are building your own game interface.
Connect with Socket.IO and pass the player token. Crash games (Aviator, JetX) use the root namespace. Others use /chicken-road, /pilot-chicken or /fast-keno.
import { io } from 'socket.io-client';
const socket = io(url, { auth: { token: playerJwt } });Client sends
| Event | Payload | Purpose |
|---|---|---|
client:seed | 8–64 hex chars | Adds your entropy to the provably fair seed |
bet:place | amount, slot (1–4), autoCashoutAt (1.01 or more), freeBetId | Place a bet |
bet:cashout | { betId } | Manual cash-out |
round:history | none | Last 50 crash results |
Server sends
| Event | Meaning |
|---|---|
round:sync / round:idle | State on connect / no round running |
round:betting_open | New round, betting window open (includes serverSeedHash) |
round:started | Betting closed, multiplier climbing |
multiplier:tick | Current multiplier, about every 100 ms |
round:crashed | Round over; server seed revealed for fairness checks |
round:live-bets | Snapshot of all live bets |
bet:accepted / bet:rejected | Bet confirmed / refused (with reason) |
cashout:confirmed / cashout:failed | Cash-out result |
player:cashout | Social feed: someone cashed out |
game:status | Game enabled or disabled |
player:restored | Account reactivated |
server:ping | Keepalive every 10 s; no reply needed |
error | Fatal; connection will close |
Rate limits (per connection): 30 bets per minute and 10 cash-outs per minute. Going over returns a "Too many requests" rejection.
10Server settings
These live in the Galaxy Games server .env file. They are managed by the provider, but good to know.
| Variable | What it does |
|---|---|
GAME_BASE_URL | Prefix for gameUrl in session responses |
ALLOWED_ORIGINS | Comma-separated list of sites allowed to embed the iframe |
JWT_SECRET | Signs player tokens. Never share. |
WEBHOOK_HMAC_SECRET | Webhook signing secret. Share securely with operators. |
WEBHOOK_ALLOW_PRIVATE | true only in development. Never in production. |
REST_RATE_LIMIT_PER_MIN | Public API limit per IP (default 120) |
WS_BET_RATE_LIMIT_PER_MIN | Bets per minute per connection (default 30) |
WS_CASHOUT_RATE_LIMIT_PER_MIN | Cash-outs per minute per connection (default 10) |
TRUST_PROXY | Trusted proxy hops for IP detection (default 1) |
11Go-live checklist
Tick every box before you launch. Your ticks are saved in this browser.
0 / 10 complete
12Code examples
Create a session (Node.js)
async function createGameSession(playerId, username, currency = 'ETB') {
const res = await fetch('https://games.example.com/api/v1/session/create', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Api-Key': process.env.GALAXY_API_KEY,
},
body: JSON.stringify({
externalPlayerId: playerId, username, currency,
isDemo: false, gameType: 'aviator', launch: 'game',
}),
});
if (!res.ok) {
const err = await res.json();
throw new Error(`Session create failed: ${err.error}`);
}
return res.json(); // { token, playerId, expiresIn, gameUrl }
}Receive webhooks (Node.js / Express)
const express = require('express');
const crypto = require('crypto');
const app = express();
app.post('/webhooks/galaxy', express.raw({ type: 'application/json' }),
async (req, res) => {
const signature = req.headers['x-galaxy-signature'] ?? '';
const expected = 'sha256=' + crypto
.createHmac('sha256', process.env.GALAXY_WEBHOOK_SECRET)
.update(req.body).digest('hex');
const a = Buffer.from(expected), b = Buffer.from(signature);
if (a.length !== b.length || !crypto.timingSafeEqual(a, b))
return res.status(401).json({ error: 'Invalid signature' });
const event = JSON.parse(req.body.toString());
if (await isAlreadyProcessed(event.betId)) return res.sendStatus(200);
switch (event.type) {
case 'bet':
await debitPlayerWallet(event.playerId, event.amount,
event.currency, event.betId);
break;
case 'win': // payout = amount x multiplier (confirm with provider)
await creditPlayerWallet(event.playerId,
event.amount * event.multiplier,
event.currency, event.betId);
break;
case 'refund':
await creditPlayerWallet(event.playerId, event.amount,
event.currency, event.betId);
break;
}
await markProcessed(event.betId);
res.sendStatus(200);
});A single bet can produce both a bet and a win event with the same betId. If you deduplicate on betId alone, store it together with the event type (for example betId + type) so the win is not skipped.
Embed the game (React)
function GameFrame({ playerId, username }) {
const [gameUrl, setGameUrl] = React.useState(null);
React.useEffect(() => {
fetch('/api/game-session', { // your own backend endpoint
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ playerId, username }),
}).then(r => r.json()).then(({ gameUrl }) => setGameUrl(gameUrl));
}, [playerId]);
if (!gameUrl) return <div>Loading game...</div>;
return (
<iframe src={gameUrl} title="Galaxy Games" width="100%" height="700"
allow="fullscreen"
sandbox="allow-scripts allow-same-origin allow-popups allow-forms"
style={{ border: 'none', borderRadius: 8 }} />
);
}13Error reference
Session errors
| HTTP | Error | Cause |
|---|---|---|
| 400 | Invalid request | Missing or malformed fields |
| 401 | Unauthorized | Wrong or missing API key |
| 403 | Player account is suspended | Player is suspended |
| 403 | Player is self-excluded | Active self-exclusion (includes until) |
| 500 | Internal server error | Retry after a short delay |
Bet rejection reasons (WebSocket)
| Reason | Cause |
|---|---|
| Betting window is closed | Round is flying or none is active |
| Bet must be between X and Y | Amount outside operator limits |
| Potential payout N exceeds max win M | Auto-cashout times bet is above max win |
| Slot N already has a bet this round | Slot already used |
| Free bet not available or expired | Free bet missing, used or expired |
| Free bet amount mismatch | Amount differs from free bet value |
| Game is currently disabled | Disabled by an admin |
| Your account has been suspended | Suspended during the session |
| Too many bet requests - slow down | Rate limit hit |
| Invalid bet data | Malformed payload |
| Internal server error | Temporary problem, retry |
If webhook deliveries keep failing
- Your endpoint takes longer than 5 seconds to respond.
- It returns a non-2xx status, including redirects (3xx). Use the final URL.
- There is an SSL certificate error on your endpoint.
- A firewall blocks Galaxy Games server IPs.
Failed transactions stay in the log. Contact your provider if problems continue after you fix your endpoint.