跳到文档正文

消息信封、交付、恢复与错误

按类别和操作分发 v1 消息信封,容忍重复,并在支持回放时使用已存 History。

打开 AsyncAPI 规范
在聊天中打开
按产品代码校对:2026年9月24日

分发消息信封

每个实时消息信封先按 t 分发,再按 op 分发。处理程序必须幂等,因为历史回放可能返回已实时处理的事件。

消息信封基础类型

typescript
type EnvelopeBase<
  TFamily extends 'tweet' | 'account' | 'charity' | 'control' | 'news',
  TOp extends string,
  TPayload extends object,
> = {
  v: 1;
  t: TFamily;
  op: TOp;
  id?: string;
  ts: number;
  d: TPayload;
};

分发操作

TweetStream 发送 18 种服务端操作。先按 t、再按 op 分发每个消息信封;遇到未知操作时忽略该消息,不要关闭连接。客户端发送两种命令:auth_ping 用于认证检查,twitter_handles 用于在 handle-management 连接上添加或移除账号。

类别操作含义
tweetcontent帖子内容
tweetmeta帖子富化
tweetupdate帖子更新
tweetdelete帖子删除
tweetpin帖子置顶
tweetunpin帖子取消置顶
accountprofile_update资料更新
accountfollow关注账号
accountunfollow取消关注账号
accountaffiliate_update关联账号变更
accountcallout代币喊单
accountcallout_update喊单富化
accountthesisFOMO 观点
accounttoken_verified观察到 FOMO 代币认证
charityadded新增慈善项目
newsarticle新闻文章
controlauth_pong认证 pong
controltwitter_handles_result账号管理命令结果

按事件流处理交付

消息信封没有确认、恢复 token 或序列号。不要根据 ts 或 id 推断顺序或恰好一次交付。

处理程序必须幂等,并保留足够的本地状态,以识别应用已经处理的事件。

重连后回补

固定 startDate 和 endDate,在其他筛选条件不变的情况下持续使用 nextCursor,直到返回 null。以幂等方式处理每行。如果中断后的回放需要继续而不是重启,请一起保存时间范围和 cursor。

History 返回已存事件,不保证完整性或保留期限。AFFILIATE 返回已存的关联账号变更,不是当前列表快照。delete、pin 和 unpin 仍只在实时流中发送。

回放恢复窗口

typescript
type HistoryRow = {
  time: string;
  tweetId: string;
};
 
type HistoryPage = {
  data: Array<HistoryRow>;
  metadata: { nextCursor: string | null };
};
 
async function replayAfterReconnect(
  checkpoint: string,
  processEvent: (event: HistoryRow) => Promise<void>,
  saveCheckpoint: (eventTime: string) => Promise<void>,
) {
  const apiKey = process.env.TWEETSTREAM_API_KEY;
  if (!apiKey) throw new Error("Missing TWEETSTREAM_API_KEY");
 
  const url = new URL("https://api.tweetstream.io/api/history");
  const replayEnd = new Date().toISOString();
  url.searchParams.set("handles", "marketdesk");
  url.searchParams.set("startDate", checkpoint);
  url.searchParams.set("endDate", replayEnd);
  url.searchParams.set("limit", "1000");
  url.searchParams.set("type", "TWEET");
 
  const pages: Array<Array<HistoryRow>> = [];
  let cursor: string | null = null;
  do {
    if (cursor) url.searchParams.set("cursor", cursor);
    const response = await fetch(url, {
      headers: { Authorization: `Bearer ${apiKey}` },
    });
    if (!response.ok) {
      const detail = await response.text();
      throw new Error(`Replay failed (${response.status}): ${detail}`);
    }
 
    const page = (await response.json()) as HistoryPage;
    pages.push(page.data);
    cursor = page.metadata.nextCursor;
  } while (cursor);
 
  for (const event of pages.reverse().flatMap((page) => [...page].reverse())) {
    await processEvent(event);
  }
  await saveCheckpoint(replayEnd);
}

修复握手错误

WebSocket 连接升级失败时,会在连接被接受前返回 JSON。客户端可以安全记录该响应。

状态可能原因修复方式
400WebSocket 协议 header 缺失或无效发送 tweetstream.v1 和有效认证 token 协议
401API 密钥缺失、为空或无效在控制台重新生成或轮换 API 密钥
403订阅状态不允许实时流开始试用、续费或升级账号
429活跃 WebSocket 连接数达到限制关闭旧连接或升级套餐
503连接限制服务暂时不可用使用退避策略重试
{
  "error": "WebSocket connection limit reached (3/3 active). Close an existing connection and retry.",
  "status": 429
}