Integration Reference v1.0

Build with
Pooled.

Everything you need to integrate Pooled prize pools into your casino platform. JWT authentication, iFrame embedding, and webhook handling — live in half a day.

Quickstart

§ QS

Pooled uses Integration Mode only. Your server authenticates players via signed JWT tokens — we never handle player accounts, KYC, or funds. Here's how it works end to end:

1
Get your credentials
Log into your casino admin panel → Integration tab. Generate your JWT Secret and Webhook Secret. Store both securely in your environment variables. Never expose them client-side.
2
Implement JWT token generation (server-side)
When a player loads your lobby, your backend generates a signed JWT containing their identity and balance. See the JWT section for full details and code samples.
3
Embed the iFrame
Add one HTML snippet to your lobby page. Inject the server-generated token into the iFrame src URL. Players land directly in the game — no Pooled login required.
4
Set up your webhook endpoint
Create an HTTPS endpoint on your server to receive pool events. The pool.win event is critical — this is when you credit the winner's wallet.
5
Test end-to-end and go live
Use the demo tenant to verify your integration. See the Test Mode section for a complete testing guide.
Total integration time: Approximately half a working day for a backend developer familiar with JWT and webhooks.
🔑

JWT Token Auth

§ JWT

Your server generates a signed JWT for each authenticated player session. Pooled validates the signature and uses the token payload to identify the player and display their balance. Tokens must be generated server-side — never in the browser.

Security: Your JWT Secret must never appear in client-side code, public repos, or any environment accessible to third parties. Treat it like a database password. Rotate it immediately if compromised.
Token Structure
// JWT Header (set automatically by your library)
{
  "alg": "HS256",   // Required — must be HS256
  "typ": "JWT"
}

// JWT Payload (you supply these claims)
{
  "sub":      "player_abc123",   // Your internal player ID
  "name":     "Jordan S.",        // Display name (last name initial only)
  "balance":  250.00,             // Current wallet balance in USD
  "currency": "USD",              // Display currency
  "verified": true,              // KYC/age verification status
  "exp":      1716998400         // Unix timestamp — max 24hr from now
}

Code Samples

Node.js / Express
// npm install jsonwebtoken
const jwt = require('jsonwebtoken');

function getPooledToken(player) {
  return jwt.sign(
    {
      sub:      player.id,            // string — your internal player ID
      name:     player.displayName,   // e.g. "Jordan S."
      balance:  player.walletUSD,    // number — current balance in USD
      currency: player.currency,     // "USD" | "EUR" | "GBP" | "CAD" | "AUD" | "NZD"
      verified: player.kycPassed,   // boolean — KYC/18+ verification status
      exp:      Math.floor(Date.now() / 1000) + 3600,  // 1 hour
    },
    process.env.POOLED_JWT_SECRET,
    { algorithm: 'HS256' }
  );
}

// In your lobby route:
app.get('/lobby', (req, res) => {
  const player = req.user;   // your authenticated session
  const token  = getPooledToken(player);
  res.render('lobby', { pooledToken: token });
});
PHP
// composer require firebase/php-jwt
use Firebase\JWT\JWT;

function getPooledToken($player) {
    $payload = [
        'sub'      => $player->id,
        'name'     => $player->display_name,
        'balance'  => (float) $player->wallet_usd,
        'currency' => $player->currency,
        'verified' => (bool) $player->kyc_passed,
        'exp'      => time() + 3600,
    ];
    return JWT::encode(
        $payload,
        getenv('POOLED_JWT_SECRET'),
        'HS256'
    );
}

// In your lobby controller:
$token = getPooledToken($currentPlayer);
// Pass $token to your view template
Python
# pip install PyJWT
import jwt
import time
import os

def get_pooled_token(player):
    payload = {
        'sub':      str(player.id),
        'name':     player.display_name,
        'balance':  float(player.wallet_usd),
        'currency': player.currency,
        'verified': bool(player.kyc_passed),
        'exp':      int(time.time()) + 3600,
    }
    return jwt.encode(
        payload,
        os.environ['POOLED_JWT_SECRET'],
        algorithm='HS256'
    )

# In your lobby view:
token = get_pooled_token(request.user)
# Pass token to template context
📋

Token Reference

§ TR
Claim Type Required Description
sub string Required Your internal player ID. Must be unique and stable per player. This is the ID you will receive back in webhook events to identify the player.
name string Required Player display name shown in the game UI and leaderboard. Use last name initial only for privacy — e.g. "Jordan S."
balance number Required Player's current wallet balance in USD. Must be a number, not a string. Regenerate the token after wallet transactions to keep balance current.
currency string Required Display currency for prizes and balance. Supported: USD EUR GBP CAD AUD NZD
verified boolean Required Set to true if your platform has completed KYC and 18+ age verification for this player. Players with false cannot enter paid pools.
exp unix timestamp Required Token expiry time. Maximum 24 hours from issuance. Recommended: 1 hour (Math.floor(Date.now()/1000) + 3600). Expired tokens are rejected.
email string Optional Player email address. Only include if you want Pooled to send win notification emails to players. Not stored after session ends.
Important: Regenerate the token on every page load — do not cache it. A stale token will show an outdated balance. The token is valid until its exp timestamp or until the player reloads the page, whichever comes first.
🖼

iFrame Embed

§ IF

Embed Pooled in your casino lobby with a single HTML snippet. Players see your branding — nothing in the UI mentions Pooled. The token is injected server-side into the src URL.

HTML — Server-rendered (recommended)
<!-- Generate token server-side and inject into src -->
<iframe
  src="https://app.pooledgame.com/?tenant=YOUR_SLUG&token=SERVER_GENERATED_TOKEN"
  width="100%"
  height="800px"
  frameborder="0"
  allow="payment"
  title="Prize Pools"
/>
Your tenant slug is in your casino admin panel → Integration tab. It's the unique identifier for your casino (e.g. royal-casino).

Content Security Policy

If your platform sets CSP headers, add app.pooledgame.com to your frame-src directive:

HTTP Header
Content-Security-Policy: frame-src 'self' https://app.pooledgame.com;

Mobile / WebView

The game is fully responsive and works in iOS and Android WebViews. Use the same src URL. No additional configuration needed.

📡

Webhooks

§ WH

Pooled sends signed HTTP POST requests to your configured endpoint when pool events occur. The pool.win event is critical — this is when you must credit the winner's wallet. Your endpoint must be HTTPS and respond with HTTP 200 within 10 seconds.

Prize distribution is your responsibility. Pooled's obligation ends at successful webhook delivery (HTTP 200 response). You are solely responsible for crediting winners after receiving a pool.win event.

Signature Verification

Every webhook is signed with HMAC-SHA256 using your Webhook Secret. Always verify the X-Pooled-Signature header before processing any event.

Node.js / Express
const crypto = require('crypto');

// IMPORTANT: Use express.raw() — not express.json() — for this route
app.post('/pooled/webhook', express.raw({ type: 'application/json' }), (req, res) => {

  // Step 1 — Verify signature
  const sig      = req.headers['x-pooled-signature'];
  const expected = crypto
    .createHmac('sha256', process.env.POOLED_WEBHOOK_SECRET)
    .update(req.body)
    .digest('hex');

  if (sig !== expected) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  // Step 2 — Parse and handle
  const event = JSON.parse(req.body);

  switch (event.event) {
    case 'pool.win':
      // Credit winner — winner_id matches the player's JWT sub claim
      walletService.credit(event.winner_id, event.amount);
      break;
    case 'pool.refund':
      // Pool expired — refund the entry fee
      walletService.refund(event.user_id, event.refund_amount);
      break;
    case 'pool.entry':
      // Optional — log entry in your system
      break;
  }

  // Must return 200 — Pooled logs non-200 as failed delivery
  res.status(200).json({ received: true });
});
PHP
$body     = file_get_contents('php://input');
$sig      = $_SERVER['HTTP_X_POOLED_SIGNATURE'] ?? '';
$secret   = getenv('POOLED_WEBHOOK_SECRET');
$expected = hash_hmac('sha256', $body, $secret);

if (!hash_equals($expected, $sig)) {
    http_response_code(401);
    exit('Invalid signature');
}

$event = json_decode($body, true);

if ($event['event'] === 'pool.win') {
    creditWallet($event['winner_id'], $event['amount']);
}

if ($event['event'] === 'pool.refund') {
    refundWallet($event['user_id'], $event['refund_amount']);
}

http_response_code(200);
echo json_encode(['received' => true]);
Python / Flask
import hmac, hashlib, os, json
from flask import request, jsonify, abort

@app.route('/pooled/webhook', methods=['POST'])
def pooled_webhook():
    body     = request.get_data()
    sig      = request.headers.get('X-Pooled-Signature', '')
    secret   = os.environ['POOLED_WEBHOOK_SECRET'].encode()
    expected = hmac.new(secret, body, hashlib.sha256).hexdigest()

    if not hmac.compare_digest(expected, sig):
        abort(401)

    event = json.loads(body)

    if event['event'] == 'pool.win':
        credit_wallet(event['winner_id'], event['amount'])

    if event['event'] == 'pool.refund':
        refund_wallet(event['user_id'], event['refund_amount'])

    return jsonify({ 'received': True }), 200

Webhook Events

§ EV
pool.win
A pool completes and a winner is drawn. Fires once per pool.
⚠ Action Required — Credit winner
pool.refund
A pool expires without filling. Fires once per player who entered.
⚠ Action Required — Refund player
pool.entry
A player successfully enters a pool. Fires on every entry.
Optional — Log entry
pool.filled
A pool reaches 100% capacity. Draw is imminent.
Optional — Notify players
📦

Event Payloads

§ PL

pool.win

JSON Payload
{
  "event":       "pool.win",
  "tenant_id":   "uuid",
  "timestamp":   "2026-05-29T14:32:00Z",
  "pool_id":     "uuid",
  "pool_size":   1000,              // Prize in USD: 100 | 1000 | 10000 | 100000 | 1000000
  "winner_id":   "player_abc123",  // Matches the player's JWT sub claim
  "winner_name": "Jordan S.",
  "amount":      1000,              // Amount to credit in USD
  "seed":        "a3f8c2d1...",     // Revealed random seed
  "seed_commit": "sha256hash...",  // Verify: hash(seed) == seed_commit
  "entry_count": 1000
}

pool.refund

JSON Payload
{
  "event":         "pool.refund",
  "tenant_id":     "uuid",
  "timestamp":     "2026-05-29T14:32:00Z",
  "pool_id":       "uuid",
  "pool_size":     1000,
  "user_id":       "player_abc123",   // Player's JWT sub claim
  "refund_amount": 1.00,              // Always $1.00 per paid entry
  "entry_type":    "paid"             // "paid" | "bundle"
}

pool.entry

JSON Payload
{
  "event":        "pool.entry",
  "tenant_id":    "uuid",
  "timestamp":    "2026-05-29T14:32:00Z",
  "pool_id":      "uuid",
  "pool_size":    1000,
  "user_id":      "player_abc123",
  "entry_type":   "paid",            // "paid" | "bundle" | "credit"
  "tx_id":        "uuid",
  "slots_filled": 347,
  "slots_total":  1000
}
🧪

Test Mode

§ TM

Use the demo tenant to verify your integration before going live. Demo mode uses fast-filling pools (seconds not minutes) and fake balances — no real money involved.

Demo URLs
// Player game view (demo)
https://app.pooledgame.com/?role=game&tenant=demo&demo=true

// Test your JWT token with the demo tenant
https://app.pooledgame.com/?tenant=demo&token=YOUR_TEST_JWT

// Casino admin (to view demo webhook delivery log)
https://app.pooledgame.com/?role=casino&tenant=demo
Webhook testing: Configure your staging webhook URL in your casino admin panel → Integration → Webhooks. All events fire in demo mode exactly as they do in production.

End-to-End Test Checklist

TestExpected Result
Token validates at jwt.ioAll 6 claims present, correct types, exp in future
balance is a number not a stringjwt.io shows 250 not "250"
iFrame loads with valid tokenGame UI appears with your casino branding
Balance shown matches tokenExact match to balance claim
Player can enter a poolEntry recorded, balance deducts $1.50
pool.win webhook firesDelivery log shows 200 OK
winner_id matches player subExact string match
Your wallet credits on winWinner balance increases by prize amount
pool.refund fires on expiryDelivery log shows event, your wallet refunds
Invalid token is rejectedPlayer sees error, cannot enter game

Error Reference

§ ER
ErrorCauseFix
White screen / blank iFrameInvalid or expired JWT tokenRegenerate token server-side. Check exp claim is in the future.
Token validation failedJWT secret mismatchVerify POOLED_JWT_SECRET matches exactly what's in your admin panel. No trailing whitespace or newlines.
Wrong balance displayedToken cached or balance sent as stringRegenerate token on every page load. Ensure balance is a number, not "250.00".
Player can't enter poolverified is false or balance too lowSet verified: true after KYC. Balance must be ≥ $1.50.
Webhooks not arrivingEndpoint not HTTPS or not publicly reachableEnsure your endpoint is HTTPS, publicly accessible, not localhost. Check firewall rules.
Signature verification failingWrong secret or body pre-parsedUse POOLED_WEBHOOK_SECRET (not JWT secret). Use express.raw() not express.json() on the webhook route.
iFrame blocked by browserContent Security Policy violationAdd app.pooledgame.com to your frame-src CSP header.
Webhook delivery timeoutEndpoint takes > 10 seconds to respondReturn 200 immediately, then process the event asynchronously (queue it).

Go-Live Checklist

§ GL

Confirm every item before switching from test to production traffic.

ItemNotes
✓ SLA signedRequired before any production traffic
✓ JWT Secret stored in env varNever hardcoded or in client-side code
✓ Webhook Secret stored in env varSeparate from JWT secret
✓ Production webhook URL configuredIn admin panel → Integration → Webhooks
✓ Webhook endpoint responds within 10sReturn 200 immediately, process async if needed
✓ Wallet credits on pool.win verifiedTested end-to-end in demo mode
✓ Refund logic testedpool.refund event handled correctly
✓ Token generated fresh on every page loadNot cached — balance must be current
✓ CSP headers updatedapp.pooledgame.com in frame-src
✓ Mobile display verifiedTested on actual mobile device
Need help? Email us at hello@pooledgame.com — we're available to jump on a call during your integration. Most issues are resolved in under 30 minutes.