Docs / Protocols / WebSocket

WebSocket protocol

One WebSocket per client at /ws. All messages are JSON with a common envelope. Use this directly if you're writing a custom client; the SDKs wrap it.

Envelope

// Client → server
{"cid": "optional", "type": "message.type", "payload": {}}

// Server → client
{"cid": "echoed-if-request", "type": "message.type", "payload": {}}

The cid is optional. When present, the server echoes it back so the client can correlate request/response pairs.

Session

session.connect (client)

First message; authenticates the connection.

{"type": "session.connect", "payload": {"token": ""}}

session.connected (server)

Ack of session.connect.

{"type": "session.connected", "payload": {"player_id": "..."}}

session.heartbeat (client)

Keep-alive ping; send periodically.

{"type": "session.heartbeat", "payload": {}}

Matches

match.join (client)

Join a specific match (after matchmaking or invite).

{"type": "match.join", "payload": {"match_id": "..."}}

match.input (client)

Send an input to the match.

{"type": "match.input", "payload": {"action": "move", "x": 10, "y": 5}}

match.leave (client)

Leave the current match.

{"type": "match.leave", "payload": {}}

match.started (server)

Match has begun.

{"type": "match.started", "payload": {"match_id": "...", "players": [...]}}

match.state (server)

Broadcast state update (shape is game-specific, returned by your get_state callback).

{"type": "match.state", "payload": {"players": {...}, "tick": 42}}

match.finished (server)

Match ended with a result.

{"type": "match.finished", "payload": {"match_id": "...", "result": {...}}}

Matchmaking

matchmaker.add (client)

Submit a ticket.

{"type": "matchmaker.add",
 "payload": {"mode": "arena", "properties": {"skill": 1200}}}

matchmaker.remove (client)

Cancel a ticket.

{"type": "matchmaker.remove", "payload": {"ticket_id": "..."}}

matchmaker.matched (server)

A match was found.

{"type": "matchmaker.matched", "payload": {"match_id": "...", "players": [...]}}

Chat

chat.join / chat.leave (client)

Join/leave a channel.

{"type": "chat.join",  "payload": {"channel_id": "lobby"}}
{"type": "chat.leave", "payload": {"channel_id": "lobby"}}

chat.send (client)

Post a message.

{"type": "chat.send", "payload": {"channel_id": "lobby", "content": "Hello!"}}

chat.message (server)

A new message in a joined channel.

{"type": "chat.message",
 "payload": {
   "channel_id": "lobby",
   "sender_id": "...",
   "content": "Hello!",
   "sent_at": "2026-04-15T10:30:00Z"
 }}

Voting

vote.cast (client)

Cast a vote. For approval voting, option_id is a list.

{"type": "vote.cast",
 "payload": {"vote_id": "...", "option_id": "jungle"}}

// approval voting
{"type": "vote.cast",
 "payload": {"vote_id": "...", "option_id": ["jungle", "caves"]}}

vote.veto (client)

Use a veto token to cancel. Requires veto_tokens_per_player > 0 and veto_enabled on the vote.

{"type": "vote.veto", "payload": {"vote_id": "..."}}

match.vote_start (server)

A new vote has started.

{"type": "match.vote_start",
 "payload": {
   "vote_id": "...",
   "options": [{"id": "jungle", "label": "Jungle Path"}, {"id": "volcano", "label": "Volcano Path"}],
   "window_ms": 15000,
   "method": "plurality"
 }}

match.vote_tally (server)

Running tally update (only with visibility: live).

{"type": "match.vote_tally",
 "payload": {
   "vote_id": "...",
   "tallies": {"jungle": 2, "volcano": 1},
   "time_remaining_ms": 8432,
   "total_votes": 3
 }}

match.vote_result (server)

Vote closed, winner determined.

{"type": "match.vote_result",
 "payload": {
   "vote_id": "...",
   "winner": "jungle",
   "counts": {"jungle": 2, "volcano": 1},
   "distribution": {"jungle": 0.666, "volcano": 0.333},
   "total_votes": 3,
   "turnout": 1.0
 }}

match.vote_vetoed (server)

A player vetoed the vote.

{"type": "match.vote_vetoed", "payload": {"vote_id": "...", "vetoed_by": "player_id"}}

Presence & notifications

presence.update (client)

Update your online status.

{"type": "presence.update",
 "payload": {"status": "in_game", "metadata": {"match_id": "..."}}}

presence.changed (server)

A friend's presence changed.

{"type": "presence.changed", "payload": {"player_id": "...", "status": "online"}}

notification.new (server)

A new notification for the player.

{"type": "notification.new",
 "payload": {
   "id": "...",
   "type": "friend_request",
   "subject": "New friend request",
   "content": {"from_player_id": "..."}
 }}

Where next?

  • REST API — HTTP endpoints for things that don't fit a real-time channel.
  • Authentication — how to get the session token for session.connect.
  • Voting in depth — methods, tie-breakers, weighted, ranked.