---
title: "连接、认证与心跳"
canonical_url: "https://tweetstream.io/zh-cn/docs/connection-auth-heartbeat"
last_updated: 2026-09-24
---

# 连接、认证与心跳

使用文档列出的区域端点，在升级连接时认证，并保留原生 ping 和 pong 处理。

Canonical URL: https://tweetstream.io/zh-cn/docs/connection-auth-heartbeat

## 选择基础 URL

| 接口 | URL | 用途 |
| --- | --- | --- |
| WebSocket（美国） | `wss://ws-iad.tweetstream.io/ws` | 用于美国境内的连接 |
| WebSocket（全球） | `wss://ws-global.tweetstream.io/ws` | 用于美国以外的连接 |
| REST | `https://api.tweetstream.io` | `/api/history`、`/api/me` 和账号管理的基础域名 |
| 控制台 | `https://tweetstream.io/dashboard` | API 密钥、监控列表、账单和 Discord 路由 |

## 认证请求

实时客户端使用 WebSocket 子协议认证，REST 请求使用 Bearer token。API 密钥只能保存在服务端，绝不能放入公开的浏览器代码。

| 上下文 | Header 或协议 | 说明 |
| --- | --- | --- |
| WebSocket | `tweetstream.v1` + `tweetstream.auth.token.<API_KEY>` | 首选的实时认证方式 |
| WebSocket 备用方式 | `Authorization: Bearer <API_KEY>` 或 `?apiKey=<API_KEY>` | 优先使用 Authorization；只有运行时无法设置 Header 时才使用查询参数认证 |
| REST | `Authorization: Bearer <API_KEY>` | Pro 或 Scale 的 History 请求使用标准密钥。有效基础套餐叠加当前 Ultra 时，标准密钥可请求完整 History；当前 Ultra 密钥可请求指定账号的完整 History。账号管理和 `/api/me` 可使用任一密钥 |

### 认证服务端连接

```typescript
const socket = new WebSocket("wss://ws-global.tweetstream.io/ws", [
  "tweetstream.v1",
  `tweetstream.auth.token.${process.env.TWEETSTREAM_API_KEY}`,
]);

socket.addEventListener("message", (event) => {
  const message = JSON.parse(event.data);
  console.log(message.t, message.op, message.d);
});
```

## 使用 Node.js 连接

美国境内使用 `wss://ws-iad.tweetstream.io/ws`，其他地区使用 `wss://ws-global.tweetstream.io/ws`。服务端返回 `tweetstream.v1`，不会回显携带认证 token 的协议。示例通过指数退避重连。短连接会继续增加等待时间，稳定连接 30 秒后重置。

### Node.js 重连客户端

```typescript
import WebSocket from "ws";

type StreamEvent = {
  t?: string;
  op?: string;
  d?: {
    author?: { handle?: string };
    detected?: unknown;
    text?: string;
  };
};

const apiKey = process.env.TWEETSTREAM_API_KEY;
if (!apiKey) {
  throw new Error("Missing TWEETSTREAM_API_KEY");
}

let retry = 0;
let healthyTimer: ReturnType<typeof setTimeout> | undefined;
let reconnectTimer: ReturnType<typeof setTimeout> | undefined;

function scheduleReconnect(reason: string) {
  if (reconnectTimer) return;

  const backoffMs = Math.min(30_000, 1_000 * 2 ** retry);
  const jitterMs = Math.floor(Math.random() * 500);
  const delayMs = backoffMs + jitterMs;
  retry += 1;
  console.warn(`Reconnecting in ${delayMs}ms: ${reason}`);
  reconnectTimer = setTimeout(() => {
    reconnectTimer = undefined;
    connect();
  }, delayMs);
}

function connect() {
  const ws = new WebSocket("wss://ws-global.tweetstream.io/ws", [
    "tweetstream.v1",
    `tweetstream.auth.token.${apiKey}`,
  ]);

  ws.on("open", () => {
    if (reconnectTimer) clearTimeout(reconnectTimer);
    reconnectTimer = undefined;
    healthyTimer = setTimeout(() => {
      retry = 0;
      healthyTimer = undefined;
    }, 30_000);
    console.log("TweetStream connected");
  });

  ws.on("message", (raw) => {
    const event = JSON.parse(raw.toString()) as StreamEvent;

    if (event.t === "tweet" && event.op === "content") {
      const tweet = event.d;
      console.log(tweet?.author?.handle, tweet?.text);
    }

    if (event.t === "tweet" && event.op === "meta") {
      console.log("enrichment", event.d?.detected);
    }
  });

  ws.on("close", (code, reason) => {
    if (healthyTimer) clearTimeout(healthyTimer);
    healthyTimer = undefined;
    scheduleReconnect(`close ${code}: ${reason.toString()}`);
  });

  ws.on("unexpected-response", (_request, response) => {
    console.error(
      "Connection rejected",
      response.statusCode,
      response.statusMessage,
    );
    response.resume();
    if (response.statusCode === 429 || response.statusCode === 503) {
      scheduleReconnect(`HTTP ${response.statusCode}`);
      return;
    }
    process.exitCode = 1;
  });

  ws.on("error", (error) => {
    console.error("WebSocket error", error.message);
  });
}

connect();
```

## 使用 Python 连接

使用任何能发送这两个子协议的 WebSocket 运行时。正常关闭和传输错误都会通过指数退避重连。短连接会继续增加等待时间，稳定连接 30 秒后重置。

### Python 重连客户端

```python
import asyncio
import json
import os
import websockets

API_KEY = os.environ["TWEETSTREAM_API_KEY"]
URI = "wss://ws-global.tweetstream.io/ws"
PROTOCOLS = ["tweetstream.v1", f"tweetstream.auth.token.{API_KEY}"]

async def main():
    loop = asyncio.get_running_loop()
    retry = 0
    while True:
        connected_at = None
        reason = "connection closed"
        try:
            async with websockets.connect(URI, subprotocols=PROTOCOLS) as ws:
                connected_at = loop.time()
                async for raw in ws:
                    event = json.loads(raw)
                    if event["t"] == "tweet" and event["op"] == "content":
                        tweet = event["d"]
                        handle = tweet.get("author", {}).get("handle")
                        print(handle, tweet.get("text"))
        except Exception as error:
            reason = str(error)

        if connected_at is not None and loop.time() - connected_at >= 30:
            retry = 0
        wait = min(30, 2 ** retry)
        retry = min(retry + 1, 5)
        print(f"reconnecting in {wait}s after {reason}")
        await asyncio.sleep(wait)

asyncio.run(main())
```

## 处理心跳和断开

TweetStream 每 30 秒发送一次原生 WebSocket `ping`。标准 Node.js 和 Python 客户端会自动回复 `pong`。服务端会关闭无响应的连接。重连后，使用 History API 回补已存内容、资料、关注和关联账号事件。

## 处理限制和重试

套餐设定活跃 WebSocket、监控账号和 History API 限制。添加和移除 handle 的请求在你的所有密钥和连接间共用一个限额，可先连续发送 100 次请求，之后每秒 1 次。超过 100 个 handle 的请求按每 100 个计一次，向上取整。收到 `429` 时暂停对应流程；如果响应包含 `retryAfterSeconds` 或 `Retry-After` 头，按其等待，否则使用常规退避。

| 接口 | 限制信号 | 推荐处理 |
| --- | --- | --- |
| WebSocket | 连接升级时返回 429 | 关闭不用的连接，或升级到更多连接数的套餐 |
| History API | 限流时返回 `retryAfterSeconds` | 等待后再回放下一个窗口 |
| 监控账号 | `/api/me` 返回套餐用量 | 批量添加前检查 count 和 limit |
| Handle 请求 | 返回 `429`，或 `twitter_handles_result` 中带有限流 `error` | 把变更的 handle 合并发送，每次最多 100 个，重试前先退避 |
