Asobi uses a single WebSocket connection per client at /ws. All messages
are JSON with a common envelope format.
{
"cid": "optional-correlation-id",
"type": "message.type",
"payload": {}
}{
"cid": "correlation-id-if-request",
"type": "message.type",
"payload": {}
}The cid field is optional. When provided, the server echoes it back in
the response so the client can correlate request/response pairs.
Authenticate the WebSocket connection. Must be the first message sent.
{"type": "session.connect", "payload": {"token": "session_token_here"}}Response:
{"type": "session.connected", "payload": {"player_id": "..."}}Keep-alive ping. Send periodically to prevent timeout.
{"type": "session.heartbeat", "payload": {}}Join a match (after being matched via matchmaker or direct invite).
{"type": "match.join", "payload": {"match_id": "..."}}Send game input to the match server.
{"type": "match.input", "payload": {"action": "move", "x": 10, "y": 5}}Server broadcasts game state updates to all players in the match.
{"type": "match.state", "payload": {"players": {...}, "tick": 42}}Notification that a match has begun.
{"type": "match.started", "payload": {"match_id": "...", "players": [...]}}Notification that a match has ended with results.
{"type": "match.finished", "payload": {"match_id": "...", "result": {...}}}Leave the current match.
{"type": "match.leave", "payload": {}}Submit a matchmaking ticket.
{"type": "matchmaker.add", "payload": {"mode": "arena", "properties": {"skill": 1200}}}Cancel a matchmaking ticket.
{"type": "matchmaker.remove", "payload": {"ticket_id": "..."}}Notification that a match was found.
{"type": "matchmaker.matched", "payload": {"match_id": "...", "players": [...]}}Join a chat channel.
{"type": "chat.join", "payload": {"channel_id": "lobby"}}Send a message to a channel.
{"type": "chat.send", "payload": {"channel_id": "lobby", "content": "Hello!"}}A new message in a joined channel.
{
"type": "chat.message",
"payload": {
"channel_id": "lobby",
"sender_id": "...",
"content": "Hello!",
"sent_at": "2025-01-15T10:30:00Z"
}
}Leave a chat channel.
{"type": "chat.leave", "payload": {"channel_id": "lobby"}}Cast a vote in an active match vote.
{"type": "vote.cast", "cid": "v1", "payload": {"vote_id": "...", "option_id": "jungle"}}For approval voting, option_id is a list:
{"type": "vote.cast", "payload": {"vote_id": "...", "option_id": ["jungle", "caves"]}}Use a veto token to cancel the current vote. Requires veto_tokens_per_player > 0
in match config and veto_enabled on the vote.
{"type": "vote.veto", "payload": {"vote_id": "..."}}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"
}
}Running tally update (only with "live" visibility).
{
"type": "match.vote_tally",
"payload": {
"vote_id": "...",
"tallies": {"jungle": 2, "volcano": 1},
"time_remaining_ms": 8432,
"total_votes": 3
}
}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
}
}A player vetoed the vote.
{"type": "match.vote_vetoed", "payload": {"vote_id": "...", "vetoed_by": "player_id"}}Update your online status.
{"type": "presence.update", "payload": {"status": "in_game", "metadata": {"match_id": "..."}}}A friend's presence changed.
{"type": "presence.changed", "payload": {"player_id": "...", "status": "online"}}A new notification for the player.
{
"type": "notification.new",
"payload": {
"id": "...",
"type": "friend_request",
"subject": "New friend request",
"content": {"from_player_id": "..."}
}
}