Webhook 最佳实践
快速响应,稍后处理
Section titled “快速响应,稍后处理”验证签名,将事件入队(数据库记录、队列、后台任务),然后立即返回 2xx。SpacePost 最多等待 15 秒,处理程序过慢会导致重试和重复投递。
投递至少会发生一次。请保存每次已处理投递的 webhook-id(唯一约束就足够了),并跳过重复的投递。
const inserted = await db.insert(processedWebhooks).values({ id: webhookId }).onConflictDoNothing().returning();if (inserted.length === 0) return new Response("duplicate", { status: 200 });始终验证签名
Section titled “始终验证签名”使用原始请求体,拒绝时间戳超过 5 分钟的请求,并以恒定时间比较签名(SDK 和各类库都会这样做)。参见验证签名。
不要依赖顺序
Section titled “不要依赖顺序”事件可能乱序到达,尤其是在重试之后。请将载荷中的 created_at 与你已存储的时间比较,并且只应用能让状态向前推进的更改。SpacePost 自身的 last_event 也遵循同样的规则。
处理重要的事件
Section titled “处理重要的事件”| 事件 | 典型处理方式 |
|---|---|
email.bounced |
在你的数据库中将该地址标记为无效,并停止向它发送。 |
email.complained |
立即让该用户退订非必要邮件。 |
email.delivery_delayed |
通常无需处理;如果是时效性很强的邮件(重置密码),则发出告警。 |
email.failed |
记录 data.failed.reason,发出告警,必要时修复后重试。 |
email.suppressed |
有收件人在你的抑制列表中;请在你的管理界面中展示。 |
只订阅你会处理的事件。事件越少,流量越小,失败也越少。
保护端点安全
Section titled “保护端点安全”- 使用有效证书,通过 HTTPS 提供服务。
- 不要记录密钥或包含个人数据的完整载荷。
- 使用专用路径,不承担其他功能,并返回通用的响应体。
- 将
signing_secret保存在密钥管理器中,而不是源代码仓库里。
请关注控制台中的投递日志,或对反复失败设置告警:如果你的端点宕机超过约 27 小时,所有重试次数都会用完,之后你就必须手动重放。