开发

变更与 Webhook

每个空间都有一份按顺序的变更日志。可以直接读,可以通过 WebSocket 收到推送信号,也可以让它按顺序、带签名地 POST 到你的地址。

查看 Markdown

变更日志

空间里的每一次变化都按序号追加到它的变更日志(journal)里:没有空缺,不会乱序。同步客户端、流水线、Webhook 和 SDK 的 changes() 都是它的读者。

Shell
curl -H "Authorization: Bearer $SIDESPACE_TOKEN" \
  "https://side.example.com/v1/spaces/$SPACE/journal?after=1040&limit=200"
JSON
{
  "entries": [
    {
      "seq": 1041,
      "op": "version.created",
      "node_id": "nod_…",
      "version_id": "ver_…",
      "actor_id": "usr_…",
      "actor_kind": "agent",
      "actor_via": "tok_…",
      "ts": "2026-11-14T09:30:00Z",
      "node": { "id": "nod_…", "name": "A001_C003.mov", "parent_id": "nod_…", "head_version_id": "ver_…" }
    }
  ],
  "last_seq": 1041,
  "has_more": false
}

记下处理过的最后一个 seq,下次用 after= 接着读。令牌需要 files:read 或 events:read。

op什么时候
node.created、node.renamed、node.moved、node.deleted、node.restored目录结构变了
version.created有人保存了;version_id 是新版本
version.labeled某个版本起了名或去掉了名字
sidecar.updated某个版本的元数据变了
rendition.updated缩略图或代理生成好了
incoming.started、incoming.stopped有人开始或停止上传这个文件(应用里显示“正在传入”)
comment.created、comment.updated审阅评论

actor_kind 是 human、agent 或 plugin,可以用来跳过你自己工具的改动,或者显示是谁做的。

for await (const change of side.changes("Film A", { after: lastSeq })) {
  console.log(change.seq, change.op, change.node.name);
}

实时信号

不想轮询的话,打开 GET /v1/ws(WebSocket;令牌放在 Bearer 请求头里,不能设请求头时用 ?access_token=),然后订阅:

JSON
{ "type": "subscribe", "spaces": ["spc_…"] }

空间的变更日志有新内容时,服务器发 {"type": "journal", "space_id": "spc_…", "seq": 1042};有人打开文件时发 {"type": "presence", …};给你的通知是 {"type": "notification", …}。它从不直接发变更内容:收到信号后,从你的游标往后读变更日志。每 25 秒 ping 一次,60 秒没动静的连接会被关闭。信号可能重复或乱序,用 seq 和你的游标比较即可。

Webhook

空间管理员可以把变更日志推送到一个 URL(令牌需要 webhooks:manage)。

Shell
sidespace webhook create "Film A" https://hooks.example.com/side \
  --event version.created --path "/dailies/**" --mime video/
# whsec_…  ← 签名密钥,只显示这一次
sidespace webhook list "Film A"
sidespace webhook ping "Film A" whk_…
sidespace webhook deliveries "Film A" whk_…
sidespace webhook replay "Film A" whk_… --from 120
sidespace webhook enable "Film A" whk_… [--off]
sidespace webhook rm "Film A" whk_…

用 API:POST /v1/spaces/{sid}/webhooks,带 url、events、path_glob 和 mime_prefix。

收到什么

每个事件一个 POST,按变更日志的顺序,只带引用,不带文件内容:

JSON
{
  "id": "spc_….1042",
  "type": "version.created",
  "space_id": "spc_…",
  "seq": 1042,
  "time": "2026-11-14T09:30:00Z",
  "node_id": "nod_…",
  "version_id": "ver_…",
  "path": "/dailies/A001_C003.mov",
  "node": { "name": "A001_C003.mov", "mime": "video/quicktime", "size": 734003200 },
  "actor": { "id": "usr_…", "kind": "human" }
}

过滤条件:事件类型(--event,可重复)、路径 glob(/dailies/**、**/*.mov)、MIME 前缀(video/)。comment.* 事件还会带上评论内容。

投递规则

  • 10 秒内返回 2xx。其他情况都算失败,这个订阅后面的事件会等它成功,以保证顺序。
  • 重试从 10 秒开始翻倍,最长 1 小时。连续失败 24 次(约 16 小时)后订阅自动停用,并写明原因。enable 或 replay 会清掉失败计数。
  • 创建它的成员不再是空间管理员时,订阅停用。
  • 生产环境只允许 https,且不能指向内网、本机或链路本地地址。

验证签名

每次投递带 Side-Signature: t=<unix 秒>,v1=<HMAC-SHA256(密钥, "<t>.<原始 body>") 的十六进制> 和 Side-Event-Id(等于 body 里的 id,重试和重放时不变)。用原始 body 验签,拒绝时间戳偏差超过 5 分钟的请求,并按事件 id 去重。

import { verifyWebhookSignature } from "@sidespace/sdk/webhooks";

app.post("/hooks/side", express.raw({ type: "application/json" }), (req, res) => {
  if (!verifyWebhookSignature(process.env.SIDE_WEBHOOK_SECRET!, req.get("Side-Signature"), req.body)) return res.sendStatus(401);
  const event = JSON.parse(req.body.toString());
  queue.add(event.id, event); // 按 id 去重
  res.sendStatus(204);
});