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.

submit.sh
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

L'hôte du callback est résolu et vérifié avant l'acceptation de la tâche : une adresse privée ou de loopback fait donc échouer la soumission elle-même — pas l'envoi une heure plus tard. Pendant le développement, faites pointer webhook_url vers un tunnel public, ou omettez-le et interrogez.

#En-têtes et charge utile

En-têteEnvoyé quandUtilisation
X-Xiaoxiang-SignatureUniquement 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-EventChaque 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-IdChaque 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.
ChampTypePrésenceDescription
event_idstringToujoursIdentique à X-Xiaoxiang-Event-Id. Dédupliquez sur cette valeur.
eventstringToujours"task.completed" ou "task.failed".
task_idstringToujoursL'id renvoyé par l'appel de soumission — votre clé de jointure.
statusstringToujoursStatut final dans le vocabulaire vidéo — "completed" ou "failed", même pour les tâches d'image et de musique.
modelstringSi connuModèle ayant produit la tâche. Omis s'il est inconnu.
urlstringEn cas de succèsURL du résultat si la tâche a réussi. Préférez /content pour un lien de plateforme stable.
fail_reasonstringEn cas d'échecPrésent uniquement en cas d'échec.
quotaintegerToujoursQuota réellement consommé, en unités internes. Faites le rapprochement avec le journal d'utilisation, pas avec ce champ.
created_atintegerToujoursSecondes Unix, soumission de la tâche.
finished_atintegerToujoursSecondes Unix, passage à l'état final.
delivery.json
{
  "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

Exécutez un traitement à basse fréquence sur les tâches encore non finales dans votre base de données — un déploiement pendant une fenêtre d'envoi suffit à en manquer une. Interrogez l'endpoint de statut, corrigez la ligne, et considérez le webhook uniquement comme un gain de latence.

Toujours bloqué ?

Envoyez-nous l'ID de requête ou le message d'erreur, et nous examinerons le problème.