X/Twitter WebSocket API 参考

打开一个 WebSocket,把被监控账号的推文、资料/关注信号与丰富化结果流式送入机器人或提醒系统。

最后更新:2026 年 5 月 26 日

基于被监控账号 feed 构建

TweetStream 为选定的 X/Twitter 账号打开一个 WebSocket。你添加重要账号,保持 socket 连接,然后在事件到达时接收 JSON 包裹。

把本页当作实用 API 参考:连接详情、认证方式、事件操作、快速开始代码、生产重连与消息包裹。

连接详情

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

事件操作

每个帧都使用同一套包裹结构。根据消息族和操作路由,而不是解析自由文本。

消息族操作用途
tweetcontent来自被监控账号的初始推文载荷,包含 kind:post、reply、quote 或 retweet。
tweetmetaOCR、代币检测、市场与实时价格上下文等丰富化。
tweetupdate引用上下文或媒体更新等后续推文字段。
tweetdeleteTweetStream 观察到被监控推文被删除时发送。
tweetpin / unpin观察到被监控推文的置顶状态变化时发送。
accountprofile_update被监控账号的资料变更。
accountfollow被监控账号及其目标账号的关注事件。
accountunfollow可用时,被监控账号及其目标账号的取消关注事件。
controltwitter_handles_result通过 WebSocket 发送 follow 或 unfollow 命令后的结果。

认证方式

尽量使用子协议认证。对于无法设置 WebSocket 子协议的环境,也支持 Bearer 请求头和 apiKey 查询参数认证。

方式示例适用场景
子协议tweetstream.auth.token.YOUR_API_KEY大多数 Node.js、浏览器和 WebSocket 客户端的首选方式。
Bearer 请求头Authorization: Bearer YOUR_API_KEY适用于支持自定义 WebSocket 请求头的服务器运行时。
查询参数?apiKey=YOUR_API_KEY无法设置请求头或子协议的客户端兜底方案。

快速开始示例

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

TweetStream 只会发送你已在控制台跟踪账号的内容。载荷里的 author.handle 在有值时会包含开头的 @;如果要和 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(`[${tweet.kind}] [${author}] ${tweet.text}`);
  }
};

ws.onclose = () => {
  console.log('WebSocket closed');
};

ws.onerror = (error) => {
  console.error('WebSocket error', error);
};

生产示例

生产环境需要在部署、网络波动或 socket 关闭后自动重连。事件处理保持幂等,因为重连时可能会再次看到近期状态。

type VerifiedType = 'blue' | 'business' | 'government' | 'none';
type TweetVerifiedLabel = {
  badge: string | null;
  description: string;
  url: string | null;
};

type TweetAuthor = {
  banner?: string;
  bio?: string;
  followersCount?: number;
  followingCount?: number;
  id?: string;
  joinedAt?: number;
  location?: string;
  metrics?: {
    likes?: number;
    tweets?: number;
  };
  // Includes a leading @ when present, for example "@elonmusk".
  handle?: string;
  name?: string;
  platform?: 'twitter' | 'truth_social';
  profileImage?: string;
  url?: string;
  verifiedLabel?: TweetVerifiedLabel;
  verifiedType?: VerifiedType;
};

type Media = {
  url: string;
  type?: 'image' | 'video' | 'gif';
  thumbnail?: string;
};

type TweetUrl = {
  url: string;
  name?: string;
  tco?: string;
};

type TweetMention = {
  handle?: string;
  id?: string;
  name?: string;
};

type TweetContentKind = 'post' | 'reply' | 'quote' | 'retweet';

type TweetContent = {
  tweetId: string;
  kind: TweetContentKind;
  text: string;
  createdAt: number;
  author: TweetAuthor;
  link?: string;
  media?: Media[];
  mentions?: TweetMention[];
  receivedAt?: number;
  urls?: TweetUrl[];
  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 TweetUpdate = {
  tweetId: string;
  kind?: TweetContentKind;
  text?: string;
  author?: TweetAuthor;
  media?: Media[];
  mentions?: TweetMention[];
  receivedAt?: number;
  urls?: TweetUrl[];
  ref?: TweetContent['ref'];
};

type TweetDeleteEvent = {
  tweetId: string;
  eventId: string;
  deletedAt?: number;
  receivedAt?: number;
  author?: TweetAuthor;
  text?: string;
};

type TweetPinEvent = {
  tweetId: string;
  eventId: string;
  observedAt: number;
  receivedAt?: number;
  action: 'pin' | 'unpin';
  author: TweetAuthor;
  text?: string;
  tweet?: TweetContent;
};

type AccountEventActor = TweetAuthor & {
  websiteUrl?: string;
};

type ProfileUpdateEvent = {
  kind: 'PROFILE';
  eventId: string;
  observedAt: number;
  receivedAt?: number;
  actor: AccountEventActor;
  changes: {
    avatar?: string;
    banner?: string;
    bio?: string;
    handle?: string;
    location?: string;
    name?: string;
    verifiedLabel?: TweetVerifiedLabel | null;
    websiteUrl?: string | null;
  };
  previous?: {
    avatar?: string;
    banner?: string;
    bio?: string;
    handle?: string;
    location?: string;
    name?: string;
    verifiedLabel?: TweetVerifiedLabel | null;
    websiteUrl?: string | null;
  };
};

type FollowEvent = {
  kind: 'FOLLOW' | 'UNFOLLOW';
  eventId: string;
  observedAt: number;
  receivedAt?: 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;
    name?: string;
    normalizedHandle?: string;
    profileImage?: string;
    twitterId?: string;
    state:
      | 'added'
      | 'already_following'
      | 'invalid_input'
      | 'duplicate'
      | 'not_found'
      | 'failed'
      | 'removed'
      | 'not_following';
    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:${tweet.kind}] ${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();

管理被监控账号

TweetStream 围绕选定账号监控构建。明确维护 watchlist,可以让 feed 足够小,便于交易机器人或提醒路由快速处理。

  • 可从控制台或支持的账号管理 API 添加、移除跟踪账号。
  • 账号管理命令会返回 control 消息,方便 UI 或 worker 确认变更结果。
  • stream 只会发送当前 workspace 配置为监控的账号和过滤器相关事件。

封装格式

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

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

常见问题

  • 认证失败: 确认 API Key 来自有效订阅
  • 连接数已达上限: 关闭现有连接或升级套餐
  • 没有收到推文: 确认已在控制台跟踪账号;先打印原始消息再过滤;如果用裸用户名比较,请去掉 author.handle 开头的 @

立即开启实时 Twitter WebSocket 提醒

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

开始 TweetStream 试用

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

相关页面