Duvet DOCS
GitHub

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
}
FieldDescription
opOpcode (1 = dispatch)
d.typeEvent name (e.g., MESSAGE_CREATE)
d.dataEvent payload
sSequence 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>"
  }
}
FieldTypeDescription
tokenstringBot token or user access token
last_seqintegerLast received sequence number (for resume)
session_tokenstringPrevious session token (for resume)
shard_idintegerShard ID for this connection (bots only)
num_shardsintegerTotal 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_id and num_shards are required if either is provided.
  • shard_id must 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 the guild_ids assigned 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" }
CodeReason
4000Invalid shard options in identify (see Bot Sharding)
4003A message other than identify or a heartbeat before identifying
4004The 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
4005A 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