L'intégrer à votre application
Callbacks de résultat
Les webhooks réduisent le délai entre la fin d'une tâche et le moment où votre application le sait. C'est une optimisation de l'envoi : trois tentatives, puis la passerelle abandonne. L'endpoint de statut reste la source de vérité.
#Enregistrer le callback lors de la soumission
Transmettez webhook_url dans la requête de soumission. Ajoutez webhook_secret et chaque envoi est signé — sans lui, aucun ne l'est.
curl https://www.aisplice.store/v1/videos \
-H "Authorization: Bearer sk-YOUR_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: video-job-8842" \
-d '{
"model": "doubao-seedance-2.5",
"prompt": "A cinematic product scene",
"webhook_url": "https://your.app/hooks/ai-tasks",
"webhook_secret": "whsec_replace_me"
}'Doit être routable publiquement
localhost, 127.0.0.1, les plages privées, les adresses link-local et de métadonnées cloud sont rejetés lors de la soumission de la tâche — pas au moment de l'envoi. Utilisez un tunnel pendant le développement.
http ou https, résolvable au moment de la soumission
Le nom d'hôte est résolu et chaque adresse renvoyée est vérifiée. Un nom qui ne se résout pas fait échouer la soumission.
Envoi sans garantie
Trois nouvelles tentatives, puis la passerelle abandonne et se contente de journaliser. L'endpoint de statut reste la source de vérité.
localhost est rejeté lors de la soumission de la tâche
#En-têtes et charge utile
| En-tête | Envoyé quand | Utilisation |
|---|---|---|
| X-Xiaoxiang-Signature | Uniquement si la tâche a été soumise avec webhook_secret. | sha256=<HMAC-SHA256 hexadécimal du corps brut>. Vérifiez avant de parser, avec une comparaison à temps constant. |
| X-Xiaoxiang-Event | Chaque envoi. | task.completed ou task.failed — routez sur cette valeur au lieu de déduire l'intention à partir de la charge utile. |
| X-Xiaoxiang-Event-Id | Chaque envoi. | Stable entre les nouvelles tentatives d'un même événement. Stockez-le et ignorez les doublons — c'est votre clé de déduplication. |
| Champ | Type | Présence | Description |
|---|---|---|---|
| event_id | string | Toujours | Identique à X-Xiaoxiang-Event-Id. Dédupliquez sur cette valeur. |
| event | string | Toujours | "task.completed" ou "task.failed". |
| task_id | string | Toujours | L'id renvoyé par l'appel de soumission — votre clé de jointure. |
| status | string | Toujours | Statut final dans le vocabulaire vidéo — "completed" ou "failed", même pour les tâches d'image et de musique. |
| model | string | Si connu | Modèle ayant produit la tâche. Omis s'il est inconnu. |
| url | string | En cas de succès | URL du résultat si la tâche a réussi. Préférez /content pour un lien de plateforme stable. |
| fail_reason | string | En cas d'échec | Présent uniquement en cas d'échec. |
| quota | integer | Toujours | Quota réellement consommé, en unités internes. Faites le rapprochement avec le journal d'utilisation, pas avec ce champ. |
| created_at | integer | Toujours | Secondes Unix, soumission de la tâche. |
| finished_at | integer | Toujours | Secondes Unix, passage à l'état final. |
{
"event_id": "task_8f21c...:task.completed",
"event": "task.completed",
"task_id": "8f21c0d2-...",
"status": "completed",
"model": "doubao-seedance-2.5",
"url": "https://cdn.example.com/renders/8f21c0d2.mp4",
"quota": 128000,
"created_at": 1789000000,
"finished_at": 1789000184
}#Vérifier le corps brut de la requête
Calculez le HMAC-SHA256 sur les octets exacts reçus, avant tout parsing JSON — resérialiser la charge utile modifie les octets et fausse la comparaison. Comparez en temps constant et rejetez en cas de différence.
import crypto from "node:crypto";
// Express: express.raw({ type: "application/json" }) — not express.json().
export function handleTaskWebhook(req, res) {
const received = req.get("X-Xiaoxiang-Signature") ?? "";
const expected =
"sha256=" +
crypto.createHmac("sha256", process.env.WEBHOOK_SECRET)
.update(req.body) // Buffer of the raw body
.digest("hex");
const a = Buffer.from(received);
const b = Buffer.from(expected);
if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
return res.status(401).end();
}
const event = JSON.parse(req.body.toString("utf8"));
// Deduplicate first, acknowledge fast, then do the work off-request.
if (!seen.add(req.get("X-Xiaoxiang-Event-Id"))) return res.status(200).end();
queue.push(event);
res.status(200).end();
}#Envoi et déduplication
Répondre sous 15 secondes
C'est le délai d'expiration de l'envoi. Au-delà, la tentative compte comme un échec : mettez le travail en file et renvoyez d'abord un 2xx.
Dédupliquer sur l'id d'événement
X-Xiaoxiang-Event-Id est stable d'une tentative à l'autre. Stockez-le et ignorez les doublons avant de toucher à l'état métier.
Trois nouvelles tentatives, puis plus rien
Les envois échoués sont retentés après 5 s, 30 s et 2 min. Ensuite, la passerelle se contente de journaliser — rien d'autre n'arrivera.
#Garder un processus d'interrogation de rapprochement
L'envoi des webhooks n'est pas garanti
Toujours bloqué ?
Envoyez-nous l'ID de requête ou le message d'erreur, et nous examinerons le problème.