Webhook
Webhook 会针对选定的租户事件投递带签名的出站 POST 请求,因此你无需轮询即可对变更做出响应。可从 Settings -> Integrations -> Webhooks 或公共 API 管理端点。
事件
| 事件 | 状态 | 投递 |
|---|---|---|
inventory.item.created | 已发出 | 带签名的发件箱投递 |
inventory.item.updated | 已发出 | 带签名的发件箱投递 |
order.created | 已发出 | 带签名的发件箱投递 |
order.updated | 已发出 | 带签名的发件箱投递 |
contact.created | 已发出 | 带签名的发件箱投递 |
contact.updated | 已发出 | 带签名的发件箱投递 |
stock_document.created | 已发出 | 带签名的发件箱投递 |
stock_document.updated | 已发出 | 带签名的发件箱投递 |
user.invited | 已发出 | 带签名的发件箱投递 |
organization.updated | 已发出 | 带签名的发件箱投递 |
integration.connected | 已发出 | 带签名的发件箱投递 |
bom.created | 计划中 | 契约已预留,尚未发出 |
bom.updated | 计划中 | 契约已预留,尚未发出 |
webhook.test | 仅测试 | 无签名的连通性检查 |
订阅
使用 POST /v1/webhooks(作用域 webhooks:write,企业版)创建端点。签名密钥在响应中仅返回一次,请立即存储。
curl -X POST https://api.fabhub.app/v1/webhooks \
-H "X-API-Key: $FABHUB_API_KEY" \
-H "Idempotency-Key: 7c1f...-..." \
-H "Content-Type: application/json" \
-d '{"name":"Orders sync","targetUrl":"https://example.com/hooks/fabhub","subscribedEvents":["order.created","order.updated"]}'
{
"data": {
"id": "wh_1",
"name": "Orders sync",
"targetUrl": "https://example.com/hooks/fabhub",
"status": "active",
"environment": "production",
"description": null,
"subscribedEvents": ["order.created", "order.updated"],
"createdAt": "2026-06-20T09:00:00Z",
"updatedAt": "2026-06-20T09:00:00Z"
},
"signingSecret": "whsec_9f3a...stored-once"
}
投递格式
每次投递都是一个带有 JSON 事件主体及以下标头的 POST:
X-FabHub-Event- 事件类型,例如order.createdX-FabHub-Timestamp- 对负载签名时的 unix 秒数X-FabHub-Signature-v1=<hex>HMAC;在密钥轮换期间会出现多个以逗号分隔的v1=部分
POST /hooks/fabhub HTTP/1.1
X-FabHub-Event: order.created
X-FabHub-Timestamp: 1718873400
X-FabHub-Signature: v1=4f2c...e1
{ "event": "order.created", "data": { "id": "ord_1", "module": "sell", "status": "open" } }
验证签名
签名为 HMAC-SHA256(secret, "<timestamp>.<rawBody>"),以十六进制编码,其中 secret 是你从十六进制解码的签名密钥。务必对照确切的原始请求主体进行验证,并在 JSON 解析之前进行。 SDK 自带一个验证器:
import { verifyFabHubWebhookSignature } from '@fabhub/sdk';
const result = verifyFabHubWebhookSignature({
signingSecret: process.env.FABHUB_WEBHOOK_SECRET,
rawBody,
timestamp: req.headers['x-fabhub-timestamp'],
signature: req.headers['x-fabhub-signature'],
// toleranceSeconds: 300 (default) - rejects stale/replayed timestamps
});
if (!result.ok) return res.status(400).end();
// safe to JSON.parse(rawBody) now
密钥轮换
使用 PATCH /v1/webhooks/{webhook_id} 并传入 {"rotateSecret": true} 进行轮换。新密钥仅返回一次,在重叠窗口期间投递会同时使用新密钥和旧密钥签名(通过 signingSecrets 将两者都传给验证器)。
投递日志
使用 GET /v1/webhooks/{webhook_id}/deliveries(作用域 webhooks:deliveries:read)检查投递尝试:
{
"data": [
{
"id": "del_1",
"eventType": "order.created",
"status": "delivered",
"attempts": 1,
"lastError": null,
"lastHttpStatus": 200,
"createdAt": "2026-06-20T09:01:00Z",
"updatedAt": "2026-06-20T09:01:01Z"
}
],
"pagination": { "page": 1, "pageSize": 20, "total": 1, "totalPages": 1 }
}
合成测试探测(webhook.test)是无签名的连通性检查,不会出现在发件箱日志中。
最佳实践
- 快速返回
2xx;繁重工作异步执行。 - 将投递视为至少一次(at-least-once),并基于事件标识进行去重。
- 在
X-FabHub-Event上进行过滤,忽略你不处理的事件类型。