Skip to content

WebSocket Protocol ​

Status: roadmap — current transport is HTTPS REST

This protocol is specified and partially implemented, but it is not what production games use today: all game actions, results, and state updates currently flow over HTTPS REST calls (see the API Reference). ws.slottylabs.com is not yet live. This page documents the planned protocol.

Slotty Labs plans WebSocket connections for real-time game communication. When enabled, game actions, results, and state updates will flow through the WebSocket.

Connection ​

wss://{host}/ws?token={jwt}&gameId={gameId}
ParameterDescription
hostws.slottylabs.com (environment derived from your API key)
tokenSession JWT (from SSO exchange)
gameIdTarget game ID

Connection Example ​

typescript
const ws = new WebSocket(
  `wss://ws.slottylabs.com/ws?token=${sessionToken}&gameId=slotty-slots`
);

ws.onopen = () => {
  console.log('Connected to game server');
};

ws.onmessage = (event) => {
  const message = JSON.parse(event.data);
  handleMessage(message);
};

ws.onclose = (event) => {
  console.log(`Disconnected: ${event.code} ${event.reason}`);
  if (!event.wasClean) {
    attemptReconnect();
  }
};

Client → Server Messages ​

action ​

Send a game action (bet, move, guess, etc.).

json
{
  "type": "action",
  "payload": {
    "action": "spin",
    "betPerLine": 1.00,
    "activePaylines": 20
  },
  "requestId": "req-001"
}

Game-specific action payloads:

json
{
  "type": "action",
  "payload": {
    "action": "spin",
    "betPerLine": 1.00,
    "activePaylines": 20
  },
  "requestId": "req-001"
}
json
{
  "type": "action",
  "payload": {
    "action": "hit"
  },
  "requestId": "req-002"
}
json
{
  "type": "action",
  "payload": {
    "action": "guess",
    "position": 3,
    "character": "K"
  },
  "requestId": "req-003"
}
json
{
  "type": "action",
  "payload": {
    "action": "move",
    "dx": 5.0,
    "dy": -3.0
  },
  "requestId": "req-004"
}
json
{
  "type": "action",
  "payload": {
    "action": "launch",
    "angle": 62.0,
    "power": 1800
  },
  "requestId": "req-005"
}

cashout ​

Cash out the current round (multi-step games only).

json
{
  "type": "cashout",
  "payload": {},
  "requestId": "req-010"
}

ping ​

Keep-alive ping. The server responds with pong.

json
{
  "type": "ping",
  "payload": {},
  "requestId": "req-099"
}

Server → Client Messages ​

action_result ​

Result of a game action.

json
{
  "type": "action_result",
  "payload": {
    "roundId": "round-abc123",
    "status": "completed",
    "outcome": {
      "reelPositions": [3, 7, 12, 1, 9],
      "paylines": [
        { "line": 5, "symbols": ["cherry", "cherry", "cherry"], "win": 4.0 }
      ],
      "totalWin": 4.0,
      "freeSpinsTriggered": false
    },
    "balance": {
      "before": 100.00,
      "after": 84.00,
      "currency": "USD"
    },
    "provablyFair": {
      "serverSeedHash": "a1b2c3d4...",
      "clientSeed": "f0e1d2c3...",
      "nonce": 42
    }
  },
  "requestId": "req-001",
  "timestamp": 1719849600000
}

cashout_result ​

Result of a cash-out request.

json
{
  "type": "cashout_result",
  "payload": {
    "roundId": "round-abc123",
    "status": "cashed_out",
    "multiplier": 3.7,
    "winAmount": 37.00,
    "balance": {
      "before": 90.00,
      "after": 127.00,
      "currency": "USD"
    }
  },
  "requestId": "req-010",
  "timestamp": 1719849600000
}

balance_update ​

Player balance changed (from another source, e.g., deposit).

json
{
  "type": "balance_update",
  "payload": {
    "balance": 500.00,
    "currency": "USD",
    "reason": "deposit",
    "amount": 100.00
  },
  "timestamp": 1719849600000
}

round_recovered ​

Sent on reconnect if the player has an active round.

json
{
  "type": "round_recovered",
  "payload": {
    "roundId": "round-abc123",
    "gameId": "safe-smash",
    "state": {
      "hitCount": 4,
      "accumulatedMultiplier": 3.7,
      "canCashOut": true
    },
    "balance": {
      "current": 90.00,
      "currency": "USD"
    }
  },
  "timestamp": 1719849600000
}

session_warning ​

Session time or reality check warning.

json
{
  "type": "session_warning",
  "payload": {
    "warningType": "reality_check",
    "sessionDuration": 3600,
    "totalWagered": 500.00,
    "netResult": -45.50,
    "nextAction": "acknowledge",
    "expiresIn": 60
  },
  "timestamp": 1719849600000
}

error ​

An error occurred processing a request.

json
{
  "type": "error",
  "payload": {
    "code": "30007",
    "message": "Insufficient balance",
    "details": {
      "required": 20.00,
      "available": 15.50
    }
  },
  "requestId": "req-001",
  "timestamp": 1719849600000
}

pong ​

Response to a ping.

json
{
  "type": "pong",
  "payload": {
    "serverTime": 1719849600000
  },
  "requestId": "req-099",
  "timestamp": 1719849600000
}

Reconnection Flow ​

If the WebSocket connection drops, follow this reconnection procedure:

  1. Detect disconnect — ws.onclose fires with wasClean: false
  2. Wait — Start with a 2-second delay
  3. Reconnect — Open a new WebSocket with the same session token
  4. Receive recovery — Server sends round_recovered if a round was in progress
  5. Resume play — Continue from the recovered state

Reconnection Parameters ​

SettingValue
Initial delay2 seconds
Max attempts5
BackoffNo backoff (fixed 2s intervals)
Session validityToken remains valid for its original TTL

Reconnection Example ​

typescript
class GameConnection {
  private ws: WebSocket | null = null;
  private reconnectAttempts = 0;
  private maxAttempts = 5;
  private reconnectDelay = 2000; // 2 seconds

  connect(token: string, gameId: string) {
    this.ws = new WebSocket(
      `wss://ws.slottylabs.com/ws?token=${token}&gameId=${gameId}`
    );

    this.ws.onopen = () => {
      this.reconnectAttempts = 0;
      console.log('Connected');
    };

    this.ws.onclose = (event) => {
      if (!event.wasClean && this.reconnectAttempts < this.maxAttempts) {
        this.reconnectAttempts++;
        console.log(`Reconnecting (attempt ${this.reconnectAttempts}/${this.maxAttempts})...`);
        setTimeout(() => this.connect(token, gameId), this.reconnectDelay);
      }
    };

    this.ws.onmessage = (event) => {
      const message = JSON.parse(event.data);

      if (message.type === 'round_recovered') {
        // Restore game state from recovered data
        this.handleRoundRecovery(message.payload);
      }
    };
  }

  private handleRoundRecovery(state: any) {
    console.log(`Recovered round ${state.roundId}`, state.state);
    // Update UI with recovered state
  }
}