Skip to main content
The Gateway connection is managed automatically via client.login(token). Direct interaction with WebSocketManager and WebSocketShard is not required in most cases.

Connection Lifecycle

GET /gateway/bot

  connect_async (TLS)

  ← Hello { heartbeat_interval }

  → Identify (or Resume if session_id + seq are available)

  ← READY (or RESUMED)

  ← Dispatch events...
StepDescription
GET /gateway/botFetches the WebSocket URL and recommended shard count.
connect_asyncOpens a TLS WebSocket connection to the received URL.
HelloThe server sends heartbeat_interval in milliseconds. The shard starts a heartbeat task.
IdentifyThe shard sends the token, intents, shard info, and optional presence.
ResumeIf session_id and seq are saved - Resume is sent instead of Identify.
READY / RESUMEDSession established. The client starts receiving events.

WebSocketManagerOptions

FieldTypeDefaultDescription
tokenString""Bot token. Set automatically during client.login.
intentsu640Gateway intents. Always 0 in Fluxer.
presenceOption<GatewayPresenceUpdateSendData>NoneInitial presence on connection.
shard_idsOption<Vec<u32>>NoneList of specific shard IDs to start. If None, all are started.
shard_countOption<u32>NoneTotal number of shards. If None, taken from the /gateway/bot response.
versionString"1"Gateway version.

WebSocketManager

Manages a pool of shards. Created and started inside client.login.

connect

pub async fn connect(&mut self) -> Result<(), RestError>
Fetches /gateway/bot, determines the shard count, and starts each shard in a separate tokio::spawn task.

broadcast

pub async fn broadcast(&self, payload: Value)
Sends a JSON payload to all active shards. Called via client.send_to_gateway.

send

pub async fn send(&self, shard_id: u32, payload: Value) -> Result<(), String>
Sends a JSON payload to a specific shard by its ID. Returns Err if the shard is not found or its channel is closed.

shard_count

pub fn shard_count(&self) -> u32
Returns the total number of running shards.

gateway_url

pub fn gateway_url(&self) -> Option<&str>
Returns the WebSocket URL received from /gateway/bot. None before connect is called.

WebSocketShard

One shard - one WebSocket connection. Manages its own heartbeat and automatically reconnects on disconnect.

ShardOptions

FieldTypeDescription
urlStringGateway WebSocket URL.
tokenStringBot token.
intentsu64Gateway intents.
presenceOption<GatewayPresenceUpdateSendData>Initial presence.
shard_idu32ID of this shard (from 0 to num_shards - 1).
num_shardsu32Total number of shards.
versionStringGateway version.

Heartbeat

After receiving Hello, the shard starts a separate tokio::spawn task:
  1. The first heartbeat is sent with a random jitter: sleep(interval * rand()).
  2. After that, a heartbeat is sent every heartbeat_interval milliseconds.
  3. If HeartbeatAck is not received before the next heartbeat while a session is active - the shard disconnects and reconnects.
rand_f64() in the shard is implemented via SystemTime::subsec_nanos(). This is not cryptographically secure randomness, but it is sufficient for heartbeat jitter.

Reconnection

On any disconnect, the shard automatically reconnects with exponential backoff.
ParameterValue
Initial delay1,000 ms
Maximum delay45,000 ms
Growth factor×1.5 on each failure
Jitter×(0.75 … 1.25) of the current delay
After a successful connection, the delay resets to 1,000 ms.

Identify vs Resume

ConditionAction
session_id and seq are savedSends Resume - restores the session without losing missed events.
session_id or seq are missingSends Identify - starts a new session.
Opcode InvalidSession (9)Resets session_id and seq, waits 1–5 seconds, reconnects with Identify.

Opcodes

OpcodeValueDirectionDescription
Dispatch0← ServerGateway event (READY, MESSAGE_CREATE, etc.).
Heartbeat1→ ClientServer requests an immediate heartbeat.
Identify2→ ClientAuthenticate a new session.
PresenceUpdate3→ ClientUpdate status and activity.
VoiceStateUpdate4→ ClientConnect/disconnect from a voice channel.
Resume6→ ClientRestore an interrupted session.
Reconnect7← ServerServer requests a reconnect.
RequestGuildMembers8→ ClientRequest the guild member list.
InvalidSession9← ServerSession is invalid.
Hello10← ServerFirst message after connecting, contains heartbeat_interval.
HeartbeatAck11← ServerHeartbeat acknowledgement.

Close Codes

Determine whether to reconnect when a Close frame is received.

Reconnection is performed

CodeDescription
1000Normal closure.
1001Client going away.
1005No code specified.
1006Abnormal closure (no Close frame).
1011Internal server error.
1012Service restart.
1013Try again later.
1014Bad Gateway.
1015TLS error.
4000Unknown error.
4007Invalid sequence number during Resume.
4009Session timed out.
4010Invalid shard.
4011Sharding required.
4012Invalid API version.

Reconnection is not performed

CodeDescription
4001Unknown opcode.
4002Decode error.
4003Not authenticated.
4004Token is invalid. Reconnecting is pointless.
4005Already authenticated.
4008Rate limit exceeded.
4013Invalid intents.
4014Disallowed intents.
When code 4004 is received, the shard shuts down without reconnecting. Verify that the token is correct.

GatewayPresenceUpdateSendData

Set in ClientOptions.presence or via send_to_gateway with opcode 3.
FieldTypeDescription
sinceOption<u64>Unix time (ms) since going AFK. None if not AFK.
activitiesOption<Vec<GatewayActivity>>List of activities.
custom_statusOption<GatewayCustomStatus>Custom status (Fluxer-specific).
statusStringStatus: "online", "idle", "dnd", "invisible".
afkOption<bool>Whether the session is AFK.

GatewayActivity

FieldTypeDescription
nameStringActivity name.
kindu8Type: 0 - Playing, 1 - Streaming, 2 - Listening, 3 - Watching, 5 - Competing.
urlOption<String>Stream URL. Used only when kind == 1.

GatewayCustomStatus

FieldTypeDescription
textOption<String>Custom status text.
emoji_nameOption<String>Emoji name.
emoji_idOption<String>Custom emoji ID.

WsEvent / ShardEvent

Internal events that shards pass to the manager.

ShardEvent (inside a shard)

VariantDescription
ShardEvent::Ready(Value)The shard received a READY payload.
ShardEvent::ResumedThe shard restored its session via Resume.
ShardEvent::Dispatch(GatewayReceivePayload)A Gateway event arrived.
ShardEvent::Close(u16)Connection closed with the given code.
ShardEvent::Error(String)WebSocket error.
ShardEvent::Debug(String)Diagnostic message.

WsEvent (from manager to client)

VariantFieldsDescription
WsEvent::ShardReadyshard_id: u32, data: ValueShard is ready.
WsEvent::ShardResumedshard_id: u32Shard restored its session.
WsEvent::Dispatchshard_id: u32, payload: GatewayReceivePayloadGateway event to process.
WsEvent::ShardCloseshard_id: u32, code: u16Shard closed.
WsEvent::Errorshard_id: u32, error: StringShard error.
WsEvent::Debugmessage: StringDiagnostic message.

Examples

use fluxer_core::client::{Client, ClientOptions};
use fluxer_types::gateway::{GatewayActivity, GatewayPresenceUpdateSendData};

let options = ClientOptions {
    intents: 0,
    presence: Some(GatewayPresenceUpdateSendData {
        status: "online".to_string(),
        activities: Some(vec![GatewayActivity {
            name: "Fluxer.RUST".to_string(),
            kind: 0,
            url: None,
        }]),
        custom_status: None,
        since: None,
        afk: Some(false),
    }),
    ..Default::default()
};

let mut client = Client::new(options);
use serde_json::json;

let payload = json!({
    "op": 3,
    "d": {
        "status": "dnd",
        "afk": false,
        "since": null,
        "activities": [{
            "name": "Maintenance",
            "type": 0
        }]
    }
});

client.send_to_gateway(payload).await;
use serde_json::json;

let payload = json!({
    "op": 8,
    "d": {
        "guild_id": "GUILD_ID",
        "query": "",
        "limit": 0
    }
});

client.send_to_gateway(payload).await;
use serde_json::json;

let payload = json!({
    "op": 4,
    "d": {
        "guild_id": "GUILD_ID",
        "channel_id": "VOICE_CHANNEL_ID",
        "self_mute": false,
        "self_deaf": false
    }
});

if let Err(e) = client.send_to_shard(0, payload).await {
    tracing::error!("send_to_shard: {e}");
}
client.on_typed(|event| {
    Box::pin(async move {
        if let DispatchEvent::Debug { message } = event {
            tracing::debug!("[Gateway] {message}");
        }
    })
});