Webhook
PaidCarbon 记录创建、变更或删除时接收 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 包含之前的行。
{
"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或httpsURL。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=703d2abadc91281113b98bbc26dd6ec6ebd63072172c44b3635b575b1f1313f1t 是尝试时间的 Unix 秒。v1 是 `${t}.${body}` 的十六进制 HMAC-SHA256,以整个密钥字符串为键,body 为原始请求正文。验证投递:
- 在解析之前读取原始正文。解析并重新序列化 JSON 会改变字节。
- 计算 HMAC 并用常量时间与
v1比较。 - 拒绝与你时钟相差超过五分钟的
t,使捕获的请求无法重放。每次重试都会用新的t重新签名。
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(轮换密钥) 会替换它,下一次投递使用新密钥签名,因此请同时更新你的端点。查看或轮换密钥需要更新设置权限。
投递告诉你记录发生了变化。请把正文视为通知,并在处理前用 API 密钥获取记录。