Skip to docs content

Connection, authentication, and heartbeat

Use the documented regional endpoint, authenticate during the upgrade, and leave native ping and pong handling enabled.

Run the 5-minute quickstart
Open in chat
Reviewed against product code on September 2, 2026

Choose a base URL

SurfaceURLUse
WebSocket (USA)wss://ws-iad.tweetstream.io/wsUse for connections in the USA
WebSocket (global)wss://ws-global.tweetstream.io/wsUse for connections outside the USA
RESThttps://api.tweetstream.ioBase origin for /api/history, /api/me, and account management
Dashboardhttps://tweetstream.io/dashboardAPI key, watchlists, billing, and Discord routing

Authenticate requests

Authenticate realtime clients with WebSocket subprotocols and REST requests with a bearer token. Keep API keys on the server, never in public browser code.

ContextHeader or protocolNotes
WebSockettweetstream.v1 + tweetstream.auth.token.<API_KEY>Preferred realtime authentication
WebSocket fallbackAuthorization: Bearer <API_KEY> or ?apiKey=<API_KEY>Prefer Authorization; use query authentication only when the runtime cannot set headers
RESTAuthorization: Bearer <API_KEY>History accepts a standard key for Pro or Scale. With active or trialing Ultra, AFFILIATE accepts the standard or Ultra key that owns the account. Account management and /api/me accept either key

Authenticate a server connection

typescript
const socket = new WebSocket("wss://ws-global.tweetstream.io/ws", [
  "tweetstream.v1",
  `tweetstream.auth.token.${process.env.TWEETSTREAM_API_KEY}`,
]);
 
socket.addEventListener("message", (event) => {
  const message = JSON.parse(event.data);
  console.log(message.t, message.op, message.d);
});

Connect

Use wss://ws-iad.tweetstream.io/ws in the USA and wss://ws-global.tweetstream.io/ws elsewhere. The server responds with tweetstream.v1; it does not echo the authentication-token protocol. The example reconnects with exponential backoff. Short sessions keep increasing the delay; 30 seconds of uptime resets it.

Connect with Node.js

typescript
import WebSocket from "ws";
 
type StreamEvent = {
  t?: string;
  op?: string;
  d?: {
    author?: { handle?: string };
    detected?: unknown;
    text?: string;
  };
};
 
const apiKey = process.env.TWEETSTREAM_API_KEY;
if (!apiKey) {
  throw new Error("Missing TWEETSTREAM_API_KEY");
}
 
let retry = 0;
let healthyTimer: ReturnType<typeof setTimeout> | undefined;
let reconnectTimer: ReturnType<typeof setTimeout> | undefined;
 
function scheduleReconnect(reason: string) {
  if (reconnectTimer) return;
 
  const delayMs = Math.min(30_000, 1_000 * 2 ** retry) + Math.floor(Math.random() * 500);
  retry += 1;
  console.warn(`Reconnecting in ${delayMs}ms: ${reason}`);
  reconnectTimer = setTimeout(() => {
    reconnectTimer = undefined;
    connect();
  }, delayMs);
}
 
function connect() {
  const ws = new WebSocket("wss://ws-global.tweetstream.io/ws", [
    "tweetstream.v1",
    `tweetstream.auth.token.${apiKey}`,
  ]);
 
  ws.on("open", () => {
    if (reconnectTimer) clearTimeout(reconnectTimer);
    reconnectTimer = undefined;
    healthyTimer = setTimeout(() => {
      retry = 0;
      healthyTimer = undefined;
    }, 30_000);
    console.log("TweetStream connected");
  });
 
  ws.on("message", (raw) => {
    const event = JSON.parse(raw.toString()) as StreamEvent;
 
    if (event.t === "tweet" && event.op === "content") {
      const tweet = event.d;
      console.log(tweet?.author?.handle, tweet?.text);
    }
 
    if (event.t === "tweet" && event.op === "meta") {
      console.log("enrichment", event.d?.detected);
    }
  });
 
  ws.on("close", (code, reason) => {
    if (healthyTimer) clearTimeout(healthyTimer);
    healthyTimer = undefined;
    scheduleReconnect(`close ${code}: ${reason.toString()}`);
  });
 
  ws.on("unexpected-response", (_request, response) => {
    console.error("Connection rejected", response.statusCode, response.statusMessage);
    response.resume();
    if (response.statusCode === 429 || response.statusCode === 503) {
      scheduleReconnect(`HTTP ${response.statusCode}`);
      return;
    }
    process.exitCode = 1;
  });
 
  ws.on("error", (error) => {
    console.error("WebSocket error", error.message);
  });
}
 
connect();

Connect with Python

Use any WebSocket runtime that can send both subprotocols. Clean closes and transport errors reconnect with exponential backoff. Short sessions keep increasing the delay; 30 seconds of uptime resets it.

Python reconnecting consumer

python
import asyncio
import json
import os
import websockets
 
API_KEY = os.environ["TWEETSTREAM_API_KEY"]
URI = "wss://ws-global.tweetstream.io/ws"
PROTOCOLS = ["tweetstream.v1", f"tweetstream.auth.token.{API_KEY}"]
 
async def main():
    loop = asyncio.get_running_loop()
    retry = 0
    while True:
        connected_at = None
        reason = "connection closed"
        try:
            async with websockets.connect(URI, subprotocols=PROTOCOLS) as ws:
                connected_at = loop.time()
                async for raw in ws:
                    event = json.loads(raw)
                    if event["t"] == "tweet" and event["op"] == "content":
                        tweet = event["d"]
                        print(tweet.get("author", {}).get("handle"), tweet.get("text"))
        except Exception as error:
            reason = str(error)
 
        if connected_at is not None and loop.time() - connected_at >= 30:
            retry = 0
        wait = min(30, 2 ** retry)
        retry = min(retry + 1, 5)
        print(f"reconnecting in {wait}s after {reason}")
        await asyncio.sleep(wait)
 
asyncio.run(main())

Handle heartbeats and disconnects

TweetStream sends a native WebSocket ping every 30 seconds. Standard Node and Python clients reply with pong automatically. The server closes an unresponsive socket. Reconnect, then use History API to backfill stored content, profile, follow, and affiliate events.

Handle limits and retries

Your plan sets active WebSocket, monitored-account, and History API limits. On 429, pause that workflow and wait for retryAfterSeconds when present. Otherwise, use your normal backoff.

SurfaceLimit signalRecommended handling
WebSocket429 during upgradeClose unused sockets or move to a plan with more connections
History APIretryAfterSeconds when rate limitedWait before replaying the next window
Tracked accountsPlan usage from /api/meCheck count and limit before batch add flows