Skip to content

Protocol Reference

Signal Fish Server uses a JSON-based WebSocket protocol. All messages are JSON objects with a type field and optional data field.

MessagePack encoding is also supported for game data when enable_message_pack_game_data is enabled.

Client Messages

Authenticate

Authenticate with app credentials (required when auth is enabled). App ID is a public identifier that identifies the game application.

JSON
{
  "type": "Authenticate",
  "data": {
    "app_id": "my-game"
  }
}

Optional fields:

  • sdk_version - SDK version for debugging and analytics
  • platform - Platform information (e.g., "unity", "godot", "unreal")
  • game_data_format - Preferred game data encoding (defaults to JSON text frames)

JoinRoom

Join or create a room for a specific game. There is no separate room-creation message. JoinRoom behavior depends on room_code:

  1. Omit room_code: create a new room with a generated room code.
  2. Provide room_code and room exists for that game_name: join that room.
  3. Provide room_code and no room exists for that game_name: create a new room with that room code.
JSON
{
  "type": "JoinRoom",
  "data": {
    "game_name": "my-game",
    "player_name": "Player1"
  }
}

Required fields:

  • game_name - Name of the game
  • player_name - Name for the player

Optional fields:

  • room_code - Code used to join/create a room for this game_name
  • max_players - Maximum players (applied only when a new room is created)
  • supports_authority - Authority support (applied only when a new room is created)
  • relay_transport - Preferred relay transport protocol (TCP, UDP, or Auto)

GameData

Send arbitrary game data to other players in the room.

JSON
{
  "type": "GameData",
  "data": {
    "data": {
      "action": "move",
      "x": 100,
      "y": 200
    }
  }
}

The outer data is the serde content tag. The inner data is the variant field and can be any JSON-serializable object.

PlayerReady

Toggle your own ready state in the lobby. This message has no payload.

Behavior:

  1. First send in lobby state marks the player ready.
  2. Sending again in lobby state marks the player unready.
  3. The server broadcasts LobbyStateChanged after each toggle.
  4. When all players are ready, the server sends GameStarting.
JSON
{
  "type": "PlayerReady"
}

This message has no data payload.

If sent while not in a joinable lobby state, the server returns an Error with INVALID_ROOM_STATE.

AuthorityRequest

Request or release game authority.

JSON
{
  "type": "AuthorityRequest",
  "data": {
    "become_authority": true
  }
}

LeaveRoom

Leave the current room.

JSON
{
  "type": "LeaveRoom"
}

Ping

Heartbeat ping. Server responds with Pong.

JSON
{
  "type": "Ping"
}

Reconnect

Reconnect to a room after disconnection using authentication token.

JSON
{
  "type": "Reconnect",
  "data": {
    "player_id": "player-id",
    "room_id": "room-id",
    "auth_token": "token-string"
  }
}

The auth_token is generated by the server when a player disconnects and is provided to the client for reconnection purposes.

ProvideConnectionInfo

Provide connection info for P2P establishment.

JSON
{
  "type": "ProvideConnectionInfo",
  "data": {
    "connection_info": {
      "type": "direct",
      "host": "192.168.1.10",
      "port": 7777
    }
  }
}

JoinAsSpectator

Join a room as a spectator (read-only observer).

JSON
{
  "type": "JoinAsSpectator",
  "data": {
    "game_name": "my-game",
    "room_code": "ABC123",
    "spectator_name": "Observer1"
  }
}

Required fields:

  • game_name - Name of the game
  • room_code - Code of the room to spectate
  • spectator_name - Name for the spectator

LeaveSpectator

Leave spectator mode.

JSON
{
  "type": "LeaveSpectator"
}

This message has no data payload.

Server Messages

Authenticated

Authentication successful. Includes app information and rate limits.

JSON
{
  "type": "Authenticated",
  "data": {
    "app_name": "my-game",
    "organization": "My Organization",
    "rate_limits": {
      "per_minute": 60,
      "per_hour": 3600,
      "per_day": 86400
    }
  }
}

Optional fields:

  • organization - Organization name (if any)

ProtocolInfo

SDK/protocol compatibility details advertised after authentication.

JSON
{
  "type": "ProtocolInfo",
  "data": {
    "capabilities": ["reconnection", "spectators", "authority"],
    "game_data_formats": ["json", "message_pack"]
  }
}

AuthenticationError

Authentication failed.

JSON
{
  "type": "AuthenticationError",
  "data": {
    "error": "Invalid app_id",
    "error_code": "INVALID_APP_ID"
  }
}

RoomJoined

Successfully joined or created a room. This message is sent both when creating a new room and when joining an existing room. There is no separate room-created response type.

JSON
{
  "type": "RoomJoined",
  "data": {
    "room_id": "uuid-string",
    "room_code": "ABC123",
    "player_id": "your-player-id",
    "game_name": "my-game",
    "max_players": 8,
    "supports_authority": true,
    "current_players": [
      {
        "id": "player-id",
        "name": "Player 1",
        "is_authority": false,
        "is_ready": false,
        "connected_at": "2024-01-01T00:00:00Z"
      }
    ],
    "is_authority": false,
    "lobby_state": "waiting",
    "ready_players": [],
    "relay_type": "WebRTC",
    "current_spectators": []
  }
}

PlayerJoined

Another player joined the room.

JSON
{
  "type": "PlayerJoined",
  "data": {
    "player": {
      "id": "player-id",
      "name": "Player 2",
      "is_authority": false,
      "is_ready": false,
      "connected_at": "2024-01-01T00:00:00Z"
    }
  }
}

PlayerLeft

A player left the room.

JSON
{
  "type": "PlayerLeft",
  "data": {
    "player_id": "player-id"
  }
}

RoomJoinFailed

Failed to join room.

JSON
{
  "type": "RoomJoinFailed",
  "data": {
    "reason": "Room is full",
    "error_code": "ROOM_FULL"
  }
}

Note: The error_code field is optional.

RoomLeft

Successfully left room.

JSON
{
  "type": "RoomLeft"
}

This message has no data payload.

GameData

Game data relayed from another player.

JSON
{
  "type": "GameData",
  "data": {
    "from_player": "player-id",
    "data": {
      "action": "move",
      "x": 100,
      "y": 200
    }
  }
}

GameDataBinary

Binary game data payload from another player. Uses bytes for zero-copy cloning during broadcast.

JSON
{
  "type": "GameDataBinary",
  "data": {
    "from_player": "player-id",
    "encoding": "message_pack",
    "payload": "<base64-encoded-bytes>"
  }
}

LobbyStateChanged

Lobby state transitioned.

JSON
{
  "type": "LobbyStateChanged",
  "data": {
    "lobby_state": "finalized",
    "ready_players": ["player-id-1", "player-id-2"],
    "all_ready": true
  }
}

Possible states:

  • waiting - Waiting for players to join
  • lobby - Room is full, players coordinating readiness
  • finalized - All players ready, game starting

AuthorityChanged

Authority status changed in the room.

JSON
{
  "type": "AuthorityChanged",
  "data": {
    "authority_player": "player-id",
    "you_are_authority": false
  }
}

The authority_player field can be null if no player currently has authority.

AuthorityResponse

Authority request response.

JSON
{
  "type": "AuthorityResponse",
  "data": {
    "granted": true,
    "reason": "Authority granted"
  }
}

Note: The reason and error_code fields are optional.

GameStarting

Game is starting with peer connection information.

JSON
{
  "type": "GameStarting",
  "data": {
    "peer_connections": [
      {
        "player_id": "player-id-1",
        "player_name": "Player 1",
        "is_authority": false,
        "relay_type": "WebRTC"
      }
    ]
  }
}

Error

An error occurred.

JSON
{
  "type": "Error",
  "data": {
    "message": "Room is full",
    "error_code": "ROOM_FULL"
  }
}

Note: The error_code field is optional.

Common error codes:

  • ROOM_FULL - Room has reached max players
  • ROOM_NOT_FOUND - Room code does not exist
  • INVALID_GAME_NAME - Game name validation failed
  • RATE_LIMIT_EXCEEDED - Too many requests
  • AUTHENTICATION_REQUIRED - Authentication required
  • INVALID_APP_ID - Invalid app ID

Pong

Response to client Ping.

JSON
{
  "type": "Pong"
}

Reconnected

Reconnection successful. Includes current room state and missed events.

JSON
{
  "type": "Reconnected",
  "data": {
    "room_id": "uuid-string",
    "room_code": "ABC123",
    "player_id": "your-player-id",
    "game_name": "my-game",
    "max_players": 8,
    "supports_authority": true,
    "current_players": [
      {
        "id": "player-id",
        "name": "Player 1",
        "is_authority": false,
        "is_ready": false,
        "connected_at": "2024-01-01T00:00:00Z"
      }
    ],
    "is_authority": false,
    "lobby_state": "lobby",
    "ready_players": ["player-id-1"],
    "relay_type": "WebRTC",
    "current_spectators": [],
    "missed_events": [
      {
        "type": "GameData",
        "data": {
          "from_player": "player-id",
          "data": {"action": "move"}
        }
      }
    ]
  }
}

ReconnectionFailed

Reconnection failed.

JSON
{
  "type": "ReconnectionFailed",
  "data": {
    "reason": "The reconnection token is invalid or malformed.",
    "error_code": "RECONNECTION_TOKEN_INVALID"
  }
}

PlayerReconnected

Another player reconnected to the room.

JSON
{
  "type": "PlayerReconnected",
  "data": {
    "player_id": "player-id"
  }
}

SpectatorJoined

Successfully joined a room as spectator.

JSON
{
  "type": "SpectatorJoined",
  "data": {
    "room_id": "uuid-string",
    "room_code": "ABC123",
    "spectator_id": "your-spectator-id",
    "game_name": "my-game",
    "current_players": [
      {
        "id": "player-id",
        "name": "Player 1",
        "is_authority": false,
        "is_ready": false,
        "connected_at": "2024-01-01T00:00:00Z"
      }
    ],
    "current_spectators": [
      {
        "id": "spectator-id",
        "name": "Observer1",
        "connected_at": "2025-01-15T10:35:00Z"
      }
    ],
    "lobby_state": "lobby",
    "reason": "joined"
  }
}

Note: The reason field is optional.

SpectatorJoinFailed

Failed to join as spectator.

JSON
{
  "type": "SpectatorJoinFailed",
  "data": {
    "reason": "Room not found",
    "error_code": "ROOM_NOT_FOUND"
  }
}

Note: The error_code field is optional.

SpectatorLeft

Successfully left spectator mode.

JSON
{
  "type": "SpectatorLeft",
  "data": {
    "room_id": "uuid-string",
    "room_code": "ABC123",
    "reason": "voluntary_leave",
    "current_spectators": []
  }
}

Note: All fields are optional.

NewSpectatorJoined

Another spectator joined the room.

JSON
{
  "type": "NewSpectatorJoined",
  "data": {
    "spectator": {
      "id": "spectator-id",
      "name": "Observer2",
      "connected_at": "2025-01-15T10:36:00Z"
    },
    "current_spectators": [
      {
        "id": "spectator-id-1",
        "name": "Observer1",
        "connected_at": "2025-01-15T10:35:00Z"
      },
      {
        "id": "spectator-id-2",
        "name": "Observer2",
        "connected_at": "2025-01-15T10:36:00Z"
      }
    ],
    "reason": "joined"
  }
}

Note: The reason field is optional.

SpectatorDisconnected

Another spectator left the room.

JSON
{
  "type": "SpectatorDisconnected",
  "data": {
    "spectator_id": "spectator-id",
    "reason": "disconnected",
    "current_spectators": []
  }
}

Note: The reason field is optional.

Session Flow

Text Only
Client                              Server
  |                                    |
  |--- Authenticate ------------------>|
  |<-- Authenticated ------------------|
  |                                    |
  |--- JoinRoom (no room_code) ------->|
  |<-- RoomJoined ---------------------|
  |                                    |
  |         (other client joins)       |
  |<-- PlayerJoined -------------------|
  |                                    |
  |--- PlayerReady ------------------->|
  |<-- LobbyStateChanged (lobby) ------|
  |                                    |
  |--- GameData ---------------------->|
  |<-- GameData (from other player) ---|
  |                                    |
  |--- LeaveRoom --------------------->|
  |<-- RoomLeft -----------------------|

Reconnection Flow

When a client disconnects, the server generates a reconnection token bound to the player's ID and room. The client uses this token along with the player_id and room_id (from the original RoomJoined response) to reconnect:

JSON
{
  "type": "Reconnect",
  "data": {
    "player_id": "your-player-id",
    "room_id": "your-room-id",
    "auth_token": "stored-token"
  }
}

On successful reconnection, the server sends a Reconnected message with the current room state and any missed events that occurred during the disconnection.

Next Steps