商业版功能—— 本实例的 Business 方案已包含。如需自托管此功能,需要商业许可。 许可说明 →

Webhook

Paid

Carbon 记录创建、变更或删除时接收 HTTP 回调。

Webhook 在订阅表中的记录被插入、更新或删除时,向配置的 URL 发送 HTTP POST。用 Webhook 响应变更,而无需轮询 API。

可订阅的表

每个 Webhook 订阅一张表。以下表支持订阅:

模块表
销售customer、quote、salesOrder、salesRfq
采购purchaseOrder、supplier、supplierQuote
开票salesInvoice、purchaseInvoice
生产job
库存receipt
物料item、makeMethod、methodMaterial、methodOperation、modelUpload、changeOrder、changeOrderAffectedItem
用户employee

至少选择一个触发操作:插入、更新或删除。

负载

Carbon 向配置的 URL 发送 JSON 正文。type 标识操作,record 包含受影响的行,更新时 old 包含之前的行。

json
{
  "type": "UPDATE",
  "table": "salesOrder",
  "companyId": "abc123",
  "eventId": "1042",
  "record": { "id": "...", "salesOrderId": "SO000042", "status": "To Ship and Invoice" },
  "old": { "id": "...", "salesOrderId": "SO000042", "status": "Draft" }
}

old 仅出现在更新中。删除时,record 包含被删除行的最后状态。Carbon 设置 Content-Type: application/json 和 Carbon-Signature 头(参见验证投递)。

物料清单或工艺路线变更会以 methodMaterial 或 methodOperation 的变更形式到达,并带有其所属版本的 makeMethodId。版本状态(草稿、已启用、已归档)在 makeMethod 上。变更通知的发布是 changeOrder 从 Implementation 到 Done 的更新。

eventId 标识一次变更。它在投递重试间保持稳定,且每次新变更都不同,包括对同一记录的重复更新。请将其用作幂等键。

配置

在设置 → Webhook 中创建和管理 Webhook。每个 Webhook 包含:

  • 名称: 标签,公司内唯一。
  • 表: 要监视的一张表。
  • URL: POST 发送到的位置;必须解析到公网地址的 http 或 https URL。Carbon 拒绝向回环、私有网络、链路本地和云元数据地址投递。
  • 触发器: 插入、更新、删除中的任意项(至少一个)。
  • 启用: Webhook 默认开启;关闭可暂停投递。
  • 签名密钥: 可在 Webhook 的编辑抽屉中查看或轮换(见下文)。

Carbon 会跟踪成功和错误计数以及每种结果的最新时间戳。

投递与可靠性

Webhook 投递是异步的,通常在数据库变更后几秒内发生。

投递为至少一次。如果端点未在 30 秒内返回成功状态,Carbon 会用指数退避重试,之后将变更标记为失败。请针对这些情况设计:

  • 处理重复。 在你的服务已处理之后请求仍可能重试。请按 eventId 去重,而不是 type 加 record.id,因为同一记录的多次变更会共享后者。
  • 中断后核对。 重试耗尽后,Carbon 会丢弃变更并增加错误计数。需要完整事件处理时请查询 API。

成功和错误计数器按变更记录一次结果,而不是按尝试次数,因此重试后成功投递的事件只计一次成功。

Carbon 不跟随重定向。3xx 响应视为失败尝试,因此请配置最终 URL。投递到被拒绝的地址不会重试;它计为一次错误。

验证投递

每个 Webhook 有自己的签名密钥(whsec_…)。每次投递都带 Carbon-Signature 头。使用密钥 whsec_example,在 1760000000 发送正文 {} 的签名如下:

Carbon-Signature: t=1760000000,v1=703d2abadc91281113b98bbc26dd6ec6ebd63072172c44b3635b575b1f1313f1

t 是尝试时间的 Unix 秒。v1 是 `${t}.${body}` 的十六进制 HMAC-SHA256,以整个密钥字符串为键,body 为原始请求正文。验证投递:

  1. 在解析之前读取原始正文。解析并重新序列化 JSON 会改变字节。
  2. 计算 HMAC 并用常量时间与 v1 比较。
  3. 拒绝与你时钟相差超过五分钟的 t,使捕获的请求无法重放。每次重试都会用新的 t 重新签名。
ts
import { createHmac, timingSafeEqual } from "node:crypto";

export function verifyCarbonSignature(
  rawBody: string,
  header: string,
  secret: string,
  toleranceSeconds = 300
) {
  const parts = Object.fromEntries(
    header.split(",").map((part) => part.split("=", 2) as [string, string])
  );
  const timestamp = Number(parts.t);
  if (!Number.isFinite(timestamp)) return false;
  if (Math.abs(Date.now() / 1000 - timestamp) > toleranceSeconds) return false;

  const expected = createHmac("sha256", secret)
    .update(`${timestamp}.${rawBody}`)
    .digest("hex");
  const received = parts.v1 ?? "";
  return (
    received.length === expected.length &&
    timingSafeEqual(Buffer.from(received), Buffer.from(expected))
  );
}

在设置 → Webhook 的 Webhook 编辑抽屉中查看密钥。Roll secret(轮换密钥) 会替换它,下一次投递使用新密钥签名,因此请同时更新你的端点。查看或轮换密钥需要更新设置权限。

NOTE

投递告诉你记录发生了变化。请把正文视为通知,并在处理前用 API 密钥获取记录。

相关内容