Skip to content

WebSocket Protocol

Slotty Labs uses WebSocket connections for real-time game communication. All game actions, results, and state updates flow through the WebSocket.

Connection

wss://{host}/ws?token={jwt}&gameId={gameId}
ParameterDescription
hostws.slottylabs.com (production) or sandbox-ws.slottylabs.com (sandbox)
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 disconnectws.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
  }
}