Gateway overview
The main Gateway is a persistent WebSocket. It sends account and guild events and accepts a small set of bounded client commands. Every other read or mutation is an HTTP API operation.
Connecting
Section titled “Connecting”A client opens the socket, waits for Hello, sends Identify, and then heartbeats for the life of the connection.
- Read
endpoints.gatewayfrom the instance discovery document and open a WebSocket to it with?v=1&encoding=json. - Read Opcode 10 Hello and take
heartbeat_intervalfrom it. - Send Opcode 2 Identify with the token and
properties. - Read Ready and keep its
session_idfor a later Resume. - Send Opcode 1 Heartbeat with the last Dispatch sequence every
heartbeat_intervalmilliseconds.
{ "op": 2, "d": { "token": "flx_ZDb1GURItsMuYl1zvrgxv2qLBxyNmgNSEaWT", "properties": { "os": "Linux", "browser": "FiveCord Client", "device": "desktop" } }}Only token and properties are required. Send the raw user or bot token without an HTTP authentication prefix. See Identify for optional fields. Account and guild updates arrive as Dispatches, with the event name in t and its data in d.
Protocol version
Section titled “Protocol version”Gateway version 1 is the only version. The v=1 connection parameter selects it. The Gateway closes with 4012 and reason Invalid API version for any other value and for an omitted one, before it sends Hello.
Discovering the endpoint
Section titled “Discovering the endpoint”Read endpoints.gateway from the instance discovery document. A bot can instead call GET /v1/gateway/bot, which returns the same WebSocket URL together with a recommended shard count and a session start limit object. That route rejects any credential outside bot token form with 401 INVALID_AUTH_TOKEN.
Connection parameters
Section titled “Connection parameters”Append the connection parameters to the discovered URL.
| Field | Type | Description |
|---|---|---|
| v | integer | The Gateway protocol version, which is 1 |
| encoding?1 | string | The payload encoding |
| compress?2 | string | The compression stream, which accepts zstd-stream or none and defaults to no compression |
| stream?3 | string | The flag that enables the compression stream, which accepts 1 or true |
1 Only json is implemented. Every value, including an absent one, selects JSON
2 Any other value selects no compression
3 compress=zstd-stream selects compression only when stream is also 1 or true. Without the flag the connection is uncompressed
Unknown parameters are ignored. An unrecognised compress or stream value selects no compression and the connection stays open. A client MUST read whether the connection is compressed from the frame type it receives. On a zstd-stream connection every server frame is a binary frame.
The reference client connects with ?v=1&encoding=json&compress=zstd-stream&stream=1.
Gateway payload
Section titled “Gateway payload”Every logical message uses the Gateway payload.
| Field | Type | Description |
|---|---|---|
| op | integer | Gateway opcode |
| d?1 | any | The opcode payload, required in every client-to-server payload and opcode-dependent in server-to-client payloads |
| s? | integer | The non-negative session sequence, present only on Dispatch |
| t? | string | The event name, present only on Dispatch |
1 Heartbeat ACK and Reconnect have no d at all, and Invalid Session has the Boolean false
A decoded payload that is not a JSON object closes with 4002 and reason Decode failed. An object with no op closes with 4002 and reason Invalid payload. An object with op but no d closes with 4001 and reason Unknown opcode, except for Identify and Resume, which close with 4005.
A Dispatch is a server-to-client event payload. Every live Dispatch advances the session sequence by one. A session starts at sequence 0, so the Ready sequence is 1.
A replayed Dispatch keeps its original sequence, and a replayed run can have gaps, because Guild Sync, Guild Member List Update, and Guild Members Chunk are delivered live and never retained. Resumed has the current sequence and does not advance it. The next live Dispatch after Resumed has that sequence plus one. The sequence is local to one Gateway session and has no meaning across sessions or shards.
Framing
Section titled “Framing”One inbound WebSocket message is limited to 4,096 bytes on the wire, and a compressed inbound message is limited to a further 4,096 bytes after decompression. A message past either bound closes with 4002 and reason Payload too large. An inbound message that cannot be decompressed closes with 4002 and reason Decompression failed.
JSON payloads are UTF-8 objects. An uncompressed client payload is sent in a text frame, and a payload the client compressed with the negotiated zstd stream is sent in a binary frame.
The Gateway does not inspect the inbound frame type. It decodes every inbound message with the compression the connection negotiated, whatever the frame type. A client MUST send every payload in the negotiated representation. On a connection with no negotiated compression the payload is uncompressed, and on a zstd-stream connection every client payload goes through the same compression stream in order.
JSON integer representation
Section titled “JSON integer representation”Snowflakes are decimal strings. See Snowflakes for the identifier contract. Sequences, counts, versions, and bitfields are JSON numbers.
Compression
Section titled “Compression”zstd-stream is a continuous stream in both directions. A client MUST feed every server frame to the same decompressor in arrival order and produce every client frame from the same compressor.
One WebSocket message has exactly one Gateway payload.
Hello is already compressed on a connection that negotiated zstd-stream, so the first frame such a connection receives is a binary frame.
When the server cannot load the zstd streaming implementation, the connection closes with 4002 and reason Compression failed: zstd-stream as it tries to send that first frame.
Signalling state machine
Section titled “Signalling state machine”A connection moves through these states: Opening, Unauthenticated, Starting, Replaying, and Ready. The tables below give every event a state accepts, the action it triggers, and the state it lands in. Heartbeat is accepted in every open state, and Closed is terminal for that WebSocket.
Opening
Section titled “Opening”| Event and condition | Action | Next state |
|---|---|---|
WebSocket accepted. v=1 and connection capacity available | Send Hello | Unauthenticated |
Invalid version. v is absent or is not 1 | Close with 4012 and reason Invalid API version | Closed |
| Concurrent connection limit reached. The source IP already holds 256 connections | Close with 4008 and reason Too many connections | Closed |
Unauthenticated
Section titled “Unauthenticated”| Event and condition | Action | Next state |
|---|---|---|
| Identify. Valid Identify payload and Identify capacity available | Begin session creation | Starting |
| Identify. Gateway draining, node at capacity, session starts paused, or the account outside the session rollout | Hold the payload and retry it in the background | Unauthenticated |
| Identify. The source IP Identify budget is exhausted | Discard the payload without a reply | Unauthenticated |
| Resume. Valid Resume payload | Look up the retained session named by session_id | Starting |
| Authenticated command. Any command other than Heartbeat, Identify, or Resume | Close with 4003 and reason Not authenticated | Closed |
Starting
Section titled “Starting”| Event and condition | Action | Next state |
|---|---|---|
| Session creation succeeds. Identify was accepted | Send Ready | Ready |
| Session creation fails permanently. Invalid token, invalid shard, sharding required, or too many sessions | Close with the code and reason that Hello and session creation lists for that failure | Closed |
| Session creation fails with an error the Gateway does not classify | Close with 4000 and reason Failed to start session | Closed |
| Session creation fails temporarily. Draining, at capacity, RPC failure, timeout, or the account outside the session rollout | Hold the Identify and retry it in the background | Unauthenticated |
| Resume succeeds. The retained session accepted the sequence | Replay retained Dispatches | Replaying |
Session cannot be resumed. Resume named an unknown or expired session, or a seq below the replay floor | Send Invalid Session with d: false | Unauthenticated |
Replaying
Section titled “Replaying”| Event and condition | Action | Next state |
|---|---|---|
| Retained replay completes. All retained Dispatches were sent | Send Resumed | Ready |
| Authenticated command. The session is attached | Process the command | Replaying |
| Identify. A session is attached | Close with 4005 and reason Already authenticated | Closed |
| Resume. Valid Resume payload | Attach the named session to this socket in place of the current one | Starting |
| Event and condition | Action | Next state |
|---|---|---|
| Authenticated command. The command is valid in the session | Process the command | Ready |
| Identify. A session is attached | Close with 4005 and reason Already authenticated | Closed |
| Resume. Valid Resume payload | Attach the named session to this socket in place of the current one | Starting |
Replaying or Ready
Section titled “Replaying or Ready”| Event and condition | Action | Next state |
|---|---|---|
| The session process ends. The session was terminated while this socket held it | Send Invalid Session with d: false | Unauthenticated |
| Gateway drain or session transfer. The node stops serving this session | Send Reconnect and close with 4000 | Closed |
| The session is resumed elsewhere. Another socket attached this session with Resume | Send Reconnect and close with 4000 | Closed |
Any open state
Section titled “Any open state”| Event and condition | Action | Next state |
|---|---|---|
Heartbeat. No session is attached, or the payload is null, or the attached session accepts the sequence | Send Heartbeat ACK | Same state |
| Heartbeat deadline. The connection is awaiting an acknowledgement and more than 45,000 ms have passed since the last one | Close with 4009 and reason Heartbeat timeout | Closed |
| Invalid frame or payload. Size, decompression, or decoding validation fails | Close with 4002 and the matching reason from Gateway payload or Framing | Closed |
| Transport ends. A session exists | Retain the session for 60,000 ms | Closed |
An opcode outside the registry, and a server opcode sent by a client, close with 4001 once a session is attached and with 4003 while the connection is unauthenticated.
Hello and session creation
Section titled “Hello and session creation”The server sends Opcode 10 Hello while accepting the WebSocket.
{ "op": 10, "d": { "heartbeat_interval": 41250 }}The interval is in milliseconds and is authoritative for the connection.
Opcode 2 Identify creates a session. A successful Identify sends Ready, whose session_id identifies the retained session. FiveCord publishes no separate resume URL, so a Resume reconnects to the same Gateway endpoint the client discovered.
An invalid token closes with 4004 and reason Invalid token. A user account that already holds 100 live sessions closes with 4008 and reason Too many sessions, and a bot credential is not bounded by that maximum. A malformed shard pair closes with 4010 and reason Invalid shard. A bot shard assignment that resolves more than 2,500 guilds closes with 4011 and reason Sharding required.
Heartbeats
Section titled “Heartbeats”Opcode 1 is accepted before and after authentication. Before a session exists its payload is ignored and the server still acknowledges. Once a session exists, send the most recently processed Dispatch sequence, or null before any Dispatch.
{ "op": 1, "d": 42}The server answers with Opcode 11 Heartbeat ACK, which has no d. Once a session is attached, a d value that is neither null nor an integer closes with 4007 and reason Invalid sequence. When the Gateway cannot confirm the sequence with the session within 5,000 ms, the connection closes with the same code and reason.
The server requests an immediate heartbeat with Opcode 1 and d: null once 90 per cent of the interval has passed since the last acknowledgement. Answer it with your own Opcode 1. Continue sending heartbeats at the advertised interval. A connection that misses the heartbeat deadline closes with 4009 and reason Heartbeat timeout.
A heartbeat with a sequence permanently trims every retained Dispatch at or below that sequence from the replay buffer and records it as the acknowledged sequence. A client MUST send the sequence it has processed, because a later Resume from a lower sequence closes with 4007.
Resuming a session
Section titled “Resuming a session”Opcode 6 supplies the original token, the Ready session_id, and the last processed Dispatch sequence.
{ "op": 6, "d": { "token": "...", "session_id": "6f1d0b7c9a2e4f83b5c1d9e7a4f20b13", "seq": 42 }}A successful Resume replays every retained Dispatch above seq in order and ends with Resumed.
All fields are required. A missing field, a token or session_id that is not a string, or a seq that is not an integer closes with 4002 and reason Invalid resume payload.
The Gateway retains a disconnected session for 60,000 ms. An accepted seq is no greater than the session’s current sequence and no less than the sequence the session has already acknowledged. A seq outside either bound closes with 4007 and reason Invalid sequence.
An unknown or expired session sends Opcode 9 with d: false and leaves the socket unauthenticated. A seq inside both bounds but below the replay floor sends the same Opcode 9. The replay floor is the highest sequence the replay buffer has evicted. A token that does not own the named session closes with 4004 and reason Invalid token. A session that cannot be reached closes with 4000 and reason Session unavailable. None of those failures destroys a separately retained session.
A successful Resume replaces the session’s socket and restores the presence status the session last selected. A session whose socket dropped is published as offline after 5,000 ms, so a Resume later than that republishes the restored status.
Unlike Identify, Resume is accepted in every open state. A socket that already has a session attached still processes a Resume and attaches the named session in its place. Send Resume only on a fresh socket.
When the resumed session was attached to a different socket, that socket receives Opcode 7 Reconnect and then closes with 4000.
Reconnect
Section titled “Reconnect”Opcode 7 Reconnect asks the client to open a new WebSocket. The Gateway sends it when the node drains the session, when the node transfers the session to another node, and to the socket a successful Resume displaces.
{ "op": 7}The current socket then closes with 4000 and reason Session drain requested; reconnect to continue. A Resume sent within 60,000 ms of the close can recover the session, subject to the sequence bounds in Resuming a session.
Invalid session
Section titled “Invalid session”Opcode 9 with d: false means the named session cannot be resumed. The Gateway sends it when Resume names an unknown or expired session, when Resume names a seq below the replay floor, and when the session process attached to a live connection ends.
{ "op": 9, "d": false}The socket returns to the unauthenticated state, so it stays open and can Identify again. FiveCord never sends d: true.
Sharding
Section titled “Sharding”In Identify, a client MAY supply shard as a [shard_id, shard_count] pair. shard_id is a non-negative integer below shard_count, and shard_count is from 1 through 16,384. An omitted or null value applies no sharding. A malformed pair closes with 4010 and reason Invalid shard.
A guild belongs to ((guild_id >> 22) % shard_count), computed on the integer value of the guild’s decimal snowflake string.
The pair selects the session’s guild membership. At Identify, FiveCord filters the account’s guild list to the guilds the shard owns, and the session connects only to those.
On every session, the filtered set is also the Ready guilds array. A bot session has each of those guilds as an unavailable guild, and one Guild Create or Guild Delete per guild follows Ready. Ready echoes the accepted pair back as shard.
FiveCord checks only a bot session against the guild ceiling. A bot whose shard owns more than 2,500 guilds closes with 4011 and reason Sharding required. A bot that supplies no pair is checked against its whole guild list. A user session is bounded by the 100-session-per-user limit alone, whatever its guild count.
FiveCord has no large bot tier, no shard-count alignment requirement, and no Identify concurrency buckets. GET /v1/gateway/bot returns a fixed recommendation.
Ordering
Section titled “Ordering”Dispatch ordering applies within one Gateway session. It creates no total order across shards, HTTP responses, or Media Proxy operations.
Guild Create and Guild Sync send the complete roles, channels, emojis, stickers, and voice states for the guild they name, and a client replaces its stored lists with them, as Guild Create describes. Every other Dispatch for that guild changes part of that stored state.