跳到文档正文

Instagram WebSocket

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

设置 Instagram Tracking
在聊天中打开
按产品代码校对:2026年10月14日

快速连接

你需要有效的 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
// 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 保存在服务端,不要放进公开的浏览器代码。

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

读取消息信封

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

字段类型含义
v1消息信封版本
tinstagram 或 control频道
opcontent、update 或 auth_pong操作
idstring媒体 ID。更新事件沿用它所替换的内容事件的 ID
tsnumberTweetStream 发送该帧的时间,Unix 毫秒
dobject该操作的载荷

Instagram 消息信封类型

显示完整定义(72 行)
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>/
reelReels 短视频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 可能过期,请在收到事件后尽快下载所需文件。

时间戳

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

事件示例

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

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
  }
}
{
  "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 频道,请在控制台中设置。