Webhook
订阅您自己的系统 Flash 事件。为您关注的事件注册一个 HTTPS 端点,并且 Flash 每枚导弹发射瞬间都会送出一个带有签名的薄型有效载荷—— 不进行轮询,不进行夜间导出签名在后面 标准 Webhooks 规范、交付重试(采用退避策略),并且每次尝试都会被记录。
注册端点
创建一个包含 webhooks:manage 范围的 Bearer fl_live_ 密钥的 webhook 端点。通过 HTTPS 发送 url 请求,并附上您希望传递的 eventTypes 列表。 只会在响应中返回一次,之后不再返回——请妥善保管;您需要它来验证每次传递。
curl -X POST https://flash.socialhub.ai/api/v2/webhooks \
-H "Authorization: Bearer fl_live_..." \
-H "Content-Type: application/json" \
-d '{
"url": "https://hooks.your-app.com/flash",
"description": "prod ingest",
"eventTypes": ["member.created", "loyalty.tier_upgraded"]
}'
# 201 Created
# {
# "data": {
# "id": "whe_...",
# "url": "https://hooks.your-app.com/flash",
# "eventTypes": ["member.created", "loyalty.tier_upgraded"],
# "status": "active",
# "secret": "whsec_..." <-- shown ONCE, store it now
# }
# }稍后可使用 GET /api/v2/webhooks(列表,无密钥)、PATCH /api/v2/webhooks/{id}(更改 URL/事件/状态,或发送 { "action": "rotate_secret" } 以滚动密钥)和 DELETE /api/v2/webhooks/{id} 管理端点。您还可以通过 设置 → Webhooks 注册、测试 ping 和重放发送。
活动目录
询问 API 您可以订阅哪些事件。该目录源自实时自动化触发的真实数据源,因此仅列出今天实际触发的事件,并按生命周期阶段(获客、互动、忠诚度、留存、商务、拥护)分组,每个事件都包含其事件键、描述和示例有效负载。响应还记录了共享有效负载信封和签名标头。
curl https://flash.socialhub.ai/api/v2/events/catalog \
-H "Authorization: Bearer fl_live_..."
# { "data": { "events": [
# { "event": "member.created", "label": "Member created",
# "stage": "acquisition", "examplePayload": { ... } },
# { "event": "loyalty.tier_upgraded", "label": "Tier upgraded",
# "stage": "loyalty", "examplePayload": { ... } },
# ...
# ],
# "payload": { "schema": { ... }, "headers": { ... } }
# } }订阅时,将事件的 event 键直接放入 eventTypes array 中。需要 webhooks:manage 权限范围。
轻量级、非个人身份信息 (PII) 有效载荷
每次投递都包含相同的小信封:一个稳定的 id、事件 type、一个 created_at timestamp 和一个紧凑的 data 数据块。有效载荷 绝不包含个人数据——不包含电子邮件、姓名、电话号码或地址。当您需要成员的详细信息时,请使用 member_ref 通过已认证的 API 获取这些信息。这样可以将个人身份信息 (PII) 保留在成员记录中,而不会记录在您的 Webhook 日志中。
POST https://hooks.your-app.com/flash
webhook-id: evt_a1b2c3... # stable across retries — dedupe on this
webhook-timestamp: 1767225600 # unix seconds
webhook-signature: v1,K5x0mF...== # base64 HMAC-SHA256 (may be space-separated during rotation)
Content-Type: application/json
{
"id": "evt_a1b2c3...",
"type": "loyalty.tier_upgraded",
"created_at": "2026-01-01T00:00:00.000Z",
"data": {
"resource_id": "mbr_...",
"member_ref": "mbr_...", // opaque member id — fetch PII via the API
"tier": "Gold", // present on loyalty / tier events
"amount_band": "100-500" // coarse band on commerce events — never the exact amount
}
}验证每个签名
Flash 使用 标准 Webhook (HMAC-SHA256),通过端点的 whsec_ 密钥对每个交付进行签名。重新计算 {webhook-id}.{webhook-timestamp}.{body} 的签名,并在恒定时间内将其与 webhook-signature 标头进行比较。拒绝任何不匹配的交付,并拒绝过旧而无法防御重放攻击的 webhook-timestamp 密钥。根据规范,签名密钥是 whsec_ 前缀之后的密钥的 base64 解码字节。在密钥轮换期间,标头包含多个以空格分隔的 v1,… 值——如果 中有任何 匹配,则接受交付。
import crypto from "node:crypto";
// req.body must be the RAW, unparsed request body string.
function verifyFlashWebhook(headers, rawBody, whsec) {
const id = headers["webhook-id"];
const ts = headers["webhook-timestamp"];
const sigHeader = headers["webhook-signature"]; // "v1,<b64> v1,<b64> ..."
// reject stale deliveries (5 min tolerance)
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
const key = Buffer.from(whsec.replace(/^whsec_/, ""), "base64");
const expected = crypto
.createHmac("sha256", key)
.update(`${id}.${ts}.${rawBody}`)
.digest("base64");
return sigHeader
.split(" ")
.map((p) => p.split(",")[1])
.some((sig) =>
sig &&
sig.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected)),
);
}重试、幂等性和交付日志
快速回款 2xx
使用任何 2xx 进行确认,并异步执行您的工作。 429、5xx、超时或连接错误均被视为可重试。
自动重试并退避
可重试的失败会采用指数级全抖动退避策略进行重试。3xx 或非 429 的 4xx 会被视为永久性失败,不会重试。
通过 webhook-id 进行去重
id 在重试中始终相同,因此至少一次交付是安全的:将重复的 webhook-id 视为已处理。
发送日志和回放
每次尝试都会被记录。如果重试次数用尽,则该次投递将被标记为失败;您可以在“设置”→“Webhooks”中查看和重放投递记录。