X/Twitter WebSocket API Reference
Connect one WebSocket and stream monitored-account tweets, profile/follow signals, and enrichment into your bot or alerting stack.
Last updated: May 26, 2026
Build on the monitored-account feed
TweetStream opens one WebSocket for selected X/Twitter accounts. You add the handles that matter, keep the socket open, and receive JSON envelopes as monitored events arrive.
Use this page as the practical API reference: connection details, auth options, event operations, quickstart code, production reconnects, and the message envelope.
Connection Details
| Endpoint | wss://ws.tweetstream.io/ws |
| Protocol 1 | tweetstream.v1 |
| Protocol 2 | tweetstream.auth.token.YOUR_API_KEY |
Event operations
Every frame uses the same envelope. Route on the message family and operation instead of parsing free-form text.
Authentication options
Use subprotocol auth when possible. Bearer-header and apiKey query-parameter auth are available for environments that cannot set WebSocket subprotocols.
Quick Start Example
Copy and paste this code to get connected immediately:
TweetStream only sends content for accounts tracked in your dashboard. Payload author.handle values include a leading @ when present; strip it before comparing against bare usernames such as elonmusk.
const API_KEY = 'YOUR_API_KEY'; // Get your key from the dashboard
const WS_URL = 'wss://ws.tweetstream.io/ws';
const protocols = [
'tweetstream.v1',
`tweetstream.auth.token.${API_KEY}`,
];
const ws = new WebSocket(WS_URL, protocols);
ws.onopen = () => {
console.log('Connected to TweetStream!');
};
ws.onmessage = (event) => {
const envelope = JSON.parse(event.data);
if (envelope.t === 'tweet' && envelope.op === 'content') {
const tweet = envelope.d;
const author = tweet.author?.handle ?? tweet.author?.name ?? 'unknown';
console.log(`[${author}] ${tweet.text}`);
}
};
ws.onclose = () => {
console.log('WebSocket closed');
};
ws.onerror = (error) => {
console.error('WebSocket error', error);
};Production Example
For production use, reconnect after deploys, network drops, or closed sockets. Keep your event handling idempotent because reconnecting can replay recent state.
type VerifiedType = 'blue' | 'business' | 'government' | 'none';
type TweetAuthor = {
id?: string;
// Includes a leading @ when present, for example "@elonmusk".
handle?: string;
name?: string;
platform?: 'twitter' | 'truth_social';
profileImage?: string;
followersCount?: number;
verifiedType?: VerifiedType;
};
type Media = {
url: string;
type?: 'image' | 'video' | 'gif';
thumbnail?: string;
};
type TweetContent = {
tweetId: string;
text: string;
createdAt: number;
author: TweetAuthor;
link?: string;
media?: Media[];
ref?: {
type: 'reply' | 'quote' | 'retweet';
tweetId?: string;
text?: string;
author?: TweetAuthor;
media?: Media[];
};
};
type MetaSource = 'text' | 'ocr';
type DetectedCexMarket = {
exchange: 'bybit' | 'binance' | 'hyperliquid';
symbol?: string;
priceUsd?: number;
url?: string;
baseAsset?: string;
quoteAsset?: string;
sources: MetaSource[];
};
type DetectedPredictionMarket = {
exchange: 'polymarket' | 'kalshi';
marketId?: string;
title?: string;
priceUsd?: number;
url?: string;
sources: MetaSource[];
};
type DetectedToken = {
symbol?: string;
name?: string;
contract?: string;
chain?: string;
networkId?: number;
priceUsd?: number;
sources: MetaSource[];
};
type TweetMeta = {
tweetId: string;
ocr?: {
text: string;
};
detected?: {
tokens?: Array<{
symbol?: string;
name?: string;
contract?: string;
chain?: string;
networkId?: number;
priceUsd?: number;
sources: Array<'text' | 'ocr'>;
}>;
cex?: Array<{
exchange: 'bybit' | 'binance' | 'hyperliquid';
symbol?: string;
priceUsd?: number;
url?: string;
baseAsset?: string;
quoteAsset?: string;
sources: Array<'text' | 'ocr'>;
}>;
prediction?: Array<{
exchange: 'polymarket' | 'kalshi';
marketId?: string;
title?: string;
priceUsd?: number;
url?: string;
sources: Array<'text' | 'ocr'>;
}>;
};
};
type Media = {
url: string;
type?: 'image' | 'video' | 'gif';
thumbnail?: string;
};
type TweetUpdate = {
tweetId: string;
text?: string;
media?: Media[];
ref?: TweetContent['ref'];
};
type TweetDeleteEvent = {
tweetId: string;
eventId: string;
deletedAt?: number;
author?: TweetAuthor;
text?: string;
};
type TweetPinEvent = {
tweetId: string;
eventId: string;
observedAt: number;
action: 'pin' | 'unpin';
author: TweetAuthor;
text?: string;
tweet?: TweetContent;
};
type AccountEventActor = TweetAuthor & {
banner?: string;
bio?: string;
location?: string;
url?: string;
websiteUrl?: string;
followingCount?: number;
};
type ProfileUpdateEvent = {
kind: 'PROFILE';
eventId: string;
observedAt: number;
actor: AccountEventActor;
changes: {
avatar?: string;
banner?: string;
bio?: string;
handle?: string;
location?: string;
name?: string;
};
previous?: {
avatar?: string;
banner?: string;
bio?: string;
handle?: string;
location?: string;
name?: string;
};
};
type FollowEvent = {
kind: 'FOLLOW' | 'UNFOLLOW';
eventId: string;
observedAt: number;
actor: AccountEventActor;
target: AccountEventActor;
};
type Envelope<T extends object> = {
v: 1;
t: 'tweet' | 'account' | 'control';
op:
| 'content'
| 'meta'
| 'update'
| 'delete'
| 'pin'
| 'unpin'
| 'profile_update'
| 'follow'
| 'unfollow'
| 'auth_ping'
| 'auth_pong'
| 'twitter_handles_result';
id?: string;
ts: number;
d: T;
};
type TwitterHandlesResult = {
action: 'follow' | 'unfollow';
requestId: string | null;
results: Array<{
input: string;
handle?: string;
normalizedHandle?: string;
twitterId?: string;
state: string;
message?: string;
}>;
error: string | null;
};
const API_KEY = process.env.TWEETSTREAM_API_KEY ?? 'YOUR_API_KEY';
const WS_URL = 'wss://ws.tweetstream.io/ws';
const PROTOCOLS = ['tweetstream.v1', `tweetstream.auth.token.${API_KEY}`];
let ws: WebSocket | null = null;
let reconnectTimer: ReturnType<typeof setTimeout> | undefined;
function connect() {
ws = new WebSocket(WS_URL, PROTOCOLS);
ws.onopen = () => {
console.log('Connected to TweetStream');
};
ws.onmessage = (event) => {
const envelope = JSON.parse(event.data) as Envelope<Record<string, unknown>>;
if (envelope.t === 'tweet') {
if (envelope.op === 'content') {
const tweet = envelope.d as TweetContent;
const author = tweet.author?.handle ?? tweet.author?.name ?? 'unknown';
const platform = tweet.author?.platform ?? 'twitter';
console.log(`[CONTENT] ${author} (${platform}): ${tweet.text}`);
} else if (envelope.op === 'update') {
const update = envelope.d as TweetUpdate;
console.log(`[UPDATE] ${update.tweetId}`, update);
} else if (envelope.op === 'meta') {
const meta = envelope.d as TweetMeta;
console.log(`[META] ${meta.tweetId}`, meta);
} else if (envelope.op === 'delete') {
const deleted = envelope.d as TweetDeleteEvent;
console.log(`[DELETE] ${deleted.tweetId}`);
} else if (envelope.op === 'pin' || envelope.op === 'unpin') {
const pinned = envelope.d as TweetPinEvent;
console.log(`[${pinned.action.toUpperCase()}] ${pinned.tweetId}`);
}
return;
}
if (envelope.t === 'account') {
if (envelope.op === 'profile_update') {
const profile = envelope.d as ProfileUpdateEvent;
const actor = profile.actor.handle ?? profile.actor.name ?? 'unknown';
console.log(`[PROFILE] ${actor}`, profile.changes);
} else if (envelope.op === 'follow' || envelope.op === 'unfollow') {
const follow = envelope.d as FollowEvent;
const actor = follow.actor.handle ?? follow.actor.name ?? 'unknown';
const target = follow.target.handle ?? follow.target.name ?? 'unknown';
console.log(`[${follow.kind}] ${actor} -> ${target}`);
}
return;
}
if (envelope.t === 'control' && envelope.op === 'twitter_handles_result') {
const payload = envelope.d as TwitterHandlesResult;
console.log('[HANDLES RESULT]', payload);
}
};
ws.onclose = (event) => {
console.warn('WebSocket closed', event.code, event.reason);
scheduleReconnect();
};
ws.onerror = (error) => {
console.error('WebSocket error', error);
ws?.close();
};
}
function scheduleReconnect() {
if (reconnectTimer) return;
reconnectTimer = setTimeout(() => {
reconnectTimer = undefined;
connect();
}, 5_000);
}
connect();Managing watched accounts
TweetStream is built around selected-account monitoring. Keep the watchlist explicit so your stream stays small enough for a trading bot or alert router to process quickly.
- Add or remove tracked handles from the dashboard or supported account-management APIs.
- Handle-management commands return control messages so your UI or worker can confirm what changed.
- The stream only emits events for accounts and filters your workspace is configured to watch.
Envelope Format
All messages use a consistent envelope structure:
v- Protocol version (always 1)t- Message family: tweet, account, or controlop- Operation such as content, meta, update, delete, pin, unpin, profile_update, follow, unfollow, or twitter_handles_resultts- Unix timestamp in millisecondsd- Payload data (varies by type and operation)
Common Issues
- Authentication failed: Verify your API key is from an active subscription
- Connection limit reached: Close existing connections or upgrade your plan
- No tweets appearing: Check that accounts are tracked in your dashboard, inspect raw messages before filtering, and strip the leading @ from author.handle if you compare against bare usernames
Start real-time Twitter WebSocket alerts today
WebSocket delivery, OCR, and token detection - no infrastructure to build.
Start 3-Day TrialFrom $199/mo · Basic/Elite 3-day trial · OCR + token detection included
