Twitter WebSocket API Quickstart

Connect to TweetStream's real-time Twitter streaming API alternative in minutes

Getting Started

Most clients authenticate via WebSocket subprotocols by including the API key in the protocol list. If your environment cannot set that token, Bearer auth headers and apiKey query parameters are also accepted.

Stream tweet, account, and control events in real time via WebSocket. Evaluating Twitter API alternatives? Start here.

Connection Details

Endpointwss://ws.tweetstream.io/ws
Protocol 1tweetstream.v1
Protocol 2tweetstream.auth.token.YOUR_API_KEY

Quick Start Example

Copy and paste this code to get connected immediately:

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, add reconnection handling:

type VerifiedType = 'blue' | 'business' | 'government' | 'none';

type TweetAuthor = {
  id?: string;
  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 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';
  eventId: string;
  observedAt: number;
  actor: AccountEventActor;
  target: AccountEventActor;
};

type Envelope<T extends object> = {
  v: 1;
  t: 'tweet' | 'account' | 'control';
  op:
    | 'content'
    | 'meta'
    | 'update'
    | 'delete'
    | 'profile_update'
    | 'follow'
    | '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;
    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);
      }
      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') {
        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] ${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();

Envelope Format

All messages use a consistent envelope structure:

  • v - Protocol version (always 1)
  • t - Message family: tweet, account, or control
  • op - Operation such as content, meta, update, profile_update, follow, or twitter_handles_result
  • ts - Unix timestamp in milliseconds
  • d - 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

Start real-time Twitter WebSocket alerts today

WebSocket delivery, OCR, and token detection - no infrastructure to build.

Start 7-Day Trial

From $199/mo · Basic/Elite 7-day trial · OCR + token detection included

Related Pages