Webhooks
Geef een run een webhook_url in plaats van te pollen. Wanneer de request een definitieve status bereikt (succeeded, failed of
canceled), versturen we een ondertekende POST naar die 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": "..."}'
De event
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" } ]
}
}
typeisrequest.succeeded,request.failedofrequest.canceled.datais het volledige request-object, precies watGET /requests/{id}retourneert.idis stabiel voor een request en diens definitieve status. Gebruik dit om een levering die je al hebt verwerkt te negeren.
Leveringen en retries
Antwoord binnen 10 seconden met een status in 2xx. Al het andere (of een time-out) wordt opnieuw geprobeerd: maximaal 6 pogingen in totaal,
na 30 seconden, 2 minuten, 10 minuten, 1 uur en 6 uur. Voer traag werk na je antwoord uit, niet ervoor.
De handtekening verifiëren
Elke sleutel heeft een eigen ondertekeningsgeheim. Lees dit één keer uit met GET https://api.doitong.com/v1/account (webhook_signing_secret,
het begint met whsec_) en bewaar het naast de sleutel.
De Doitong-Signature-header is t=<unix time>,v1=<signature>, waarbij de handtekening de hexadecimale
HMAC-SHA256 is van "<t>.<raw request body>", met je geheim als sleutel. Zo verifieer je deze:
- Neem de onbewerkte body-bytes exact over zoals ontvangen (vóór JSON-parsing).
- Bereken HMAC-SHA256 van
t + "." + bodymet je geheim, als hexadecimale tekst in kleine letters. - Vergelijk deze in constante tijd met
v1. - Weiger de event wanneer
tmeer dan 5 minuten van je klok afwijkt (replaybeveiliging).
Een complete receiver in Node.js (Express) en 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
Tips
- Reageer snel en zet je eigen verwerking in een wachtrij.
- Het geheim hoort bij de API-sleutel die de run heeft gestart. Sleutels in verschillende services hebben verschillende geheimen.