Skip to content

Latest commit

 

History

History
311 lines (244 loc) · 10.8 KB

File metadata and controls

311 lines (244 loc) · 10.8 KB

<- Documentation - WebSocket Events

Event Message Structure

Note

This page documents the Gateway WebSocket (/subscribe on port 3100) used for chat, presence, and subscriptions. The SFU WebSocket (/signal on port 3300) is a separate connection used exclusively for voice/WebRTC signaling — see SFU Protocol for its message format.

All messages exchanged over the Gateway WebSocket share a single JSON envelope:

{
  "op": 0,
  "d":  { ... },
  "t":  100
}
Field Type Required Description
op int Operation code — determines how the message is routed
d object / json Payload data (structure varies by op + t)
t int Event type — only meaningful when op = 0 (Dispatch) or op = 7 (RTC). Omitted for control ops

Note: The server accepts both "d" and "data" as the payload key (for client convenience).

Note: Some Dispatch payloads are personalized per recipient. For example, message nonce is present only in the author's own live message event and is stripped from the same channel event delivered to other users.


OP Codes (Client → Server)

OP Name Description Payload
1 Hello First message after WS connect; authenticates the session See Hello
2 Heartbeat Keep-alive sent on the server-defined interval See Heartbeat
3 Presence Update Update own presence status See Presence Update
5 Channel Subscription Subscribe to channel and/or guild events See Channel Subscription
6 Presence Subscription Manage which users' presence you track See Presence Subscription
7 RTC WebRTC/voice signaling (send to SFU or keep-alive) See RTC

OP Codes (Server → Client)

OP Name Description
0 Dispatch Server-push event (message created, guild updated, etc.). Always includes t field
1 Hello Reply Response to Hello — contains heartbeat interval and session ID
3 Presence Update Dispatched presence snapshot for a subscribed user (includes voice state: mute/deafen)
7 RTC Voice/WebRTC events (offer, candidate, speaking, mute, kick, etc.)

Client → Server Payloads

OP 1 — Hello

Sent immediately after WebSocket upgrade. If not received within 5 seconds, the server closes the connection.

{
  "op": 1,
  "d": {
    "token": "eyJhbGci...",
    "heartbeat_session_id": "optional-previous-session-id"
  }
}
Field Type Required Description
token string JWT access token (from /auth/login or /auth/refresh)
heartbeat_session_id string Session ID from a previous connection to resume presence session

Server response (OP 1):

{
  "op": 1,
  "d": {
    "heartbeat_interval": 30000,
    "session_id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d"
  }
}
Field Type Description
heartbeat_interval int64 Heartbeat interval in milliseconds
session_id string UUID-style session ID; pass as heartbeat_session_id on reconnect

On success the server also:

  1. Fetches user profile and guild list in parallel.
  2. Subscribes the connection to the personal user.{userId} NATS topic.
  3. Subscribes to all user's guilds (guild.{guildId}).
  4. Starts the heartbeat timeout timer.

OP 2 — Heartbeat

Must be sent periodically per the heartbeat_interval from Hello. Missing heartbeats (with 10s grace) cause disconnection.

{
  "op": 2,
  "d": {
    "e": 42
  }
}
Field Type Description
e int64 Last event ID acknowledged by the client (monotonically increasing)

The server resets the heartbeat timer only if e >= lastEventId. On each heartbeat, the server also refreshes the session's presence TTL in Redis (throttled to at most once per 10 seconds).


OP 3 — Presence Update

Update own presence status. Presence is not auto-set on Hello — the client must explicitly send this op.

{
  "op": 3,
  "d": {
    "status": "online",
    "platform": "web",
    "custom_status_text": "Coding...",
    "voice_channel_id": 2230469276416868352,
    "mute": false,
    "deafen": false
  }
}
Field Type Required Description
status string "online", "idle", "dnd", or "offline" (invisible mode)
platform string "web", "mobile", "desktop" — informational only
custom_status_text string Free-text status message
voice_channel_id int64 Set to a channel ID to indicate voice presence; set to 0 to clear
mute bool Set to true/false to update mute status while in voice channel
deafen bool Set to true/false to update deafen status while in voice channel

Behavior:

  • "offline" sets a global override — the user appears offline to all watchers, even though the session is active.
  • Any other valid status clears the offline override and upserts the session.
  • The aggregated presence is published to NATS (presence.user.{userId}) for all presence subscribers.
  • When mute or deafen is provided and the user is in a voice channel, the voice state is updated and a Voice State Update event (t=209) is broadcast to all guild members.

Voice State Update Flow:

Client A → OP 3 (mute: true) → WS Service
                                   ↓
                              Update presence
                                   ↓
                         Broadcast t=209 to guild.{guildId}
                                   ↓
                    All guild members receive voice state update

See Event Types for the Voice State Update event details.


OP 5 — Channel Subscription

Subscribe to channel-specific events (typing, messages) and/or additional guild topics.

{
  "op": 5,
  "d": {
    "channels": [2226022078341972000, 2226022078341972001],
    "guilds": [2226022078304223200]
  }
}
Field Type Required Description
channels int64[] Exact channel ID list to subscribe to (guild channels, DMs, or group DMs)
channel int64 Legacy one-channel fallback; treated as channels: [channel] when channels is absent
guilds int64[] Additional guild IDs to subscribe to

Permission checks for channel subscriptions:

  1. Guild channel — must have PermServerViewChannels on that channel.
  2. DM channel — must be a participant.
  3. Group DM — must be a participant.

If none match, the subscription is silently rejected (logged server-side).

When channels is present, it replaces the connection's entire explicit channel-subscription set. Send channels: [] to clear all channel subscriptions.

For threads:

  • include the thread ID in channels if this connection should receive live thread message events
  • thread membership is managed separately through the REST thread-member routes and controls personal notifications / unread behavior

OP 6 — Presence Subscription

Manage which users' presence updates you receive in real-time.

{
  "op": 6,
  "d": {
    "add": [123456789, 987654321],
    "remove": [111111111],
    "set": [222222222, 333333333],
    "clear": false
  }
}
Field Type Description
add int64[] User IDs to start watching
remove int64[] User IDs to stop watching
set int64[] Replace the entire watch list with these IDs
clear bool If true, unsubscribe from all presence currently watched

Processing order: clearsetaddremove.

When a user ID is added (via add or set), the server immediately sends a presence snapshot for that user so the client has the current status without waiting for a change.


OP 7 — RTC (Gateway WS — limited)

Used for voice-related control over the Gateway WS connection. Only t=509 (RTCBindingAlive) is sent client → Gateway. The full RTC signaling (join, offer, answer, candidate, speaking, mute, etc.) happens on the separate SFU WebSocket (/signal on port 3300) — see SFU Protocol.

RTCBindingAlive (t=509) — Keep voice route alive:

{
  "op": 7,
  "t": 509,
  "d": {
    "channel": 2230469276416868352
  }
}

This refreshes voice:route:{channelId} TTL (60s) in Redis and updates the session's voice channel presence. Clients should send this periodically while in a voice channel.


Server → Client Payloads

OP 3 — Presence Update (Dispatch)

When you subscribe to a user's presence via OP 6, you receive their presence updates via OP 3 dispatches:

{
  "op": 3,
  "d": {
    "user_id": 2226021950625415200,
    "status": "online",
    "custom_status_text": "In a meeting",
    "since": 1700000000,
    "voice_channel_id": 2230469276416868352,
    "mute": true,
    "deafen": false,
    "client_status": {
      "web": "online"
    }
  }
}
Field Type Description
user_id int64 User whose presence changed
status string Current status: online, idle, dnd, or offline
custom_status_text string User's custom status message
since int64 Unix timestamp when this status was set
voice_channel_id int64 Voice channel ID if in voice (omitted if not in voice)
mute bool Whether the user is muted (only present if in voice channel)
deafen bool Whether the user is deafened (only present if in voice channel)
client_status map Per-platform status (e.g., web, desktop, mobile)

Voice State Fields:

  • mute: true — User has muted themselves (cannot speak)
  • deafen: true — User has deafened themselves (cannot hear others)
  • These fields are only included when the user is currently in a voice channel
  • They are updated via OP 3 (client → server) and then broadcast to all presence subscribers

OP 0 — Dispatch (Events)

Server-push events with t field indicating event type. See Event Types for complete list.

Example — Voice State Update (t=209):

{
  "op": 0,
  "t": 209,
  "d": {
    "guild_id": 2226022078304223200,
    "user_id": 2226021950625415200,
    "channel_id": 2230469276416868352,
    "mute": true,
    "deafen": false
  }
}

This event is broadcast to all guild members when any user's voice state (mute/deafen) changes.