Opcodes define the type of Gateway message being sent or received. All Gateway messages include an op field that specifies the opcode.
Opcode Reference
The following opcodes are defined in fluxer_gateway/src/utils/constants.erl:
| Opcode | Name | Direction | Description |
|---|
| 0 | Dispatch | Server → Client | Event dispatched to client |
| 1 | Heartbeat | Both | Heartbeat request/response |
| 2 | Identify | Client → Server | Start a new session |
| 3 | Presence Update | Client → Server | Update client presence |
| 4 | Voice State Update | Client → Server | Join/leave/update voice |
| 5 | Voice Server Ping | Client → Server | Voice server ping (reserved) |
| 6 | Resume | Client → Server | Resume a disconnected session |
| 7 | Reconnect | Server → Client | Server requests reconnect |
| 8 | Request Guild Members | Client → Server | Request guild member chunks |
| 9 | Invalid Session | Server → Client | Session is invalid |
| 10 | Hello | Server → Client | Initial handshake |
| 11 | Heartbeat ACK | Server → Client | Heartbeat acknowledgement |
| 12 | Gateway Error | Server → Client | Gateway error occurred |
| 14 | Lazy Request | Client → Server | Request lazy-loaded data |
Opcode 13 is intentionally skipped in the specification.
Server → Client Opcodes
These opcodes are sent by the Gateway to clients.
Opcode 0: Dispatch
Dispatches events to the client. This is the most common opcode.
Payload:
{
"op": 0,
"t": "MESSAGE_CREATE",
"s": 42,
"d": {
"id": "1234567890",
"content": "Hello!",
"channel_id": "9876543210"
}
}
Fields:
| Field | Type | Description |
|---|
op | 0 | Dispatch opcode |
t | string | Event name (see Events) |
s | integer | Sequence number (monotonically increasing) |
d | object | Event data (varies by event type) |
Store the sequence number (s) to enable session resumption.
Opcode 7: Reconnect
The server requests that the client reconnect to the Gateway.
Payload:
Client Action:
- Immediately close the connection
- Reconnect to the Gateway
- Send a Resume (opcode 6) with the last sequence number
Do NOT send a new Identify. Always attempt to Resume when receiving this opcode.
Opcode 9: Invalid Session
Indicates the session is invalid and cannot be resumed.
Payload:
Fields:
| Field | Type | Description |
|---|
d | boolean | Whether the session is resumable (always false in practice) |
Client Action:
- Wait 1-5 seconds (randomized)
- Send a new Identify (opcode 2)
- Do NOT attempt to Resume
Causes:
- Session not found (expired or invalid
session_id)
- Identify rate limited
- Invalid token during Resume
Implementation: gateway_handler.erl:send_invalid_session/1
Opcode 10: Hello
Sent immediately after connecting. Contains heartbeat configuration.
Payload:
{
"op": 10,
"d": {
"heartbeat_interval": 41250
}
}
Fields:
| Field | Type | Description |
|---|
d.heartbeat_interval | integer | Milliseconds between heartbeats (41,250ms) |
Client Action:
- Start heartbeat timer with the given interval
- Send Identify or Resume
The heartbeat interval is always 41,250 milliseconds in the current implementation.
Opcode 11: Heartbeat ACK
Acknowledges receipt of a client heartbeat.
Payload:
Client Action:
Update last ACK timestamp to prevent timeout.
If you don’t receive a Heartbeat ACK within 45 seconds, the connection may be dead. Consider reconnecting.
Opcode 12: Gateway Error
Indicates a Gateway-level error occurred.
Payload:
{
"op": 12,
"d": {
"code": "VOICE_CHANNEL_NOT_FOUND",
"message": "Voice channel not found"
}
}
This opcode is used for errors that don’t warrant connection closure.
Client → Server Opcodes
These opcodes are sent by clients to the Gateway.
Opcode 1: Heartbeat
Responds to server heartbeat requests or sends proactive heartbeats.
Payload:
Fields:
| Field | Type | Description |
|---|
d | integer | null | Last sequence number received (null before Ready) |
Server Response:
Opcode 11 (Heartbeat ACK)
Rate Limit:
Counted towards the 120 events/60s limit.
Implementation: gateway_handler.erl:handle_heartbeat/4
Opcode 2: Identify
Starts a new Gateway session.
Payload:
{
"op": 2,
"d": {
"token": "your.auth.token",
"properties": {
"os": "linux",
"browser": "fluxer-client",
"device": "fluxer-client"
},
"presence": {
"status": "online",
"afk": false
},
"ignored_events": ["TYPING_START", "PRESENCE_UPDATE"],
"flags": 0,
"initial_guild_id": "1234567890"
}
}
Required Fields:
| Field | Type | Description |
|---|
token | string | Bot or user authentication token |
properties | object | Client properties |
properties.os | string | Operating system |
properties.browser | string | Client/browser name |
properties.device | string | Device name |
Optional Fields:
| Field | Type | Default | Description |
|---|
presence | object | null | Initial presence state |
ignored_events | array | [] | Event names to filter out |
flags | integer | 0 | Connection flags |
initial_guild_id | snowflake | null | Guild to prioritize loading |
Server Response:
- Success: Opcode 0 with event “READY”
- Failure: Close code 4004 (Authentication Failed) or Opcode 9 (Invalid Session)
Errors:
- Close 4004: Invalid token
- Close 4005: Already authenticated
- Opcode 9: Rate limited
Implementation: gateway_handler.erl:validate_identify_data/1
Opcode 3: Presence Update
Updates the client’s presence status.
Payload:
{
"op": 3,
"d": {
"status": "dnd",
"afk": true,
"mobile": false
}
}
Fields:
| Field | Type | Description |
|---|
status | string | One of: online, idle, dnd, invisible |
afk | boolean | Whether the user is AFK |
mobile | boolean | Whether connecting from mobile |
Status Conversion:
The Gateway automatically converts "offline" to "invisible".
Requirements:
- Must be authenticated (sent Identify)
- Rate limited: 120/60s
Implementation: gateway_handler.erl:handle_presence_update/3
Opcode 4: Voice State Update
Joins, leaves, or updates voice channel state.
Payload:
{
"op": 4,
"d": {
"guild_id": "1234567890",
"channel_id": "9876543210",
"self_mute": false,
"self_deaf": false
}
}
Fields:
| Field | Type | Description |
|---|
guild_id | snowflake | Guild containing the voice channel |
channel_id | snowflake | null | Voice channel ID (null to disconnect) |
self_mute | boolean | Whether to self-mute |
self_deaf | boolean | Whether to self-deafen |
Rate Limit:
10 updates per second. Exceeding this queues updates (max 64). Queue processed every 100ms.
Requirements:
Implementation: gateway_handler.erl:handle_voice_state_update/3
Opcode 6: Resume
Resumes a disconnected session.
Payload:
{
"op": 6,
"d": {
"token": "your.auth.token",
"session_id": "abc123def456",
"seq": 42
}
}
Fields:
| Field | Type | Description |
|---|
token | string | Same token used for Identify |
session_id | string | Session ID from Ready event |
seq | integer | Last sequence number received |
Server Response:
- Success: Replays missed events, then sends RESUMED event
- Failure: Opcode 9 (Invalid Session) or close codes
Errors:
- Opcode 9: Session not found
- Close 4007: Invalid sequence number
- Close 4004: Token mismatch
Implementation: gateway_handler.erl:handle_resume/2
Opcode 8: Request Guild Members
Requests chunks of guild members.
Payload:
{
"op": 8,
"d": {
"guild_id": "1234567890",
"query": "search",
"limit": 100
}
}
Rate Limit:
3 requests per 10 seconds. Exceeding this closes the connection with code 4008.
Requirements:
- Must be authenticated
- Only one concurrent request allowed per connection
Implementation: gateway_handler.erl:handle_request_guild_members/3
Opcode 14: Lazy Request
Requests lazy-loaded data (guild subscriptions, channels, etc.).
Payload:
{
"op": 14,
"d": {
"guild_id": "1234567890",
"channels": {
"9876543210": [[0, 99]]
}
}
}
Requirements:
- Must be authenticated
- Rate limited: 120/60s
Implementation: gateway_handler.erl:handle_lazy_request/3
Error Handling
Unknown Opcode
Sending an unknown opcode results in:
Close Code: 4001 (Unknown Opcode)
Reason: "Unknown opcode"
Before Authentication
Sending opcodes 3, 4, 8, or 14 before authentication results in:
Close Code: 4003 (Not Authenticated)
Reason: "Not authenticated"
Rate Limiting
Exceeding rate limits results in:
Close Code: 4008 (Rate Limited)
Reason: "Rate limited"
Implementation Notes
Opcode mapping is defined in constants.erl:
%% Integer to atom
gateway_opcode(0) -> dispatch;
gateway_opcode(1) -> heartbeat;
gateway_opcode(2) -> identify;
...
%% Atom to integer
opcode_to_num(dispatch) -> 0;
opcode_to_num(heartbeat) -> 1;
opcode_to_num(identify) -> 2;
...
See fluxer_gateway/src/utils/constants.erl:44-74 for the complete implementation.