Webhooks
Webhooks
当项目中发生认证事件时接收实时 HTTP 通知。在用户注册、登录或被封禁时,将用户同步到您的数据库、发送欢迎邮件或触发工作流。
什么是 Webhooks?
Webhook 是您的服务器注册的 HTTP 回调,用于接收实时事件通知。与其轮询 Authon 检查用户是否被删除或封禁,不如让 Authon 在事件发生的瞬间主动向您的 URL 推送 POST 请求。
text
在 Authon 控制台中删除用户
→ Authon 发送 POST /webhooks/authon
→ 您的服务器接收事件并从数据库中移除该用户Webhooks 工作原理
当您的 Authon 项目中发生事件时,API 会向您注册的端点发送带有 JSON 载荷的 POST 请求。每个请求包含带有 v1= 前缀的 X-Authon-Signature 头部 — 这是使用您的 Webhook 签名密钥对 timestamp.body 计算的 HMAC-SHA256 签名。在处理事件前请务必验证此签名。
1
Authon 中发生事件(例如:用户注册)2
Authon 使用您的 Webhook 密钥以 HMAC-SHA256 对载荷进行签名3
Authon 向您的端点 URL 发送 POST 请求4
您的服务器验证签名并处理事件5
您的服务器以 2xx 响应确认接收配置 Webhook
- 1前往 控制台 → Webhooks → 添加 Webhook
- 2输入 您的端点 URL — 必须通过 HTTPS 公开访问
- 3选择 您想接收的事件
- 4复制 签名密钥 — 仅显示一次,请安全存储
- 5设置 服务器环境中的 AUTHON_WEBHOOK_SECRET
事件类型
| 事件 | 触发条件 |
|---|---|
| user.created | 新用户完成注册 |
| user.updated | 用户资料被更新 |
| user.deleted | 用户账户被永久删除 |
| user.signin | 用户通过邮箱/密码或 OAuth 成功登录 |
| user.signout | 用户主动退出登录 |
| user.banned | 管理员封禁了用户 |
| user.unbanned | 解除了用户的封禁 |
| session.created | 新会话被创建 — 登录或令牌刷新时 |
| session.revoked | 会话被撤销 — 退出登录或管理员强制撤销时 |
载荷格式
每个 Webhook 载荷都是具有统一顶层结构的 JSON 对象。 data 字段包含特定事件的详细信息。
json
{
"event": "user.created",
"data": {
"user": {
"id": "usr_abc123",
"email": "user@example.com",
"displayName": "Jane Doe",
"emailVerified": false,
"isBanned": false,
"publicMetadata": null,
"createdAt": "2026-01-15T10:30:00.000Z",
"updatedAt": "2026-01-15T10:30:00.000Z"
}
},
"timestamp": "2026-01-15T10:30:00.000Z"
}对于session.*事件, data字段还包含一个带有 id、ipAddress 和 userAgent 的 session对象。
签名验证
每个 Webhook 请求包含格式为 X-Authon-Signature 的 v1=<hex_digest>. 头部。签名是对 timestamp.rawBody 使用您的 Webhook 密钥计算的 HMAC-SHA256。请使用时间安全比较来验证结果。
| 头部 | 格式 | 描述 |
|---|---|---|
| X-Authon-Signature | v1={hmac_sha256} | 用于验证请求来源的 HMAC-SHA256 签名 |
| X-Authon-Timestamp | ISO 8601 | 事件发送时的 ISO 8601 时间戳 |
| X-Authon-Event | user.created | 事件类型(例如 user.created、session.revoked) |
验证时请务必使用 原始请求体 Buffer 而非解析后的 JSON 字符串。Express 的
json() 中间件会重新序列化请求体,可能改变空白字符并破坏签名。Node.js 示例
routes/webhooks.ts
import { createHmac, timingSafeEqual } from "crypto";
import express from "express";
const router = express.Router();
function verifySignature(
rawBody: Buffer,
signature: string,
secret: string,
timestamp: string,
): boolean {
const payload = `${timestamp}.${rawBody.toString()}`;
const expected = createHmac("sha256", secret)
.update(payload)
.digest("hex");
const actual = signature.replace("v1=", "");
const expectedBuf = Buffer.from(expected, "hex");
const actualBuf = Buffer.from(actual, "hex");
return (
expectedBuf.length === actualBuf.length &&
timingSafeEqual(expectedBuf, actualBuf)
);
}
router.post(
"/webhooks/authon",
express.raw({ type: "application/json" }),
(req, res) => {
const signature = req.headers["x-authon-signature"] as string;
const timestamp = req.headers["x-authon-timestamp"] as string; // ISO 8601
const eventType = req.headers["x-authon-event"] as string;
const webhookSecret = process.env.AUTHON_WEBHOOK_SECRET!;
if (!verifySignature(req.body, signature, webhookSecret, timestamp)) {
return res.status(401).json({ error: "Invalid signature" });
}
const { event, data } = JSON.parse(req.body.toString());
switch (eventType) {
case "user.created":
// Sync new user to your database
break;
case "user.deleted":
// Remove user from your database
break;
case "user.updated":
// Update user data
break;
case "user.banned":
// Revoke app-level access
break;
}
res.status(200).json({ received: true });
}
);
export default router;使用 SDK
@authon/node SDK 提供内置的验证辅助函数,为您处理时间安全比较:
ts
import { AuthonBackend } from "@authon/node";
const authon = new AuthonBackend(process.env.AUTHON_SECRET_KEY!);
router.post(
"/webhooks/authon",
express.raw({ type: "application/json" }),
(req, res) => {
const signature = req.headers["x-authon-signature"] as string;
const timestamp = req.headers["x-authon-timestamp"] as string;
let event: Record<string, unknown>;
try {
event = authon.webhooks.verify(
req.body,
signature,
timestamp,
process.env.AUTHON_WEBHOOK_SECRET!,
);
} catch {
return res.status(401).json({ error: "Invalid signature" });
}
console.log("Verified event:", event.event);
res.json({ received: true });
}
);重试策略
如果您的端点返回非 2xx 状态码或在 10秒内未响应,Authon 将以指数退避方式重试投递。最多重试3次。
| 次数 | 延迟 | 累计时间 |
|---|---|---|
| 初始 | — | 0s |
| 第1次重试 | 1 second | ~1s |
| 第2次重试 | 2 seconds | ~3s |
使用场景
同步用户到您的数据库
保持您的应用用户表与 Authon 同步。监听 user.created、user.updated 和 user.deleted 以自动镜像更改。
发送欢迎邮件
当 user.created 触发时,发送欢迎邮件或入门引导序列。使用事件载荷中的用户邮箱和显示名称。
实时执行封禁
当 user.banned 触发时,立即撤销应用级访问令牌或缓存条目,使用户无法继续使用您的服务。
最佳实践
始终验证签名
切勿跳过签名验证。任何人都可以向您的端点发送 POST 请求 — 验证签名可证明请求来自 Authon。
使用原始请求体解析
签名验证需要请求体的精确原始字节。避免在 Webhook 路由上使用 JSON 中间件。
快速响应,异步处理
立即返回 200 并异步处理事件。如果您的端点处理时间超过10秒,Authon 将进行重试。
使处理器具有幂等性
由于重试,您可能多次收到相同事件。使用事件时间戳进行去重 — 在数据库中存储已处理的事件。
定期轮换密钥
每90天从控制台生成新的签名密钥。在不停机的情况下更新部署中的 AUTHON_WEBHOOK_SECRET。