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. 1
    前往 控制台 → Webhooks → 添加 Webhook
  2. 2
    输入 您的端点 URL — 必须通过 HTTPS 公开访问
  3. 3
    选择 您想接收的事件
  4. 4
    复制 签名密钥 — 仅显示一次,请安全存储
  5. 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-Signaturev1={hmac_sha256}用于验证请求来源的 HMAC-SHA256 签名
X-Authon-TimestampISO 8601事件发送时的 ISO 8601 时间戳
X-Authon-Eventuser.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。
Authon — Universal Authentication Platform