Webhook
获取 API 密钥注册 webhook
注册一个公开的 https 地址和你想接收的事件。签名密钥(whsec_…)只会出现在这次响应中。一个应用最多可有 10 个 webhook。
curl -X POST https://api.sofia-post.com/api/v1/webhooks \
-H "Authorization: Bearer $SOFIA_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"url":"https://app.example.com/hooks/sofia","events":["post.published","post.failed"]}'事件
| 事件 | data |
|---|---|
post.published | postId, establishmentId, platform, publishedAt, permalink |
post.failed | postId, establishmentId, platform, error |
webhook.test | webhookId |
Sofia 以 POST 方式发送每个事件,内容为 JSON { id, type, createdAt, data },并带有 Sofia-Event-Id、Sofia-Event-Type 和 Sofia-Signature 请求头。只会通知由你的应用创建的帖子。
验证签名
Sofia-Signature 的格式为 t=<unix time>,v1=<hex>,其中 v1 是用你的密钥对 <t>.<raw body> 计算的 HMAC-SHA256。请在原始请求体上验证,并拒绝过旧的时间戳:
import { createHmac, timingSafeEqual } from 'node:crypto';
const isSignedBySofia = (header, rawBody, secret) => {
const parts = Object.fromEntries(
(header ?? '').split(',').map((part) => part.trim().split('=', 2)),
);
const timestamp = Number(parts.t);
if (!Number.isInteger(timestamp) || typeof parts.v1 !== 'string') return false;
if (Math.abs(Date.now() / 1000 - timestamp) > 300) return false;
const expected = createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest();
const received = Buffer.from(parts.v1, 'hex');
return received.length === expected.length && timingSafeEqual(received, expected);
};投递与重试
- 返回
2xx即视为事件已送达。其他响应、重定向(不会跟随)或 10 秒内无响应,都会在 1 分钟、5 分钟、30 分钟和 2 小时后重试,最多 5 次。 - 同一事件可能到达两次:请根据
Sofia-Event-Id去重。 - 尽快响应,事后再处理事件。
POST /v1/webhooks/{id}/test 会发送一个 webhook.test 事件,签名方式与其他事件相同。