Skip to main content
The Gateway API lets apps open secure WebSocket connections with Discord to receive events about actions that take place in a server/guild, like when a channel is updated or a role is created. There are a few cases where apps will also use Gateway connections to update or request resources, like when updating voice state.
In most cases, performing REST operations on Discord resources can be done using the HTTP API rather than the Gateway API.
The Gateway is Discord’s form of real-time communication used by clients (including apps). Interacting with the Gateway can be tricky, but there are community-built libraries with built-in support that simplify the most complicated bits. If you’re planning on writing a custom implementation, read the following documentation in its entirety.

Gateway Events

Gateway events are payloads sent over a Gateway connection—either from an app to Discord, or from Discord to an app. An app typically sends events when connecting and managing its connection to the Gateway, and receives events when listening to actions taking place in a server. All Gateway events are encapsulated in a Gateway event payload.

Example Gateway Event

{
  "op": 0,
  "d": {},
  "s": 42,
  "t": "GATEWAY_EVENT_NAME"
}

Sending Events

When sending a Gateway event (like when performing an initial handshake or updating presence), your app must send an event payload object with a valid opcode (op) and inner data object (d).
Specific rate limits are applied when sending events, which you can read about in the Rate Limiting section.
Event payloads sent over a Gateway connection:
  1. Must be serialized in plain-text JSON or binary ETF.
  2. Must not exceed 4096 bytes. If an event payload exceeds 4096 bytes, the connection will be closed with a 4002 close event code.

Receiving Events

Receiving a Gateway event from Discord is much more common than sending them. While some events are sent automatically, most require your app to define intents when Identifying. Intents are bitwise values that can be ORed (|) together to indicate which events (or groups of events) you want Discord to send your app.

Dispatch Events

Dispatch (opcode 0) events are the most common type of event your app will receive. Most Gateway events which represent actions taking place in a guild will be sent as Dispatch events. When your app is parsing a Dispatch event:
  • The t field determines which Gateway event the payload represents.
  • The s field represents the sequence number of the event. Cache the most recent non-null s value for heartbeats and when Resuming a connection.

Connections

Gateway connections are persistent WebSockets that introduce more complexity than sending HTTP requests. Your app must know how to open the initial connection, maintain it, and handle disconnects.

Connection Lifecycle

At a high level, Gateway connections consist of the following cycle:
  1. App establishes a connection after fetching a WSS URL using Get Gateway or Get Gateway Bot.
  2. Discord sends a Hello (opcode 10) event with a heartbeat_interval.
  3. App starts sending Heartbeat (opcode 1) events every heartbeat_interval milliseconds.
  4. App sends an Identify (opcode 2) event for the initial handshake.
  5. Discord sends a Ready (opcode 0) event with session details including resume_gateway_url and session_id.
  6. The connection may drop for various reasons. Whether the app can Resume is determined by the close opcode/code received.
  7. If resumable, open a new connection using resume_gateway_url and send a Resume (opcode 6) event. Otherwise, open a new connection using the cached URL and re-identify.

Connecting

Before establishing a Gateway connection, call Get Gateway or Get Gateway Bot to fetch the WSS URL. Cache the url value and use it when reconnecting. When connecting, explicitly pass the API version and encoding as query parameters.
wss://gateway.discord.gg/?v=10&encoding=json is an example WSS URL for connecting to the Gateway.

Gateway URL Query String Params

FieldTypeDescriptionAccepted Values
vintegerAPI Version to useAPI version
encodingstringThe encoding of received gateway packetsjson or etf
compress?stringThe optional transport compression of gateway packetszlib-stream or zstd-stream

Hello Event

Once connected, your app receives a Hello (opcode 10) event with a heartbeat_interval in milliseconds.
{
  "op": 10,
  "d": {
    "heartbeat_interval": 45000
  }
}

Sending Heartbeats

Heartbeats are pings used to keep an active Gateway connection alive. After connecting, your app should send heartbeats in a background process until the connection is closed.

Heartbeat Interval

Upon receiving the Hello event, wait heartbeat_interval * jitter (where jitter is a random value between 0 and 1), then send the first Heartbeat (opcode 1) event. From that point, send a heartbeat every heartbeat_interval milliseconds.
The initial jitter offset prevents mass client reconnection spikes at the same time.
Include the last sequence number (s field) from received events in the d field of the heartbeat. If no events have been received yet, pass null. Discord responds to each heartbeat with a Heartbeat ACK (opcode 11) event:
{
  "op": 11
}
If a client does not receive a heartbeat ACK between its attempts at sending heartbeats, the connection may be “zombied.” Terminate the connection with any close code besides 1000 or 1001, then reconnect and attempt to Resume.

Heartbeat Requests

Discord may also request additional heartbeats by sending a Heartbeat (opcode 1) event. Upon receiving it, immediately send back a heartbeat without waiting for the current interval to elapse.

Identifying

After the connection is open and heartbeats are being sent, send an Identify (opcode 2) event. This is the initial handshake required before your app can send or receive most Gateway events.
Clients are limited to 1000 IDENTIFY calls per 24-hour period (global, across all shards, not counting RESUME calls). Upon hitting this limit, all active sessions will be terminated, the bot token will be reset, and the owner will receive an email notification.
After a valid Identify payload, Discord responds with a Ready event.

Example Identify Payload

{
  "op": 2,
  "d": {
    "token": "my_token",
    "intents": 513,
    "properties": {
      "os": "linux",
      "browser": "my_library",
      "device": "my_library"
    }
  }
}

Ready Event

The Ready event includes state your app needs to start interacting with the platform. Two fields are especially important to cache:
  • resume_gateway_url — WebSocket URL to use when Resuming after a disconnect.
  • session_id — ID for the Gateway session, required when Resuming.

Disconnecting

Handling a Disconnect

When your app encounters a disconnect, it receives a close code that determines whether you can reconnect and Resume, or must start over.
  • If your app can reconnect, use the resume_gateway_url and session_id from the Ready event. See Resuming.
  • If your app cannot reconnect, open a new connection with the URL from Get Gateway or Get Gateway Bot and re-identify.

Initiating a Disconnect

Closing with code 1000 or 1001 invalidates the session and shows the bot as offline. Closing with another code or dropping the TCP connection keeps the session alive and allows resuming within a timeout window.

Resuming

When disconnected, Discord supports reconnecting and replaying missed events starting from the last sequence number received. Unlike the initial connection, Resuming does not require a new Identify. Your app should attempt to resume when:
  1. It receives a Reconnect (opcode 7) event.
  2. It’s disconnected with a close code that permits reconnection.
  3. It’s disconnected without any close code.
  4. It receives an Invalid Session (opcode 9) with d set to true.

Preparing to Resume

Before sending a Resume event, you need:
  • session_id from the Ready event
  • resume_gateway_url from the Ready event
  • The last sequence number (s) from the final Dispatch event before disconnect
Open a new connection using resume_gateway_url (not the original URL) with the same query parameters, then send:
{
  "op": 6,
  "d": {
    "token": "my_token",
    "session_id": "session_id_i_stored",
    "seq": 1337
  }
}
If successful, the Gateway replays missed events in order, then sends a Resumed event. If unsuccessful (e.g., reconnect timeout), you’ll receive an Invalid Session (opcode 9) with d: false and must re-identify.
When resuming, you must provide the same API version and encoding as the initial connection.

Gateway Intents

Intents are bitwise values passed in the intents parameter when Identifying that correlate to sets of related events. If you do not specify an intent, you will not receive any of its associated Gateway events.
Intents are optionally supported on the v6 gateway but required as of v8.
Two types of intents exist:
  • Standard intents can be passed by default with no additional configuration.
  • Privileged intents require you to toggle them in your app’s settings in the Developer Portal before use. Verified apps must also be approved during the verification process.
The connection closes if invalid intents are passed (4013) or an unapproved privileged intent is used (4014).

List of Intents

Any events not listed below are not associated with an intent and are always sent to your app.
GUILDS (1 << 0)
  - GUILD_CREATE
  - GUILD_UPDATE
  - GUILD_DELETE
  - GUILD_ROLE_CREATE
  - GUILD_ROLE_UPDATE
  - GUILD_ROLE_DELETE
  - CHANNEL_CREATE
  - CHANNEL_UPDATE
  - CHANNEL_DELETE
  - CHANNEL_PINS_UPDATE
  - THREAD_CREATE
  - THREAD_UPDATE
  - THREAD_DELETE
  - THREAD_LIST_SYNC
  - THREAD_MEMBER_UPDATE
  - THREAD_MEMBERS_UPDATE *
  - STAGE_INSTANCE_CREATE
  - STAGE_INSTANCE_UPDATE
  - STAGE_INSTANCE_DELETE

GUILD_MEMBERS (1 << 1) **
  - GUILD_MEMBER_ADD
  - GUILD_MEMBER_UPDATE
  - GUILD_MEMBER_REMOVE
  - THREAD_MEMBERS_UPDATE *

GUILD_MODERATION (1 << 2)
  - GUILD_AUDIT_LOG_ENTRY_CREATE
  - GUILD_BAN_ADD
  - GUILD_BAN_REMOVE

GUILD_EXPRESSIONS (1 << 3)
  - GUILD_EMOJIS_UPDATE
  - GUILD_STICKERS_UPDATE
  - GUILD_SOUNDBOARD_SOUND_CREATE
  - GUILD_SOUNDBOARD_SOUND_UPDATE
  - GUILD_SOUNDBOARD_SOUND_DELETE
  - GUILD_SOUNDBOARD_SOUNDS_UPDATE

GUILD_INTEGRATIONS (1 << 4)
  - GUILD_INTEGRATIONS_UPDATE
  - INTEGRATION_CREATE
  - INTEGRATION_UPDATE
  - INTEGRATION_DELETE

GUILD_WEBHOOKS (1 << 5)
  - WEBHOOKS_UPDATE

GUILD_INVITES (1 << 6)
  - INVITE_CREATE
  - INVITE_DELETE

GUILD_VOICE_STATES (1 << 7)
  - VOICE_CHANNEL_EFFECT_SEND
  - VOICE_STATE_UPDATE

GUILD_PRESENCES (1 << 8) **
  - PRESENCE_UPDATE

GUILD_MESSAGES (1 << 9)
  - MESSAGE_CREATE
  - MESSAGE_UPDATE
  - MESSAGE_DELETE
  - MESSAGE_DELETE_BULK

GUILD_MESSAGE_REACTIONS (1 << 10)
  - MESSAGE_REACTION_ADD
  - MESSAGE_REACTION_REMOVE
  - MESSAGE_REACTION_REMOVE_ALL
  - MESSAGE_REACTION_REMOVE_EMOJI

GUILD_MESSAGE_TYPING (1 << 11)
  - TYPING_START

DIRECT_MESSAGES (1 << 12)
  - MESSAGE_CREATE
  - MESSAGE_UPDATE
  - MESSAGE_DELETE
  - CHANNEL_PINS_UPDATE

DIRECT_MESSAGE_REACTIONS (1 << 13)
  - MESSAGE_REACTION_ADD
  - MESSAGE_REACTION_REMOVE
  - MESSAGE_REACTION_REMOVE_ALL
  - MESSAGE_REACTION_REMOVE_EMOJI

DIRECT_MESSAGE_TYPING (1 << 14)
  - TYPING_START

MESSAGE_CONTENT (1 << 15) ***

GUILD_SCHEDULED_EVENTS (1 << 16)
  - GUILD_SCHEDULED_EVENT_CREATE
  - GUILD_SCHEDULED_EVENT_UPDATE
  - GUILD_SCHEDULED_EVENT_DELETE
  - GUILD_SCHEDULED_EVENT_USER_ADD
  - GUILD_SCHEDULED_EVENT_USER_REMOVE

AUTO_MODERATION_CONFIGURATION (1 << 20)
  - AUTO_MODERATION_RULE_CREATE
  - AUTO_MODERATION_RULE_UPDATE
  - AUTO_MODERATION_RULE_DELETE

AUTO_MODERATION_EXECUTION (1 << 21)
  - AUTO_MODERATION_ACTION_EXECUTION

GUILD_MESSAGE_POLLS (1 << 24)
  - MESSAGE_POLL_VOTE_ADD
  - MESSAGE_POLL_VOTE_REMOVE

DIRECT_MESSAGE_POLLS (1 << 25)
  - MESSAGE_POLL_VOTE_ADD
  - MESSAGE_POLL_VOTE_REMOVE
* Thread Members Update contains different data depending on which intents are used. ** Events under GUILD_PRESENCES and GUILD_MEMBERS are turned off by default on all API versions. *** MESSAGE_CONTENT does not represent individual events but affects what data is present for events that could contain message content fields.

Caveats

  • Guild Member Update is sent for current-user updates regardless of whether the GUILD_MEMBERS intent is set.
  • Thread Members Update by default only includes if the current user was added or removed. Request the GUILD_MEMBERS intent to receive updates for other users.

Privileged Intents

Some intents are “privileged” due to the sensitive nature of their data:
  • GUILD_PRESENCES
  • GUILD_MEMBERS
  • MESSAGE_CONTENT
Unverified apps can use privileged intents without approval but must still enable them in app settings. If the app’s verification status changes, it must apply for the privileged intent(s).

Enabling Privileged Intents

Enable privileged intents in the Developer Portal under your app’s Bot page in the “Privileged Gateway Intents” section. Only toggle intents your bot requires to function. Verified apps must request access to privileged intents during the verification process. If already verified and you need additional privileged intents, contact support.

Gateway Restrictions

When using API v8 and above, all intents must be specified in the intents parameter when Identifying. Passing a privileged intent without it being configured or approved closes the connection with a 4014 close code.

HTTP Restrictions

Privileged intents also affect which HTTP API endpoints your app can call. For example, the GUILD_MEMBERS intent is required to use the List Guild Members endpoint. HTTP API restrictions are independent of which intents are passed during IDENTIFY.

Message Content Intent

MESSAGE_CONTENT (1 << 15) is a unique privileged intent not directly associated with Gateway events. Instead, it permits your app to receive message content data across the APIs. Apps without this intent receive empty values in content fields with these exceptions:

Rate Limiting

This section refers to Gateway rate limits, not HTTP API rate limits.
Apps can send 120 gateway events per connection every 60 seconds (average of 2 per second). Apps that surpass the limit are immediately disconnected. Repeat offenders may have their API access revoked. Apps also have a limit for concurrent Identify requests allowed per 5 seconds. Exceeding this limit results in an Invalid Session (opcode 9).

Encoding and Compression

Use the encoding parameter when connecting to choose between plain-text JSON or binary ETF. JSON is generally recommended if you’re unsure. Apps can also enable optional compression to receive zlib-compressed or zstd-compressed packets.

Using JSON Encoding

JSON encoding supports optional payload compression.

Payload Compression

If an app is using payload compression, it cannot use transport compression.
Payload compression enables optional per-packet compression for some events. Enable it by setting compress: true in the Identify event. When enabled, your app must decompress payloads before parsing them.

Using ETF Encoding

When using ETF encoding:
  • Snowflake IDs are transmitted as 64-bit integers or strings.
  • Your app cannot send compressed messages to the server.
  • You must use string keys in payloads—atom keys cause a 4002 decode error.
See erlpack for an ETF implementation example.

Transport Compression

Transport compression enables compression for all packets. Available options are zlib-stream and zstd-stream.

zlib-stream

When zlib transport compression is enabled, push received data to a buffer until you receive the 4-byte Z_SYNC_FLUSH suffix (00 00 ff ff), then decompress the buffer. Each Gateway connection should use its own unique zlib context.
# Z_SYNC_FLUSH suffix
ZLIB_SUFFIX = b'\x00\x00\xff\xff'
buffer = bytearray()
inflator = zlib.decompressobj()

def on_websocket_message(msg):
    buffer.extend(msg)
    if len(msg) < 4 or msg[-4:] != ZLIB_SUFFIX:
        return
    msg = inflator.decompress(buffer)
    buffer = bytearray()
    # treat `msg` as JSON or ETF encoded depending on your `encoding` param

zstd-stream

When zstd-stream is enabled, process all data through a zstd decompression context for the lifetime of the gateway connection. Each websocket message corresponds to a single gateway message but does not end a zstd frame—call ZSTD_decompressStream repeatedly until all data is processed.

Tracking State

Most client state is provided during the initial Ready event and in subsequent Guild Create events. As resources change, Gateway events notify your app and provide associated data. Cache relevant resource states to avoid excessive API calls.
For larger apps, only store data in memory that is needed for the app to operate. For example, you may not need to cache member information if events like MESSAGE_CREATE already include the full member object.

Guild Availability

When connecting as a bot, guilds start as unavailable. The gateway automatically attempts to reconnect on your behalf, and you will receive Guild Create events as guilds become available.

Sharding

For apps in a large number of guilds, the Gateway supports user-controlled guild sharding, which splits events across multiple Gateway connections.
Each shard supports a maximum of 2500 guilds. Apps in 2500+ guilds must enable sharding.
Enable sharding by including the shard array in the Identify payload: [shard_id, num_shards] (zero-indexed shard ID and total shard count).
The Get Gateway Bot endpoint provides a recommended number of shards in the shards field.
Sharding formula — to calculate which shard receives a given guild’s events:
shard_id = (guild_id >> 22) % num_shards
Gateway events without a guild_id (DMs, subscriptions, entitlements) are only sent to shard 0.

Max Concurrency

Start shards concurrently based on the max_concurrency value from the session start limit object. Shards are bucketed by:
rate_limit_key = shard_id % max_concurrency
Start shards in bucket order—fill bucket 0 before starting bucket 1, and so on.

Sharding for Large Bots

Bots in more than 150,000 guilds have additional sharding requirements. Discord will DM and email the bot owner when the bot is migrated to large bot sharding. The number of shards must be a multiple of the assigned shard number; invalid shard counts close the connection with a 4010 close code. The Get Gateway Bot endpoint always returns the correct shard count. Large bots also receive an increased session start limit: max(2000, (guild_count / 1000) * 5) per day.

Get Gateway

GET /gateway
This endpoint does not require authentication.
Returns an object with a valid WSS URL for Connecting to the Gateway. Apps should cache this value and only call this endpoint to retrieve a new URL when the cached one fails.
{
  "url": "wss://gateway.discord.gg/"
}

Get Gateway Bot

GET /gateway/bot
This endpoint requires authentication using a valid bot token.
Returns the WSS URL plus additional metadata useful for large or sharded bots. Unlike Get Gateway, this route should not be cached for extended periods as the value can change as the bot joins/leaves guilds.
FieldTypeDescription
urlstringWSS URL that can be used for connecting to the Gateway
shardsintegerRecommended number of shards to use when connecting
session_start_limitsession start limit objectInformation on the current session start limit
{
  "url": "wss://gateway.discord.gg/",
  "shards": 9,
  "session_start_limit": {
    "total": 1000,
    "remaining": 999,
    "reset_after": 14400000,
    "max_concurrency": 1
  }
}

Session Start Limit Object

FieldTypeDescription
totalintegerTotal number of session starts the current user is allowed
remainingintegerRemaining number of session starts the current user is allowed
reset_afterintegerNumber of milliseconds after which the limit resets
max_concurrencyintegerNumber of identify requests allowed per 5 seconds