Gateway Events
The Duvet gateway uses WebSocket connections to push real-time events to clients.
Connection
wss://ws.duvet.chat/gateway?token=<access_token> The gateway checks the token before upgrading the connection and answers an unknown one with 401. Right after a restart of its Redis, until the API has cached the tokens again (it checks every 10 seconds), it answers a token it can’t find with 503 and a Retry-After header instead: the token may be valid, so retry.
Payload Structure
All gateway events follow this structure:
{
"event": "payload",
"op": 1,
"d": {
"type": "EVENT_NAME",
"data": { ... }
},
"s": 10
} | Field | Description |
|---|---|
op | Opcode (1 = dispatch) |
d.type | Event name (e.g., MESSAGE_CREATE) |
d.data | Event payload |
s | Sequence number for resume support |
Identify
After connecting, send an identify message. The connection must identify before sending any other messages (except heartbeats). Connections that send non-identify messages before identifying are closed with code 4003.
{
"type": "identify",
"data": {
"token": "<access_token>"
}
} | Field | Type | Description |
|---|---|---|
token | string | Bot token or user access token |
last_seq | integer | Last received sequence number (for resume) |
session_token | string | Previous session token (for resume) |
shard_id | integer | Shard ID for this connection (bots only) |
num_shards | integer | Total number of shards (bots only) |
Bot Sharding
Bots can distribute spaces across multiple WebSocket connections (shards). Each shard handles a subset of spaces determined by fnv32a(space_uuid) % num_shards.
{
"type": "identify",
"data": {
"token": "<bot_token>",
"shard_id": 0,
"num_shards": 2
}
} - Only bots can shard. Users always receive all spaces on a single connection.
- Both
shard_idandnum_shardsare required if either is provided. shard_idmust be in range[0, num_shards). Maximum 64 shards.- Multiple connections can use the same
shard_id. Both connections coexist and receive events independently. - If a bot doesn’t specify shard info, it defaults to
shard_id: 0, num_shards: 1. - The READY event includes
"shard": [shard_id, num_shards]and only theguild_idsassigned to this shard. - Events are only sent to the shard connection that owns the relevant space.
- Bot status is per-space: if shard 0 disconnects, the bot appears offline only in shard 0’s spaces.
Resume
To resume a disconnected session, send an identify with last_seq and session_token:
{
"type": "identify",
"data": {
"token": "<access_token>",
"last_seq": 42,
"session_token": "<session_token>"
}
} A session can be resumed for 30 seconds after its connection drops, and only by the user it belongs to. The gateway replays the events after last_seq, including those sent while the client was away, before any new one, so each arrives once and in order. The session keeps its session_token, so you can resume with the same token again; no new READY is sent. The last 500 events are kept: when the ones after last_seq are gone, the gateway starts a new session and sends READY. TYPING, DM_TYPING, RECORDING and DM_RECORDING take no sequence number and are not buffered. Sharded bots can resume each shard independently.
READY
Sent once identify succeeds without resuming, before a SPACE_CREATE for each of the user’s spaces. Users get a GatewayReady payload, which includes ice_servers to use for voice; bots get a GatewayBotReady.
Disconnects
Before closing a connection on purpose, the gateway sends the reason, then a close frame, and closes the socket:
{ "event": "close-with-reason", "code": 4004, "reason": "authentication revoked" } | Code | Reason |
|---|---|
4000 | Invalid shard options in identify (see Bot Sharding) |
4003 | A message other than identify or a heartbeat before identifying |
4004 | The credentials aren’t valid: the token sent in identify is unknown or expired, or it stopped being valid (the bot was deleted or its token was reset, or the account’s password was reset), or the session ended (session ended: logged out, expired, or 30 days after the login, which the gateway checks once a minute; refreshing the session doesn’t end it). Don’t reconnect with the same token |
4005 | A second identify on a connection that already identified |
If the gateway can’t check the token in identify (an error on its side, or right after a Redis restart as described under Connection), it closes the connection without a reason; reconnect and identify again.
A client that stops reading falls behind; once 256 events are waiting for it, or a single write takes 10 seconds, the gateway drops the connection without a reason. Reconnect and resume to get the events it missed.
Event Categories
- Space Events — space updates, member joins/leaves, roles, emojis, stickers
- Room Events — room CRUD, overwrites, reordering
- Message Events — message CRUD, reactions, typing, mentions
- User Events — profile updates, status changes, activities
- Voice Events — voice state, WebRTC signaling
- Friend Events — friend requests, accepts, removes
- Conversation Events — DM/group conversation events
- Call Events — incoming, accepted, declined, ended