Intégralo en tu app
Callbacks de resultados
Los webhooks reducen la latencia entre que una tarea termina y tu app se entera. Son una optimización de entrega: tres intentos y luego el gateway deja de intentarlo. El endpoint de estado sigue siendo la fuente de verdad.
#Registra el callback al enviar la tarea
Pasa webhook_url en la solicitud de envío. Añade webhook_secret y cada entrega irá firmada — sin él, ninguna lo 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"
}'Debe ser enrutable públicamente
localhost, 127.0.0.1, los rangos privados, las direcciones link-local y las de metadatos de la nube se rechazan al enviar la tarea — no en el momento de la entrega. Usa un túnel durante el desarrollo.
http o https, resoluble en el momento del envío
Se resuelve el hostname y se comprueba cada dirección devuelta. Un nombre que no se resuelve hace fallar el envío.
La entrega es de mejor esfuerzo
Tres reintentos; después el gateway se detiene y solo lo registra. El endpoint de estado sigue siendo la fuente de verdad.
localhost se rechaza al enviar la tarea
#Encabezados y payload
| Encabezado | Cuándo se envía | Qué hacer con él |
|---|---|---|
| X-Xiaoxiang-Signature | Solo cuando la tarea se envió con webhook_secret. | sha256=<HMAC-SHA256 en hex del cuerpo sin procesar>. Verifícalo antes de parsear, con una comparación de tiempo constante. |
| X-Xiaoxiang-Event | En cada entrega. | task.completed o task.failed — enruta según este valor en lugar de deducir la intención a partir del payload. |
| X-Xiaoxiang-Event-Id | En cada entrega. | Estable entre reintentos del mismo evento. Guárdalo y descarta los repetidos — es tu clave de deduplicación. |
| Campo | Tipo | Presente | Descripción |
|---|---|---|---|
| event_id | string | Siempre | Coincide con X-Xiaoxiang-Event-Id. Deduplica según este valor. |
| event | string | Siempre | "task.completed" o "task.failed". |
| task_id | string | Siempre | El id que devuelve la llamada de envío — tu clave para relacionar registros. |
| status | string | Siempre | Estado final en el vocabulario de video — "completed" o "failed", incluso en tareas de imagen y música. |
| model | string | Cuando se conoce | Modelo que generó la tarea. Se omite si se desconoce. |
| url | string | Si tiene éxito | URL del resultado cuando la tarea tuvo éxito. Prefiere /content para obtener un enlace estable de la plataforma. |
| fail_reason | string | Si falla | Solo presente en caso de fallo. |
| quota | integer | Siempre | Cuota realmente consumida, en unidades internas. Concilia con el registro de uso, no con este campo. |
| created_at | integer | Siempre | Segundos Unix, envío de la tarea. |
| finished_at | integer | Siempre | Segundos Unix, paso a estado 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
}#Verifica el cuerpo de la solicitud sin procesar
Calcula el HMAC-SHA256 sobre los bytes exactos que recibiste, antes de parsear el JSON — volver a serializar el payload cambia los bytes y rompe la comparación. Compara en tiempo constante y rechaza si no coincide.
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();
}#Entrega y deduplicación
Responde en 15 segundos
Ese es el timeout de entrega. Cualquier respuesta más lenta cuenta como intento fallido, así que encola el trabajo y devuelve primero un 2xx.
Deduplica por el id del evento
X-Xiaoxiang-Event-Id es estable entre reintentos. Guárdalo y descarta los repetidos antes de tocar el estado de negocio.
Espera tres reintentos y luego silencio
Las entregas fallidas se reintentan a los 5 s, 30 s y 2 min. Después, el gateway solo lo registra — no llegará nada más.
#Mantén un proceso de consulta para conciliar
La entrega de webhooks es de mejor esfuerzo
¿Sigues con el problema?
Envíanos el ID de solicitud o el mensaje de error y lo revisaremos.