Tweet Alerts WebSocket: live tweets from tracked accounts
The WebSocket endpoint provides a persistent, real-time stream of tweet alerts. Connect once, subscribe to any of your tracked accounts, and receive structured JSON messages as tweets happen. This is ideal for trading bots, dashboards, and any application that needs the lowest possible latency.
Connection
Connect with your API key as a query parameter:
wss://api.xanguard.tech/v1/ws?api_key=xg_your_key
Client to Server Messages
After connecting, send JSON messages to subscribe or unsubscribe from handles. Only accounts already on your tracked list can be subscribed (add them first with POST /v1/accounts; send handles without @). Other handles are skipped with an error message, and the ack lists what was subscribed.
{
"type": "subscribe",
"handles": ["elonmusk", "VitalikButerin"]
}
{
"type": "unsubscribe",
"handles": ["elonmusk"]
}
Server to Client Messages
Tweet Alert
Received when a tracked account posts a tweet:
{
"type": "alert",
"tweet_id": "1947000000000000000",
"handle": "elonmusk",
"text": "Just bought more $BTC",
"url": "https://x.com/elonmusk/status/1947000000000000000",
"image_url": null,
"image_urls": [],
"is_reply": false,
"is_quote": false,
"received_at": 1784646000120,
"created_at": 1784645999800,
"original_tweet_id": null,
"possibly_sensitive": false,
"mentions": []
}
received_at is when Xanguard detected the tweet; created_at is when Twitter says it was posted — the difference is your true detection latency. Quotes include an embedded quoted_tweet object (tweet_id, text, author_handle, author_name, author_avatar, image_urls); replies include in_reply_to_user / in_reply_to_tweet_id refs plus an embedded quoted_tweet carrying the parent tweet (same fields) — label by is_reply vs is_quote, not by quoted_tweet presence. Reply/quote context is included inline before delivery. All timestamps are epoch milliseconds.
Delivery rules: only tweets posted less than 60 seconds earlier, and detected after you subscribed, are sent; there is no replay after a reconnect. Each connection holds only the newest unsent alert, so a client that reads slowly can miss alerts during a burst. Telegram settings, keywords and mutes do not apply here: every tweet of a subscribed handle is sent.
Acknowledgement
Sent after a successful subscribe or unsubscribe action:
{
"type": "ack",
"action": "subscribe",
"handles": ["elonmusk"],
"active": 1,
"max": 25
}
Error
Sent when a request cannot be fulfilled:
{
"type": "error",
"message": "Would exceed handle limit: 10 + 5 new = 15, max 10"
}
Limits
| Limit | Value |
|---|---|
| Connections | Per account, across all your keys: Starter 2, Growth 3, Pro 5, Business 10, Scale 10, Enterprise 25, Ultra 50 (B2B dt_ keys 5). Excess attempts get a plain-text HTTP 429. A bad key or free plan gets a plain-text HTTP 401 "Invalid or expired API key". |
| Inbound messages | 10 messages/sec per connection — the 11th frame in any 1-second window closes the connection (pong frames count) |
| App-level ping | Send {"type":"ping"} to receive {"type":"pong"} (optional liveness check) |
Keepalive
The server sends a WebSocket ping frame every 30 seconds. Most WebSocket libraries answer automatically. If pongs go missing for ~3 minutes (6 consecutive pings), the server closes the connection. Your client should implement automatic reconnection with backoff.
Example: JavaScript Client
const WS_URL = 'wss://api.xanguard.tech/v1/ws?api_key=xg_your_key';
let backoff = 1000; // ms, doubles on each failure, capped at 30s
function connect() {
const ws = new WebSocket(WS_URL);
ws.onopen = () => {
backoff = 1000;
// Subscribe to handles once connected (re-sent on every reconnect)
ws.send(JSON.stringify({
type: 'subscribe',
handles: ['elonmusk', 'VitalikButerin']
}));
};
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
if (msg.type === 'alert') {
console.log(`Tweet from @${msg.handle}: ${msg.text}`);
console.log(`Link: ${msg.url}`);
} else if (msg.type === 'ack') {
console.log(`Watching ${msg.active}/${msg.max} handles`);
} else if (msg.type === 'error') {
console.error(`Error: ${msg.message}`);
}
};
ws.onerror = () => ws.close();
ws.onclose = () => {
const delay = backoff + Math.random() * 500;
console.log(`Disconnected, reconnecting in ${Math.round(delay)} ms`);
backoff = Math.min(backoff * 2, 30000);
setTimeout(connect, delay);
};
}
connect();
Example: Python Client
import asyncio, json, random
import websockets # pip install websockets
URI = "wss://api.xanguard.tech/v1/ws?api_key=xg_your_key"
HANDLES = ["elonmusk", "VitalikButerin"]
async def run_once():
# The websockets library answers the server's ping frames automatically
async with websockets.connect(URI) as ws:
await ws.send(json.dumps({"type": "subscribe", "handles": HANDLES}))
async for message in ws:
msg = json.loads(message)
if msg["type"] == "alert":
print(f"@{msg['handle']}: {msg['text']}")
print(f"Link: {msg['url']}")
elif msg["type"] == "error":
print("Error:", msg.get("message"))
async def main():
backoff = 1
while True:
try:
await run_once()
backoff = 1 # clean close: reconnect quickly
except websockets.exceptions.InvalidHandshake as e:
status = getattr(getattr(e, "response", None), "status_code", None)
if status in (401, 403):
print(f"Rejected ({status}): check your API key and plan.")
return
print(f"Handshake failed: {e}")
except (websockets.ConnectionClosed, OSError) as e:
print(f"Disconnected: {e}")
await asyncio.sleep(backoff + random.random())
backoff = min(backoff * 2, 30)
asyncio.run(main())