文档集成

Rust · 0.1.0

Webhook

把笔记事件投递给你控制的接收端。

配置与测试

在设置中创建个人 Webhook,并使用可信接收地址。载荷可能包含笔记内容,因此要确认接收端如何保存,以及谁能访问。先使用不敏感的测试笔记。私有网络目标默认受限,除非运维者明确放行;不要为了一个投递问题整体开放内部网络。

校验签名与响应

配置签名密钥后,投递会携带 webhook-id、webhook-timestamp 和 webhook-signature。签名对 id.timestamp.原始请求体进行 HMAC-SHA256,编码为 v1,<base64>;whsec_ 前缀后是 Base64 编码密钥。请使用未经改写的请求体校验,安全比较,并拒绝过期或重放投递。当前接收协议要求成功 HTTP 状态及有效 JSON;若包含 code 字段,它必须是数字 0。空的 204 响应不满足本实现的 JSON 确认要求。

按尽力投递设计

投递器使用有界内存队列和超时机制,队列满时可能丢弃投递;它不是持久化的严格一次事件日志。接收端应具备幂等性,并监控投递失败。如果不能接受漏事件,应定期通过 API 对账。

连接一个接收端并测试一条笔记

前提:你控制的 HTTPS 接收端、保留原始请求字节的能力,以及受保护的接收日志。在「设置 → Webhook」创建名称清楚的 Webhook,填入准确接收地址,把签名密钥保存到接收端密钥存储。若创建时未显示,可重新打开编辑对话框显示已存密钥。使用同一个账号创建无敏感内容的私人测试笔记,检查收到 JSON 中的 activityType、creator、memo,并与测试笔记比较。不要把真实私人内容发送到公开请求查看服务。

有时间窗口的 Node.js 签名校验

此函数使用原始字节校验签名,并拒绝与接收端时钟相差超过五分钟的时间戳。应在解析或处理载荷之前运行。它是校验模块,不是完整 HTTP 服务:接收端还需限制请求大小、通过持久去重存储拒绝重复 webhook-id,并安全记录已接受工作。HMAC 与恒定时间比较使用 Node.js crypto API。

import { createHmac, timingSafeEqual } from "node:crypto";

export function verifyWebhook({ id, timestamp, signature, rawBody, secret },
  nowSeconds = Math.floor(Date.now() / 1000)) {
  if (typeof id !== "string" || !id ||
      typeof timestamp !== "string" || !/^\d+$/.test(timestamp) ||
      typeof signature !== "string" || typeof secret !== "string" || !secret ||
      !Buffer.isBuffer(rawBody)) return false;
  const sentAt = Number(timestamp);
  if (!Number.isSafeInteger(sentAt) || Math.abs(nowSeconds - sentAt) > 300) return false;
  const key = secret.startsWith("whsec_")
    ? Buffer.from(secret.slice(6), "base64")
    : Buffer.from(secret, "utf8");
  if (!key.length) return false;
  const digest = createHmac("sha256", key)
    .update(`${id}.${timestamp}.`, "utf8")
    .update(rawBody)
    .digest("base64");
  const expected = Buffer.from(`v1,${digest}`, "utf8");
  const received = Buffer.from(signature, "utf8");
  return expected.length === received.length && timingSafeEqual(expected, received);
}

确认接收与定位投递问题

完成签名及重放检查,并安全记录事件后,返回 HTTP 200、Content-Type: application/json 和下面的正文;不要在接收端尚未安全接受工作前确认。原始 JSON 空白被改写后,原签名应失效;在本地测试这种拒绝及过期时间戳。接收端完全没请求,应检查目标限制、出站策略、DNS 或连接;收到请求但 Memos 判投递失败,应检查状态、响应 JSON、签名处理及超时。修复对应层;可能丢失事件时通过 API 对账。

{"code":0}