Integre ao seu app

Callbacks de resultado

Os webhooks reduzem a latência entre o fim de uma tarefa e o momento em que o seu app fica sabendo. São uma otimização de entrega: três tentativas, e depois o gateway para de tentar. O endpoint de status continua sendo a fonte da verdade.

#Registre o callback no momento do envio

Passe webhook_url na requisição de envio. Adicione webhook_secret e toda entrega é assinada — sem ele, nenhuma é.

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"
  }'

Precisa ser roteável publicamente

localhost, 127.0.0.1, faixas privadas, link-local e endereços de metadados de nuvem são rejeitados quando a tarefa é enviada — não no momento da entrega. Use um túnel durante o desenvolvimento.

http ou https, resolvível no momento do envio

O hostname é resolvido e cada endereço retornado é verificado. Um nome que não resolve faz o envio falhar.

A entrega é de melhor esforço

Três novas tentativas; depois o gateway para e apenas registra em log. O endpoint de status continua sendo a fonte da verdade.

localhost é rejeitado quando a tarefa é enviada

O host do callback é resolvido e verificado antes de a tarefa ser aceita, então um endereço privado ou de loopback faz o próprio envio falhar — não a entrega uma hora depois. Durante o desenvolvimento, aponte webhook_url para um túnel público ou omita-o e faça consultas.

#Cabeçalhos e payload

CabeçalhoEnviado quandoO que fazer com ele
X-Xiaoxiang-SignatureSomente quando a tarefa foi enviada com webhook_secret.sha256=<HMAC-SHA256 em hex do corpo bruto>. Verifique antes de fazer o parse, com comparação em tempo constante.
X-Xiaoxiang-EventToda entrega.task.completed ou task.failed — roteie por este valor em vez de deduzir a intenção pelo payload.
X-Xiaoxiang-Event-IdToda entrega.Estável entre os reenvios do mesmo evento. Armazene-o e descarte repetições — esta é sua chave de deduplicação.
CampoTipoPresenteDescrição
event_idstringSempreIgual a X-Xiaoxiang-Event-Id. Faça a deduplicação por ele.
eventstringSempre"task.completed" ou "task.failed".
task_idstringSempreO id retornado pela chamada de envio — sua chave de junção.
statusstringSempreStatus final no vocabulário de vídeo — "completed" ou "failed", mesmo para tarefas de imagem e música.
modelstringQuando conhecidoModelo que gerou a tarefa. Omitido quando desconhecido.
urlstringEm caso de sucessoURL do resultado quando a tarefa teve sucesso. Prefira /content para um link estável da plataforma.
fail_reasonstringEm caso de falhaPresente apenas em caso de falha.
quotaintegerSempreCota efetivamente consumida, em unidades internas. Faça a conciliação com o log de uso, não com este campo.
created_atintegerSempreSegundos Unix, envio da tarefa.
finished_atintegerSempreSegundos Unix, transição para o estado 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
}

#Verifique o corpo bruto da requisição

Calcule o HMAC-SHA256 sobre os bytes exatos que você recebeu, antes de qualquer parse de JSON — serializar o payload de novo muda os bytes e quebra a comparação. Compare em tempo constante e rejeite se não bater.

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 e deduplicação

Confirme em até 15 segundos

Esse é o timeout da entrega. Qualquer coisa mais lenta conta como tentativa falha, então coloque o trabalho em fila e retorne 2xx primeiro.

Deduplique pelo id do evento

X-Xiaoxiang-Event-Id é estável entre as novas tentativas. Armazene-o e descarte repetições antes de mexer no estado do negócio.

Espere três novas tentativas e depois silêncio

Entregas que falham são repetidas após 5s, 30s e 2min. Depois disso o gateway só registra em log — nada mais vai chegar.

#Mantenha um verificador de conciliação

A entrega do webhook é de melhor esforço

Rode um job de baixa frequência sobre as tarefas que ainda não estão em estado final no seu banco de dados — um deploy durante uma janela de entrega basta para perder uma. Consulte o endpoint de status, corrija a linha e trate o webhook apenas como um ganho de latência.

Ainda com problemas?

Envie para nós o ID da requisição ou a mensagem de erro e vamos investigar.