---
title: "Instagram WebSocket"
canonical_url: "https://tweetstream.io/zh-cn/docs/instagram-websocket"
last_updated: 2026-10-14
---

# Instagram WebSocket

使用 TweetStream API key 连接 `wss://insta-ws.tweetstream.io/ws`，接收监控列表中 Instagram 账号新发布的帖子、Reels、轮播帖和快拍。

Canonical URL: https://tweetstream.io/zh-cn/docs/instagram-websocket

## 快速连接

你需要有效的 Instagram Tracking 订阅或试用、控制台监控列表中至少一个 Instagram 账号，以及 TweetStream API key。运行下面的示例：输出 `Instagram feed connected` 表示认证成功，监控账号发布内容时会打印事件。

| 设置 | 值 | 说明 |
| --- | --- | --- |
| 端点 | `wss://insta-ws.tweetstream.io/ws` | 与 X 数据流端点分开 |
| 子协议 | `tweetstream.instagram.v1` | 只要发送任何子协议（包括认证令牌）就必须提供。其他子协议返回 HTTP 400 |
| 认证协议 | `tweetstream.auth.token.<API_KEY>` | 使用现有的 TweetStream API key |

### 运行 TypeScript 冒烟测试

```typescript
// bun add ws
// TWEETSTREAM_API_KEY=ts_... bun run instagram.ts
import WebSocket from "ws";

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

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

ws.on("open", () => {
  console.log("Instagram feed connected");
});

ws.on("message", (raw) => {
  const event = JSON.parse(raw.toString());
  if (event.t === "instagram" && (event.op === "content" || event.op === "update")) {
    console.log(event.op, event.d.kind, event.d.author.username, event.d.url);
  }
});

ws.on("unexpected-response", (_request, response) => {
  console.error("Connection rejected", response.statusCode, response.statusMessage);
  response.resume();
  process.exitCode = 1;
});

ws.on("close", (code, reason) => {
  console.log("Instagram feed closed", code, reason.toString());
});
```

## 认证

Instagram WebSocket 接受与 X 数据流相同的 API key 和认证方式。如果以 `tweetstream.auth.token.<API_KEY>` 发送 key，还需同时提供 `tweetstream.instagram.v1`。请把 key 保存在服务端，不要放进公开的浏览器代码。

| 场景 | 请求头或协议 | 说明 |
| --- | --- | --- |
| WebSocket | `tweetstream.instagram.v1` + `tweetstream.auth.token.<API_KEY>` | 推荐方式 |
| WebSocket 备用方式 | `Authorization: Bearer <API_KEY>` 或 `?apiKey=<API_KEY>` | 只有运行环境无法设置请求头时才使用查询参数 |

## 读取消息信封

每一帧都是一个 JSON 消息信封，结构与 X 数据流相同。先按 `t`、再按 `op` 分流；遇到无法识别的操作时忽略该消息，不要关闭连接。

| 字段 | 类型 | 含义 |
| --- | --- | --- |
| `v` | `1` | 消息信封版本 |
| `t` | `instagram` 或 `control` | 频道 |
| `op` | `content`、`update` 或 `auth_pong` | 操作 |
| `id` | string | 媒体 ID。更新事件沿用它所替换的内容事件的 ID |
| `ts` | number | TweetStream 发送该帧的时间，Unix 毫秒 |
| `d` | object | 该操作的载荷 |

### Instagram 消息信封类型

```typescript
type InstagramKind = 'post' | 'reel' | 'carousel' | 'story' | 'unknown';

type InstagramMedia = {
  // Position in the media array, starting at 0.
  index: number;
  type: 'image' | 'video' | 'unknown';
  url: string;
  // Present only on an image that is a video's cover.
  cover?: true;
};

type InstagramContent = {
  // Instagram media ID; also the envelope id.
  id: string;
  // Present when known; the post URL uses it.
  shortcode?: string;
  kind: InstagramKind;
  url: string;
  author: {
    id?: string;
    // Lowercase, without a leading @.
    username: string;
  };
  // Empty string when the post has no caption.
  caption: string;
  // May be empty or partial; see the media notes.
  media: Array<InstagramMedia>;
  // Publish time in Unix milliseconds, or null when it is not known.
  createdAt: number | null;
  // True when createdAt is an estimate.
  createdAtEstimated: boolean;
  // When TweetStream received the post, in Unix milliseconds.
  receivedAt: number;
  // Content events only. True when this post reached TweetStream well after its
  // publish time; the post is still new to you. A missing flag does not mean
  // the post is recent: when createdAt is set, compare it with receivedAt.
  late?: true;
};

type InstagramContentEnvelope = {
  v: 1;
  t: 'instagram';
  op: 'content';
  id: string;
  ts: number;
  d: InstagramContent;
};

type InstagramUpdateEnvelope = {
  v: 1;
  t: 'instagram';
  // Insert, or replace the stored event with the same id. Sent at most once per id.
  op: 'update';
  id: string;
  ts: number;
  // Never carries late: keep the value from the content event, or compare
  // createdAt with receivedAt if you never got one.
  d: Omit<InstagramContent, 'late'>;
};

type InstagramAuthPongEnvelope = {
  v: 1;
  t: 'control';
  op: 'auth_pong';
  ts: number;
  d: Record<string, never>;
};

type InstagramEnvelope =
  | InstagramContentEnvelope
  | InstagramUpdateEnvelope
  | InstagramAuthPongEnvelope;
```

## 内容类型

已知类型时，`d.kind` 会说明发布的内容类型。`d.url` 指向 Instagram 上的内容；未知 shortcode 时指向作者主页。

| 类型 | 含义 | URL 格式 |
| --- | --- | --- |
| `post` | 包含一张图片或一个视频的帖子 | `instagram.com/p/<shortcode>/` |
| `reel` | Reels 短视频 | `instagram.com/reel/<shortcode>/` |
| `carousel` | 包含多张图片或多个视频的帖子 | `instagram.com/p/<shortcode>/` |
| `story` | 快拍 | `instagram.com/stories/<username>/<id>/` |
| `unknown` | 未能确定类型。事件仍包含 ID、作者、URL 和已找到的媒体，可按该账号的新内容处理 | `instagram.com/p/<shortcode>/`；未知 shortcode 时为作者主页 |

## 媒体顺序与封面

- `index` 是该项在 `media` 中的位置，从 0 开始。已知帖子的滑动顺序时，`media` 按该顺序排列。图片和视频混合的轮播帖可能先列出图片。
- `cover: true` 表示该图片是某个视频的封面，其他媒体不含此字段。
- `type` 为 `image`、`video`，未能确定时为 `unknown`。
- `media` 可能为空，或少于帖子实际包含的媒体数量。之后的 `update` 可能补全其余媒体。需要完整内容时请打开 `url`。
- 媒体 URL 可能过期，请在收到事件后尽快下载所需文件。

## 时间戳

| 字段 | 类型 | 含义 |
| --- | --- | --- |
| `createdAt` | number 或 `null` | 发布时间，Unix 毫秒；未知时为 `null` |
| `createdAtEstimated` | boolean | `createdAt` 为估算值时为 `true` |
| `receivedAt` | number | TweetStream 收到该内容的时间，Unix 毫秒 |
| `late` | `true` 或不出现 | 帖子在发布很久之后才到达 TweetStream 时为 `true`，它对你来说仍是新帖子。只有 `content` 事件带此字段，`update` 不带。没有 `late` 不代表帖子是最近发布的：`createdAt` 有值时，请与 `receivedAt` 比较 |

## 事件示例

每个示例都是连接中收到的完整一帧。

### 轮播帖

```json
{
  "v": 1,
  "t": "instagram",
  "op": "content",
  "id": "3988012345678901234",
  "ts": 1772000001060,
  "d": {
    "id": "3988012345678901234",
    "shortcode": "DdsPyK1GrTK",
    "kind": "carousel",
    "url": "https://www.instagram.com/p/DdsPyK1GrTK/",
    "author": {
      "id": "25025320",
      "username": "examplebrand"
    },
    "caption": "Three frames from launch day",
    "media": [
      {
        "index": 0,
        "type": "image",
        "url": "https://scontent-iad3-1.cdninstagram.com/v/1.jpg"
      },
      {
        "index": 1,
        "type": "video",
        "url": "https://scontent-iad3-1.cdninstagram.com/v/2.mp4"
      },
      {
        "index": 2,
        "type": "image",
        "url": "https://scontent-iad3-1.cdninstagram.com/v/2.jpg",
        "cover": true
      }
    ],
    "createdAt": 1772000000000,
    "createdAtEstimated": false,
    "receivedAt": 1772000001050
  }
}
```

### Reels

```json
{
  "v": 1,
  "t": "instagram",
  "op": "content",
  "id": "3987848477383929083",
  "ts": 1772000001060,
  "d": {
    "id": "3987848477383929083",
    "shortcode": "DdXrw_IjSz7",
    "kind": "reel",
    "url": "https://www.instagram.com/reel/DdXrw_IjSz7/",
    "author": {
      "id": "25025320",
      "username": "examplebrand"
    },
    "caption": "New drop",
    "media": [
      {
        "index": 0,
        "type": "video",
        "url": "https://scontent-iad3-1.cdninstagram.com/v/reel.mp4"
      }
    ],
    "createdAt": 1772000000000,
    "createdAtEstimated": false,
    "receivedAt": 1772000001050
  }
}
```

### 发布时间为估算值的快拍

```json
{
  "v": 1,
  "t": "instagram",
  "op": "content",
  "id": "3988000000000000001",
  "ts": 1772000001060,
  "d": {
    "id": "3988000000000000001",
    "kind": "story",
    "url": "https://www.instagram.com/stories/examplebrand/3988000000000000001/",
    "author": {
      "id": "25025320",
      "username": "examplebrand"
    },
    "caption": "",
    "media": [
      {
        "index": 0,
        "type": "image",
        "url": "https://scontent-iad3-1.cdninstagram.com/v/story.jpg"
      }
    ],
    "createdAt": 1772000000000,
    "createdAtEstimated": true,
    "receivedAt": 1772000001050
  }
}
```

### 未确定类型

```json
{
  "v": 1,
  "t": "instagram",
  "op": "content",
  "id": "3987848477383929083",
  "ts": 1772000001060,
  "d": {
    "id": "3987848477383929083",
    "shortcode": "DdXrw_IjSz7",
    "kind": "unknown",
    "url": "https://www.instagram.com/p/DdXrw_IjSz7/",
    "author": {
      "id": "25025320",
      "username": "examplebrand"
    },
    "caption": "Kind not established",
    "media": [
      {
        "index": 0,
        "type": "unknown",
        "url": "https://scontent-iad3-1.cdninstagram.com/v/first.jpg"
      }
    ],
    "createdAt": null,
    "createdAtEstimated": false,
    "receivedAt": 1772000001050
  }
}
```

### 迟到的帖子

```json
{
  "v": 1,
  "t": "instagram",
  "op": "content",
  "id": "3987848477383929083",
  "ts": 1772000001060,
  "d": {
    "id": "3987848477383929083",
    "shortcode": "DdXrw_IjSz7",
    "kind": "post",
    "url": "https://www.instagram.com/p/DdXrw_IjSz7/",
    "author": {
      "id": "25025320",
      "username": "examplebrand"
    },
    "caption": "Behind the scenes",
    "createdAt": 1771999101050,
    "media": [
      {
        "type": "image",
        "url": "https://scontent-iad3-1.cdninstagram.com/v/late.jpg",
        "index": 0
      }
    ],
    "createdAtEstimated": false,
    "late": true,
    "receivedAt": 1772000001050
  }
}
```

## 更新

`update` 事件携带该 `id` 的完整 `d` 载荷。如果你已保存该 `id` 的事件，请用新事件整体替换，不要逐字段合并。如果尚未见过该 `id`（例如在首个事件之后才连接），请将该更新当作新内容处理。每个事件最多发送一次更新，且仅在 TweetStream 获得更完整的版本时发送，例如补充了媒体或确定了类型。更新只通过 WebSocket 发送，Discord 中的帖子不会被修改。

### 轮播帖更新

```json
{
  "v": 1,
  "t": "instagram",
  "op": "update",
  "id": "3988012345678901234",
  "ts": 1772000001060,
  "d": {
    "id": "3988012345678901234",
    "shortcode": "DdsPyK1GrTK",
    "kind": "carousel",
    "url": "https://www.instagram.com/p/DdsPyK1GrTK/",
    "author": {
      "id": "25025320",
      "username": "examplebrand"
    },
    "caption": "Three frames from launch day",
    "createdAt": 1772000000000,
    "media": [
      {
        "type": "image",
        "url": "https://scontent-iad3-1.cdninstagram.com/v/1.jpg",
        "index": 0
      },
      {
        "type": "video",
        "url": "https://scontent-iad3-1.cdninstagram.com/v/2.mp4",
        "index": 1
      },
      {
        "type": "image",
        "url": "https://scontent-iad3-1.cdninstagram.com/v/2.jpg",
        "index": 2,
        "cover": true
      }
    ],
    "createdAtEstimated": false,
    "receivedAt": 1772000001050
  }
}
```

## 检查认证

应用需要检查认证时，发送 `{ "op": "auth_ping" }`。连接有效时会返回 `control/auth_pong`，data 为空对象。

### 认证 ping

```json
{
  "op": "auth_ping"
}
```

### 认证 pong

```json
{
  "v": 1,
  "t": "control",
  "op": "auth_pong",
  "ts": 1772000001060,
  "d": {}
}
```

## 通过 HTTP 管理账号

用 API key 修改的就是控制台中显示的同一个监控列表。`accounts` 可以是一个用户名，也可以是最多 100 个用户名的数组，带不带 `@` 均可。`207` 表示部分账号已修改、部分被拒绝，`rejected` 会给出每个账号的原因。添加已在列表中的账号会返回 `duplicate`，移除不在列表中的账号会返回 `not_tracked`。两者都不算失败，因此可以放心重试请求。

| 方法 | 路径 | 结果 |
| --- | --- | --- |
| `GET` | `/api/instagram/accounts` | 你的账号（状态为 `live`、`pending` 或 `paused`）以及 `limit` |
| `POST` | `/api/instagram/add-account` | 添加账号。超出上限的账号以 `capacity` 返回 |
| `DELETE` | `/api/instagram/remove-account` | 移除账号，并在 `removed` 中列出 |

### 添加 Instagram 账号

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

const response = await fetch("https://api.tweetstream.io/api/instagram/add-account", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${apiKey}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    accounts: ["nasa", "@natgeo"],
  }),
});

if (!response.ok && response.status !== 207) {
  const detail = await response.text();
  throw new Error(`Add failed (${response.status}): ${detail}`);
}

const { accounts, limit, rejected } = await response.json();
console.log(`${accounts.length}/${limit} accounts`, rejected);
```

## 连接与账号

- 每个 Instagram Tracking 订阅包含 3 个 Instagram WebSocket 连接，试用期间为 1 个。它们与 X 套餐的连接数分开计算。
- 可随时在控制台或通过 HTTP 添加或移除 Instagram 账号，账号变动不会改变账单。
- 事件也可以发送到 Discord 频道，请在控制台中设置。
