Voice Protocol Reference

April 27, 2026 · View on GitHub

Authoritative PHP source: src/Discord/WebSockets/OpEnum.php. Official Discord documentation: https://discord.com/developers/docs/topics/opcodes-and-status-codes.

Table of Contents


Voice Gateway Opcodes (0–20)

These opcodes are used on the voice WebSocket connection (separate from the main gateway).

OpcodeConstantDirectionDescriptionWhen fired
0VOICE_IDENTIFYclient → serverBegin a voice WebSocket connection.First message sent after WS connect; carries server_id, user_id, session_id, and token.
1VOICE_SELECT_PROTOclient → serverSelect the voice protocol and encryption mode.Sent by the client after receiving VOICE_READY (op 2).
2VOICE_READYserver → clientComplete the WebSocket handshake.Server responds to VOICE_IDENTIFY; carries ssrc, ip, port, and supported encryption modes.
3VOICE_HEARTBEATclient → serverKeep the WebSocket connection alive.Sent periodically at the interval given by VOICE_HELLO.
4VOICE_DESCRIPTIONserver → clientDescribe the session (secret key for encryption).Sent by the server after VOICE_SELECT_PROTO; carries the secret_key used by libsodium.
5VOICE_SPEAKINGbothIdentify which users are speaking.Client sends before/after transmitting audio; server relays for all connected users.
6VOICE_HEARTBEAT_ACKserver → clientAcknowledge a heartbeat.Server replies to every VOICE_HEARTBEAT.
7VOICE_RESUMEclient → serverResume a dropped voice connection.Sent instead of VOICE_IDENTIFY when reconnecting with an existing session.
8VOICE_HELLOserver → clientPass the heartbeat interval.First message from the server on a new connection; carries heartbeat_interval.
9VOICE_RESUMEDserver → clientAcknowledge a successful session resume.Server reply to VOICE_RESUME when the session is still valid.
11VOICE_CLIENTS_CONNECTserver → clientOne or more clients have connected to the voice channel.Also exposed as the deprecated alias VOICE_CLIENT_CONNECT.
13VOICE_CLIENT_DISCONNECTserver → clientA client has disconnected from the voice channel.Carries the user_id of the departing user.
15VOICE_CLIENT_UNKNOWN_15unknownUndocumented opcode.Observed in production but not in Discord's official documentation.
18VOICE_CLIENT_UNKNOWN_18unknownUndocumented opcode.Observed in production but not in Discord's official documentation.
20VOICE_CLIENT_PLATFORMserver → clientPlatform type of a connected user.Not officially documented; assumed to carry the user's platform string.

DAVE E2EE Opcodes (21–31)

These opcodes extend the voice gateway to support Discord's DAVE (Discord Audio/Video E2EE) protocol via MLS (Messaging Layer Security). Transition-control opcodes are normal JSON voice payloads; MLS payload opcodes use binary WebSocket frames handled by Discord\Voice\Dave\BinaryFrame. Opcodes marked handled have a dedicated handler in src/Discord/Voice/Client/WS.php.

OpcodeConstantDirectionDescriptionHandled in WS.php
21VOICE_DAVE_PREPARE_TRANSITIONserver → clientA downgrade from the DAVE protocol is upcoming.handleDavePrepareTransition
22VOICE_DAVE_EXECUTE_TRANSITIONserver → clientExecute a previously announced non-zero protocol transition. Transition ID 0 executes locally without waiting for this opcode.handleDaveExecuteTransition
23VOICE_DAVE_TRANSITION_READYclient → serverAcknowledge readiness for a previously announced transition.handleDaveTransitionReady (sends reply)
24VOICE_DAVE_PREPARE_EPOCHserver → clientA DAVE protocol version or MLS group change is upcoming.handleDavePrepareEpoch
25VOICE_DAVE_MLS_EXTERNAL_SENDERserver → clientCredential and public key for the MLS external sender.handleDaveMlsExternalSender
26VOICE_DAVE_MLS_KEY_PACKAGEclient → serverMLS Key Package for a pending group member, sent as a binary WebSocket frame after DAVE session initialization.handleDaveMlsKeyPackage (sends reply)
27VOICE_DAVE_MLS_PROPOSALSserver → clientMLS Proposals to be appended or revoked.handleDaveMlsProposals
28VOICE_DAVE_MLS_COMMIT_WELCOMEbothMLS Commit with optional MLS Welcome messages.handleDaveMlsCommitWelcome (sends reply)
29VOICE_DAVE_MLS_ANNOUNCE_COMMIT_TRANSITIONserver → clientMLS Commit to be processed for an upcoming transition. Transition ID 0 is applied immediately after local media preparation.handleDaveMlsAnnounceCommitTransition
30VOICE_DAVE_MLS_WELCOMEserver → clientMLS Welcome to group for an upcoming transition.handleDaveMlsWelcome
31VOICE_DAVE_MLS_INVALID_COMMIT_WELCOMEclient → serverFlag an invalid commit or welcome and request re-add.handleDaveMlsInvalidCommitWelcome (sends reply)

Note: libdave is mandatory as of March 1st, 2026. Both Manager and WS throw LibDaveNotFoundException immediately if DaveRuntime::isAvailable() returns false.


Voice Close Codes (4011–4016)

Codes returned when a voice WebSocket closes. Critical codes should not trigger a reconnect attempt.

CodeConstantMeaningReconnect?
4011CLOSE_VOICE_SERVER_NOT_FOUNDCan't find the voice server.❌ No (critical)
4012CLOSE_VOICE_UNKNOWN_PROTOUnknown protocol.❌ No (critical)
4014CLOSE_VOICE_DISCONNECTEDDisconnected from channel (e.g. kicked).✅ Yes
4015CLOSE_VOICE_SERVER_CRASHVoice server crashed.✅ Yes
4016CLOSE_VOICE_UNKNOWN_ENCRYPTUnknown encryption mode.❌ No (critical)

Critical voice close codes are enumerated by OpEnum::getCriticalVoiceCloseCodes().


Gateway Opcodes (0–11, 31)

These opcodes are used on the main Discord gateway connection (not the voice WebSocket).

OpcodeConstantDescription
0OP_DISPATCHDispatches an event.
1OP_HEARTBEATUsed for ping checking.
2OP_IDENTIFYUsed for client handshake.
3OP_PRESENCE_UPDATEUsed to update the client presence.
4OP_VOICE_STATE_UPDATEUsed to join/move/leave voice channels.
5OP_VOICE_SERVER_PINGUsed for voice ping checking.
6OP_RESUMEUsed to resume a closed connection.
7OP_RECONNECTUsed to redirect clients to a new gateway.
8OP_GUILD_MEMBER_CHUNKUsed to request member chunks.
9OP_INVALID_SESSIONUsed to notify clients when they have an invalid session.
10OP_HELLOUsed to pass through the heartbeat interval.
11OP_HEARTBEAT_ACKUsed to acknowledge heartbeats.
31OP_REQUEST_SOUNDBOARD_SOUNDSRequest soundboard sounds.

Gateway Close Codes (4000–4014)

Codes returned when the main gateway WebSocket closes.

CodeConstantMeaningReconnect?
1000CLOSE_NORMALNormal close or heartbeat is invalid.✅ Yes
1006CLOSE_ABNORMALAbnormal close.✅ Yes
4000CLOSE_UNKNOWN_ERRORUnknown error.✅ Yes
4001CLOSE_INVALID_OPCODEUnknown opcode was sent.✅ Yes
4002CLOSE_INVALID_MESSAGEInvalid message was sent.✅ Yes
4003CLOSE_NOT_AUTHENTICATEDNot authenticated.✅ Yes
4004CLOSE_INVALID_TOKENInvalid token on IDENTIFY.❌ No (critical)
4005CONST_ALREADY_AUTHDAlready authenticated.✅ Yes
4006CLOSE_INVALID_SESSIONSession is invalid.✅ Yes
4007CLOSE_INVALID_SEQInvalid RESUME sequence.✅ Yes
4008CLOSE_TOO_MANY_MSGToo many messages sent.✅ Yes
4009CLOSE_SESSION_TIMEOUTSession timeout.✅ Yes
4010CLOSE_INVALID_SHARDInvalid shard.❌ No (critical)
4011CLOSE_SHARDING_REQUIREDSharding required.❌ No (critical)
4012CLOSE_INVALID_VERSIONInvalid API version.❌ No (critical)
4013CLOSE_INVALID_INTENTSInvalid intents.❌ No (critical)
4014CLOSE_DISALLOWED_INTENTSDisallowed intents.❌ No (critical)

Critical gateway close codes are enumerated by OpEnum::getCriticalCloseCodes().


Usage Notes

Helper methods on OpEnum

MethodDescription
OpEnum::isVoiceCode(int $code)Returns true if the integer is a known voice opcode.
OpEnum::isGatewayCode(int $code)Returns true if the integer is a known gateway opcode.
OpEnum::isValidCode(int $code)Returns true if the integer is any known opcode.
OpEnum::isCriticalCloseCode(int $code)Returns true if the code is a critical gateway close code (no reconnect).
OpEnum::isCriticalVoiceCloseCode(int $code)Returns true if the code is a critical voice close code (no reconnect).
OpEnum::getVoiceCodes()Returns all voice opcodes as an array of enum cases.
OpEnum::getGatewayCodes()Returns all gateway opcodes as an array of enum cases.
OpEnum::voiceCodeToString(?self $code, bool $snakeCase, bool $pluckVoicePrefix)Converts a voice opcode enum value to a human-readable string.

Undocumented opcodes

Opcodes 15, 18, and 20 (VOICE_CLIENT_UNKNOWN_15, VOICE_CLIENT_UNKNOWN_18, VOICE_CLIENT_PLATFORM) are observed in production traffic but are not covered by Discord's official documentation. The library accepts them without error but does not dispatch specific handlers for them.

DAVE binary frames

DAVE MLS payload opcodes are transmitted as binary WebSocket frames encoded by Discord\Voice\Dave\BinaryFrame. Server frames include the gateway sequence; client frames carry only the opcode and payload:

Server → Client: [2 bytes sequence] [1 byte opcode] [N bytes payload]
Client → Server: [1 byte opcode] [N bytes payload]

Outbound MLS packets such as Opcode 26 key packages, Opcode 28 commit/welcome payloads, and Opcode 31 invalid-commit notices are sent as binary WebSocket frames, not JSON. The last received server sequence number is stored in Dave\State::$lastReceivedSequence and reused as seq_ack in heartbeat and resume payloads to signal the last acknowledged gateway sequence to the server.