WS /socket
How to connect to and interact with the WebSocket gateway for real-time presence updates.
WebSocket Endpoint
| Method | Path | Description |
|---|---|---|
| WS | /socket | WebSocket gateway for presence |
Protocol
| Opcode | Name | Direction | Description |
|---|---|---|---|
0 | EVENT | Server → Client | Presence/event updates |
1 | HELLO | Server → Client | Greeting, includes heartbeat_interval |
2 | INITIALIZE | Client → Server | Subscribe to user IDs |
3 | HEARTBEAT | Both | Heartbeat ping/ack |
Here is the sequence diagram for a typical connection:
Messages
HELLO Message
Sent by the server to the client upon connection.
{
"op": 1,
"d": {
"heartbeat_interval": 30000
}
}INITIALIZE Message
Sent by the client to the server to subscribe to user IDs.
{
"op": 2,
"d": {
"subscribe_to_ids": ["example_user_id_1", "example_user_id_2"]
}
}EVENT Message
The WebSocket gateway sends events to subscribed clients when presence data changes, such as whenever the bot receives a presence update from Discord for a subscribed user.
{
"op": 0,
"seq": 123,
"t": "PRESENCE_UPDATE",
"d": {
"user_id": "example_user_id",
"data": "..."
}
}The data field contains a snapshot of presence object for the user.
See Presence Data Model
Note
The following limitations apply to clients connecting to the WebSocket gateway.
| Limitation | Description |
|---|---|
| Frame Size Limit | The server caps inbound frame size to 1 MiB |
| Missed Heartbeats | The server allows up to 3 missed heartbeats before disconnecting the client. |
Message Details
The seq (Sequence) Field
Most messages sent from the server include a seq field, which is a per-connection sequence number. This number starts at 1 for each new connection and increases by 1 with every event message sent to the client.
- Purpose: The
seqfield allows clients to track the order of messages and detect if any messages are missed or received out of order. - Scope: The sequence is unique to each connection and resets when a new WebSocket session is established.
Example:
{
"op": 0,
"seq": 5, // --> This is the 5th event message sent on this connection
"t": "PRESENCE_UPDATE",
"d": {
"user_id": "123456789012345678",
"data": {
// PresenceData
...
}
}Subscribing to User IDs
When initializing the connection, clients must send an INITIALIZE message with a subscribe_to_ids field. This field is always an array, even if you are subscribing to a single user ID.
Example:
{
"op": 2,
"d": {
"subscribe_to_ids": ["1234567890"]
}
}or for multiple users:
{
"op": 2,
"d": {
"subscribe_to_ids": ["1234567890", "0987654321"]
}
}Always use an array for subscribe_to_ids, even for a single user.
Watching Multiple Users
When you subscribe to multiple user IDs using the subscribe_to_ids array, the server will send you updates for each user individually:
-
Initial State:
After subscribing, you will receive a separate initial presence event for each user you requested. Each event contains only one user's data, identified by theuser_idfield in the event payload. -
Live Updates:
Whenever any of your subscribed users changes presence, you will receive aPRESENCE_UPDATEevent for that specific user. Each update only contains data for a single user, and theuser_idfield tells you which user the update is for.
Recommended Tracking Pattern
To keep track of the most recent presence data for all users you are watching:
Maintain a Local Map:
Create a local dictionary or map where the keys are user IDs and the values are the latest presence data for each user.
On Each Event:
When you receive an event (either the initial state or a live update), use the user_id field in the event payload to update the entry in your map for the corresponding user.
Access Latest Data:
At any time, your map will contain the most recent presence information for every user you are tracking, keyed by user_id.
Example (JavaScript):
// Map to store the latest presence for each user
const userPresence = {};
const ws = new WebSocket("wss://tether.eggwite.moe/socket");
let heartbeatInterval = null;
ws.onmessage = (evt) => {
const msg = JSON.parse(evt.data);
if (msg.op === 1) {
// Start heartbeats
heartbeatInterval = setInterval(
() => ws.send(JSON.stringify({ op: 3 })),
msg.d.heartbeat_interval
);
// Subscribe to multiple users
ws.send(
JSON.stringify({
op: 2,
d: { subscribe_to_ids: ["1234567890", "0987654321"] }
})
);
}
if (msg.op === 0 && msg.t === "PRESENCE_UPDATE") {
const { user_id, data } = msg.d;
// Update the presence for the correct user
userPresence[user_id] = data;
console.log(`Presence update for ${user_id}:`, data);
}
};
ws.onclose = () => {
if (heartbeatInterval) clearInterval(heartbeatInterval);
};pip install websocket-clientimport json
import threading
import time
from websocket import WebSocketApp
user_presence = {}
heartbeat_interval = None
def on_message(ws_app, message):
global heartbeat_interval
msg = json.loads(message)
if msg.get("op") == 1:
# Start heartbeats (interval in ms)
heartbeat_interval = msg["d"]["heartbeat_interval"] / 1000.0
def heartbeat():
while True:
time.sleep(heartbeat_interval)
try:
ws_app.send(json.dumps({"op": 3}))
except Exception:
break
threading.Thread(target=heartbeat, daemon=True).start()
# Subscribe to multiple users
ws_app.send(json.dumps({
"op": 2,
"d": {"subscribe_to_ids": ["1234567890", "0987654321"]}
}))
elif msg.get("op") == 0 and msg.get("t") == "PRESENCE_UPDATE":
user_id = msg["d"]["user_id"]
data = msg["d"]["data"]
user_presence[user_id] = data
print(f"Presence update for {user_id}: {data}")
def on_close(ws_app, close_status_code, close_msg):
print("WebSocket closed")
def on_open(ws_app):
print("WebSocket connection opened")
if __name__ == "__main__":
ws = WebSocketApp(
"wss://tether.eggwite.moe/socket",
on_message=on_message,
on_close=on_close,
on_open=on_open
)
ws.run_forever()Example: Subscribe and receive updates
// Minimal example: subscribe to presence updates for a user
const ws = new WebSocket("wss://tether.eggwite.moe/socket");
let heartbeatInterval = null;
ws.onmessage = (evt) => {
const msg = JSON.parse(evt.data);
if (msg.op === 1) {
// Start heartbeats
heartbeatInterval = setInterval(() => ws.send(JSON.stringify({ op: 3 })), msg.d.heartbeat_interval);
// Subscribe to user
ws.send(JSON.stringify({ op: 2, d: { subscribe_to_ids: ["1234567890"] } }));
} else if (msg.op === 0 && msg.t === "PRESENCE_UPDATE") {
// Output presence data
console.log("presence", msg.d);
}
};
// Clean up on close
ws.onclose = () => { if (heartbeatInterval) clearInterval(heartbeatInterval); };pip install websocket-clientimport json
import threading
import time
from websocket import WebSocketApp
heartbeat_interval = None
def on_message(ws_app, message):
global heartbeat_interval
msg = json.loads(message)
if msg.get("op") == 1:
# Start heartbeats (server provides milliseconds)
heartbeat_interval = msg["d"]["heartbeat_interval"] / 1000.0
def hb():
while True:
time.sleep(heartbeat_interval)
try:
ws_app.send(json.dumps({"op": 3}))
except Exception:
break
threading.Thread(target=hb, daemon=True).start()
# Subscribe to user
ws_app.send(json.dumps({"op": 2, "d": {"subscribe_to_ids": ["1234567890"]}}))
elif msg.get("op") == 0 and msg.get("t") == "PRESENCE_UPDATE":
# Output presence data
print("presence", msg.get("d"))
def on_close(ws_app, close_status_code, close_msg):
# Clean up if needed; heartbeat thread will exit when socket closes
pass
def on_open(ws_app):
pass
ws = WebSocketApp("wss://tether.eggwite.moe/socket",
on_message=on_message,
on_close=on_close,
on_open=on_open)
ws.run_forever()You must send heartbeats at the interval specified in the HELLO message.
Error Codes
| Code | Name | Description |
|---|---|---|
4004 | unknown_opcode | Received an unsupported op. |
4005 | requires_data_object | INITIALIZE message did not include a valid payload. |
4006 | invalid_payload | INITIALIZE message provided no IDs or empty subscriptions. |
The server does not return error messages in response to invalid messages. Instead, it closes the connection with the appropriate close code.
Need Help? / Troubleshooting
If you run into issues or have questions about using the WebSocket gateway:
- Open an Issue:
Visit the contributing page’s issues section for guidance, or open a new issue on GitHub.
Join the Official Tether Discord Server
Get support and connect with the community.
Please provide as much detail as possible (error messages, steps to reproduce, etc.) when asking for help!