Twitter WebSocket API 快速开始

几分钟内连接 TweetStream 的实时 Twitter 流式替代方案

开始使用

大多数客户端都会使用 WebSocket 子协议认证,把 API Key 放进协议列表即可。如果你的运行环境无法这样传递令牌,也可以使用 Bearer 认证头或 apiKey 查询参数。

本快速开始展示如何通过 WebSocket 实时接收 tweet、account 与 control 事件。如果你在评估 Twitter 流式 API 替代方案,从这里开始。

连接详情

端点wss://ws.tweetstream.io/ws
协议 1tweetstream.v1
协议 2tweetstream.auth.token.YOUR_API_KEY

快速开始示例

复制以下代码即可立即连接:

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);
};

生产示例

生产环境请加入重连处理:

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();

封装格式

所有消息都使用一致的封装结构:

  • v - 协议版本(始终为 1)
  • t - 消息族:tweet、account 或 control
  • op - 操作,例如 content、meta、update、profile_update、follow、twitter_handles_result
  • ts - 毫秒级 Unix 时间戳
  • d - 载荷数据(随类型与操作变化)

常见问题

  • 认证失败: 确认 API Key 来自有效订阅
  • 连接数已达上限: 关闭现有连接或升级套餐
  • 没有收到推文: 确认已在控制台跟踪账号

立即开启实时 Twitter WebSocket 提醒

内置 WebSocket 交付、OCR 与代币检测的 Twitter API 替代方案。

开始 7 天试用

起价 $199/月 · Basic/Elite 含 7 天试用 · OCR + 代币检测

相关页面