Встраивание в ваше приложение
Вебхуки с результатом
Вебхуки сокращают время между завершением задачи и моментом, когда о нём узнаёт ваше приложение. Это оптимизация доставки: три попытки, после чего шлюз прекращает их. Источником истины остаётся эндпоинт статуса.
#Регистрируйте обратный вызов при отправке
Передайте webhook_url в запросе на отправку. Добавьте webhook_secret, и каждая доставка будет подписана, — без него подписи не будет.
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"
}'Адрес должен быть публичным
localhost, 127.0.0.1, частные диапазоны, link-local-адреса и адреса облачных метаданных отклоняются при отправке задачи, а не при доставке. Во время разработки используйте туннель.
http или https, имя разрешается в момент отправки
Имя хоста разрешается, и проверяется каждый полученный адрес. Если имя не разрешается, отправка завершается ошибкой.
Доставка не гарантируется
Три повторные попытки, затем шлюз прекращает доставку и только пишет в лог. Источником истины остаётся эндпоинт статуса.
localhost отклоняется ещё при отправке задачи
#Заголовки и тело
| Заголовок | Когда передаётся | Что с ним делать |
|---|---|---|
| X-Xiaoxiang-Signature | Только если задача отправлена с webhook_secret. | sha256=<hex HMAC-SHA256 от исходного тела>. Проверяйте до разбора, сравнением за постоянное время. |
| X-Xiaoxiang-Event | При каждой доставке. | task.completed или task.failed — ориентируйтесь на это значение, а не определяйте тип события по содержимому. |
| X-Xiaoxiang-Event-Id | При каждой доставке. | Не меняется между повторными доставками одного события. Сохраняйте его и отбрасывайте повторы — это ваш ключ дедупликации. |
| Поле | Тип | Когда есть | Описание |
|---|---|---|---|
| event_id | string | Всегда | Совпадает с X-Xiaoxiang-Event-Id. Дедуплицируйте по нему. |
| event | string | Всегда | "task.completed" или "task.failed". |
| task_id | string | Всегда | ID, возвращённый при отправке задачи, — ваш ключ для сопоставления. |
| status | string | Всегда | Конечный статус в терминах видео — "completed" или "failed", даже для задач с изображениями и музыкой. |
| model | string | Если известно | Модель, выполнившая задачу. Отсутствует, если неизвестна. |
| url | string | При успехе | URL результата, если задача выполнена успешно. Для стабильной ссылки платформы лучше использовать /content. |
| fail_reason | string | При ошибке | Только при ошибке. |
| quota | integer | Всегда | Фактически израсходованная квота во внутренних единицах. Для сверки используйте журнал использования, а не это поле. |
| created_at | integer | Всегда | Время отправки задачи, Unix-секунды. |
| finished_at | integer | Всегда | Время перехода в конечное состояние, Unix-секунды. |
{
"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
}#Проверяйте исходное тело запроса
Вычисляйте HMAC-SHA256 по точным полученным байтам до любого разбора JSON — повторная сериализация меняет байты и ломает сравнение. Сравнивайте за постоянное время и отклоняйте при несовпадении.
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();
}#Доставка и дедупликация
Отвечайте в течение 15 секунд
Это тайм-аут доставки. Более медленный ответ считается неудачной попыткой, поэтому сначала поставьте обработку в очередь и верните 2xx.
Дедуплицируйте по ID события
X-Xiaoxiang-Event-Id не меняется между повторами. Сохраняйте его и отбрасывайте повторы до изменения бизнес-данных.
Три повтора, затем тишина
Неудачные доставки повторяются через 5 с, 30 с и 2 мин. После этого шлюз только записывает событие в лог — больше ничего не придёт.
#Оставьте опрос для сверки
Доставка вебхуков не гарантируется
Проблема не решена?
Пришлите нам ID запроса или текст ошибки, и мы разберёмся.