Chuyển đến nội dung

Tài liệu API · Tác vụ

Webhook và xác minh chữ ký

Định dạng sự kiện, xác minh X-Signature và cơ chế gửi lại.

Truyền callback_url khi tạo tác vụ; khi tác vụ đến trạng thái cuối, nền tảng sẽ POST một sự kiện: task.succeeded hoặc task.failed. Trường data của sự kiện là đối tượng tác vụ đầy đủ, giống hệt kết quả của endpoint truy vấn.

Khóa bí mật ký

Tạo khóa bí mật ký Webhook tại Bảng điều khiển · Cài đặt tài khoản. Khóa chỉ hiển thị một lần; sau khi xoay vòng khóa, khóa cũ và khóa mới cùng có hiệu lực trong 24 giờ để bạn chuyển đổi mà không gián đoạn dịch vụ.

Xác minh chữ ký

Sau khi tạo khóa bí mật ký, mọi yêu cầu callback đều có header chữ ký; khi chưa tạo khóa, callback không có chữ ký. Trong môi trường production, hãy luôn tạo khóa và xác minh chữ ký:

X-Signature: t=<giây unix>,v1=<hex>

trong đó v1 = hex(HMAC_SHA256(secret, t + "." + raw_body)). Các bước xác minh:

  1. Tách t và tất cả các v1 từ header: trong 24 giờ sau khi xoay vòng khóa, header sẽ có đồng thời hai v1 của khóa mới và khóa cũ; chỉ cần một giá trị khớp là hợp lệ.
  2. Ghép t + "." + body bằng nội dung yêu cầu gốc, chưa phân tích cú pháp, rồi tính HMAC-SHA256.
  3. So sánh với từng v1 bằng phép so sánh thời gian hằng định (constant-time).
  4. Kiểm tra t lệch không quá 5 phút so với thời điểm hiện tại để chống tấn công phát lại.

Gửi và gửi lại

  • Bất kỳ phản hồi 2xx nào cũng được coi là gửi thành công; mỗi yêu cầu có thời gian chờ 10 giây, hãy phản hồi nhanh và đưa các xử lý tốn thời gian vào hàng đợi để chạy bất đồng bộ. Chuyển hướng (3xx) không được theo và bị coi là thất bại.
  • Sau lần gửi đầu tiên thất bại (không phải 2xx hoặc hết thời gian chờ), sự kiện sẽ được gửi lại sau 1 phút, 5 phút, 30 phút, 2 giờ và 12 giờ, sau đó dừng hẳn; kết quả tác vụ không bị ảnh hưởng và vẫn có thể truy vấn.
  • callback_url phải là địa chỉ http / https truy cập được từ Internet công cộng.
  • Một sự kiện có thể được gửi nhiều lần, hãy loại trùng theo id (evt_…).
  • Sự kiện có thể đến không theo thứ tự; hãy lấy trạng thái của đối tượng tác vụ làm chuẩn.
import crypto from "node:crypto";
import express from "express";

const app = express();
const SECRET = process.env.AITOKEN_WEBHOOK_SECRET;

// Phải xác minh chữ ký trên body gốc, không JSON.parse rồi tuần tự hóa lại
app.post("/hooks/video", express.raw({ type: "application/json" }), (req, res) => {
  const header = req.get("X-Signature") ?? ""; // t=1730259000,v1=5f2c...[,v1=chữ ký bằng khóa cũ]
  const pairs = header.split(",").map((kv) => kv.split("="));
  const t = Number(pairs.find(([k]) => k === "t")?.[1]);
  if (!t || Math.abs(Date.now() / 1000 - t) > 300) return res.status(400).end(); // cửa sổ thời gian 5 phút

  const expected = Buffer.from(
    crypto.createHmac("sha256", SECRET).update(`${t}.${req.body.toString("utf8")}`).digest("hex"),
  );
  // Trong 24 giờ sau khi xoay khóa sẽ có hai v1 (mới, cũ), khớp một trong hai là hợp lệ
  const ok = pairs
    .filter(([k, v]) => k === "v1" && v?.length === expected.length)
    .some(([, v]) => crypto.timingSafeEqual(Buffer.from(v), expected));
  if (!ok) return res.status(400).end();

  const event = JSON.parse(req.body.toString("utf8"));
  // Khử trùng lặp theo event.id; trả 2xx thật nhanh, việc tốn thời gian đưa vào hàng đợi
  res.status(204).end();
});