خطافات الويب
بدلاً من الاستطلاع، زوّد التنفيذ بـ webhook_url. عندما يصل الطلب إلى حالة نهائية (succeeded أو failed أو
canceled)، نرسل طلب POST موقّعًا إلى ذلك العنوان.
curl -X POST "https://api.doitong.ru/v1/run/{model_id}?webhook_url=https://example.com/hooks/doitong" \
-H "Authorization: Bearer $DOITONG_API_KEY" \
-H "Content-Type: application/json" \
-d '{"prompt": "..."}'
الحدث
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" } ]
}
}
- يشير
typeإلىrequest.succeededأوrequest.failedأوrequest.canceled. - يحتوي
dataعلى كائن الطلب الكامل، وهو مطابق تمامًا لما يعيدهGET /requests/{id}. - يكون
idثابتًا للطلب وحالته النهائية. استخدمه لتجاهل عملية تسليم سبق لك معالجتها.
عمليات التسليم وإعادة المحاولة
أجب بأي حالة 2xx خلال 10 ثوانٍ. يُعاد إرسال أي شيء آخر (أو عند انتهاء المهلة): بحد أقصى 6 محاولات إجمالًا،
بعد 30 ثانية ودقيقتين و10 دقائق وساعة واحدة و6 ساعات. نفّذ العمل البطيء بعد الإجابة، لا قبلها.
التحقّق من التوقيع
لكل مفتاح سر توقيع خاص به. اقرأه مرة واحدة من GET https://api.doitong.ru/v1/account (webhook_signing_secret،
ويبدأ بـ whsec_) وخزّنه بجوار المفتاح.
يكون ترويس Doitong-Signature بالشكل t=<unix time>,v1=<signature>، حيث يكون التوقيع هو قيمة
HMAC-SHA256 السداسية عشرية لـ "<t>.<raw request body>" باستخدام سرك كمفتاح. للتحقّق:
- خذ وحدات بايت النص الأساسي الخام كما استُلمت تمامًا (قبل أي تحليل JSON).
- احسب HMAC-SHA256 للقيمة
t + "." + bodyباستخدام سرك، بصيغة سداسية عشرية بأحرف صغيرة. - قارنها مع
v1في وقت ثابت. - ارفض الحدث عندما يكون الفارق بين
tوساعتك أكثر من 5 دقائق (للحماية من إعادة التشغيل).
مستقبِل مكتمل في Node.js (Express) و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
نصائح
- أجب بسرعة وضع المعالجة الخاصة بك في قائمة انتظار.
- السر مرتبط بمفتاح API الذي بدأ التنفيذ. للمفاتيح الموجودة في خدمات مختلفة أسرار مختلفة.