Webhook
Thay vì polling, hãy cung cấp webhook_url cho một lần chạy. Khi yêu cầu đạt trạng thái cuối cùng (succeeded, failed hoặc canceled), chúng tôi gửi một POST có chữ ký đến URL đó.
curl -X POST "https://api.doitong.com/v1/run/{model_id}?webhook_url=https://example.com/hooks/doitong" \
-H "Authorization: Bearer $DOITONG_API_KEY" \
-H "Content-Type: application/json" \
-d '{"prompt": "..."}'
Sự kiện
POST /hooks/doitong HTTP/1.1
Content-Type: application/json
User-Agent: Doitong-Webhooks/1
Doitong-Request-Id: req_8k2m4q7x1z9c3v5b6n0p2r4t
Doitong-Signature: t=1760961609,v1=5f1c0d6e2b…
{
"id": "evt_8k2m4q7x1z9c3v5b6n0p2r4t_succeeded",
"object": "event",
"type": "request.succeeded",
"created_at": "2026-10-20T12:00:09Z",
"data": {
"id": "req_8k2m4q7x1z9c3v5b6n0p2r4t",
"object": "request",
"status": "succeeded",
"outputs": [ { "type": "video", "url": "https://content.doitong.com/dev-api/…/0-output.mp4" } ]
}
}
typelàrequest.succeeded,request.failedhoặcrequest.canceled.datalà đối tượng yêu cầu đầy đủ, chính xác như kết quả củaGET /requests/{id}.idổn định cho một yêu cầu và trạng thái cuối cùng của yêu cầu đó. Dùng id này để bỏ qua lần gửi đã xử lý.
Gửi và thử lại
Trả lời bằng bất kỳ trạng thái 2xx nào trong vòng 10 giây. Mọi phản hồi khác (hoặc hết thời gian) sẽ được thử lại: tối đa 6 lần, sau 30 giây, 2 phút, 10 phút, 1 giờ và 6 giờ. Hãy thực hiện công việc chậm sau khi trả lời, không phải trước đó.
Xác minh chữ ký
Mỗi khóa có một bí mật ký riêng. Đọc bí mật này một lần từ GET https://api.doitong.com/v1/account (webhook_signing_secret, bắt đầu bằng whsec_) và lưu cùng với khóa.
Header Doitong-Signature có dạng t=<unix time>,v1=<signature>, trong đó chữ ký là HMAC-SHA256 dạng hex của "<t>.<raw request body>", dùng bí mật của bạn làm khóa. Để xác minh:
- Lấy chính xác các byte của phần thân thô như đã nhận (trước khi phân tích JSON).
- Tính HMAC-SHA256 của
t + "." + bodybằng bí mật của bạn, dưới dạng hex chữ thường. - So sánh với
v1trong thời gian không đổi. - Từ chối sự kiện khi
tcách đồng hồ của bạn hơn 5 phút (bảo vệ chống phát lại).
Bộ nhận hoàn chỉnh bằng Node.js (Express) và Python (Flask):
import crypto from "node:crypto";
import express from "express";
const SECRET = process.env.DOITONG_WEBHOOK_SECRET; // whsec_...
const app = express();
app.post("/hooks/doitong", express.raw({ type: "application/json" }), (req, res) => {
const header = req.get("Doitong-Signature") || "";
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=", 2)));
const t = Number(parts.t);
const expected = crypto.createHmac("sha256", SECRET).update(t + "." + req.body).digest("hex");
const given = Buffer.from(parts.v1 || "", "utf8");
const valid =
given.length === expected.length &&
crypto.timingSafeEqual(given, Buffer.from(expected, "utf8")) &&
Math.abs(Date.now() / 1000 - t) < 300;
if (!valid) return res.status(400).send("bad signature");
const event = JSON.parse(req.body);
if (event.type === "request.succeeded") {
console.log(event.data.id, event.data.outputs.map((o) => o.url));
}
res.sendStatus(204);
});
import hashlib
import hmac
import json
import os
import time
from flask import Flask, abort, request
SECRET = os.environ["DOITONG_WEBHOOK_SECRET"].encode() # whsec_...
app = Flask(__name__)
@app.post("/hooks/doitong")
def doitong_hook():
body = request.get_data() # raw bytes
parts = dict(p.split("=", 1) for p in request.headers.get("Doitong-Signature", "").split(",") if "=" in p)
t = parts.get("t", "0")
expected = hmac.new(SECRET, t.encode() + b"." + body, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, parts.get("v1", "")) or abs(time.time() - int(t)) > 300:
abort(400)
event = json.loads(body)
if event["type"] == "request.succeeded":
print(event["data"]["id"], [o["url"] for o in event["data"]["outputs"]])
return "", 204
Mẹo
- Phản hồi nhanh và xếp quy trình xử lý riêng của bạn vào hàng đợi.
- Bí mật thuộc về khóa API đã khởi chạy lần chạy. Các khóa trong những dịch vụ khác nhau có bí mật khác nhau.