Webhooks
Au lieu d’effectuer des sondages, fournissez une webhook_url à une exécution. Lorsque la requête atteint un état final (succeeded, failed ou
canceled), nous envoyons un POST signé à cette 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": "..."}'
L’événement
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" } ]
}
}
typeestrequest.succeeded,request.failedourequest.canceled.dataest l’objet de requête complet, exactement comme celui que renvoieGET /requests/{id}.idest stable pour une requête et son état final. Utilisez-le pour ignorer une livraison que vous avez déjà traitée.
Livraisons et nouvelles tentatives
Répondez avec n’importe quel état 2xx dans les 10 secondes. Toute autre réponse (ou un délai d’attente dépassé) entraîne une nouvelle tentative : jusqu’à 6 tentatives au total,
après 30 secondes, 2 minutes, 10 minutes, 1 heure et 6 heures. Effectuez les tâches lentes après avoir répondu, et non avant.
Vérifier la signature
Chaque clé possède son propre secret de signature. Lisez-le une seule fois à partir de GET https://api.doitong.com/v1/account (webhook_signing_secret,
il commence par whsec_) et stockez-le avec la clé.
L’en-tête Doitong-Signature est t=<unix time>,v1=<signature>, où la signature est le HMAC-SHA256 hexadécimal de "<t>.<raw request body>",
avec votre secret comme clé. Pour effectuer la vérification :
- Prenez les octets bruts du corps exactement tels qu’ils ont été reçus (avant toute analyse JSON).
- Calculez le HMAC-SHA256 de
t + "." + bodyavec votre secret, en hexadécimal minuscule. - Comparez-le avec
v1en temps constant. - Refusez l’événement lorsque
test à plus de 5 minutes de l’heure de votre horloge (protection contre les rejeux).
Un récepteur complet en Node.js (Express) et 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
Conseils
- Répondez rapidement et placez votre propre traitement en file d’attente.
- Le secret appartient à la clé API qui a démarré l’exécution. Les clés de différents services possèdent des secrets différents.