Перейти к основному контенту

Руководство по реализации

Довести задачу до конца, когда запроса уже нет.

Медиазадача переживает HTTP-вызов, который её создал, страницу, которая её запустила, и нередко тот деплой, что шёл в это время. Всё дальнейшее — про то, как это пережить.

Задача

Соединение — не транзакция

Между отправкой и готовым файлом есть четыре места, где теряется работа: вслепую повторённый вызов создания после таймаута, обратный вызов, который нельзя проверить, цикл опроса, который долбит или сдаётся, и ссылка на результат, истёкшая, пока вы решали, что с ней делать.

01

Повтор без ключа — это второй счёт

Idempotency-Key делает так, что повтор той же модели, того же ввода и того же адреса обратного вызова в течение 24 часов возвращает исходную задачу и не резервирует средства дважды. Область действия — один ключ API; соседний ключ с тем же значением получит 409 и не получит идентификатор задачи.

02

Непроверенный обратный вызов — это открытая дверь

Доставки несут X-Webhook-Timestamp и Base64-подпись HMAC-SHA256 в X-Webhook-Signature, вычисленную по taskId.timestamp.sha256(raw_body). Сверяйте по сырым байтам за постоянное время и отклоняйте устаревшие отметки до изменения состояния задачи или списаний.

03

Опрос без потолка — это заявка на ограничение частоты

recordInfo — это резервный путь, а не транспорт. Начните примерно с двух секунд, увеличивайте в полтора раза, остановитесь на пятнадцати и завершайте по сроку, который выбрали вы, а не сеть.

Подход

Одна долговечная запись на задачу, записанная до ответа

Сохраняйте ключ идемпотентности, taskId и собственный идентификатор корреляции в той же транзакции, в которой отвечаете вызывающей стороне. Любое последующее событие — вызов, опрос, повтор, вопрос поддержки — разрешается в эту строку, и повторная доставка становится обновлением, а не вторым заказом.

Сначала проверка, потом чтение. Подпись покрывает сырые байты, поэтому разбор идёт после сравнения.

Verify the callback before you trust it

import { createHash, createHmac, timingSafeEqual } from "node:crypto";

// rawBody is the exact bytes that arrived. Parse only to read the task ID:
// the digest is always computed over those raw bytes, never over a re-encode.
export function verifyDelivery(rawBody, headers, webhookKey) {
  const timestamp = headers["x-webhook-timestamp"];
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;

  const taskId = JSON.parse(rawBody).data.taskId;          // payload version 2
  const digest = createHash("sha256").update(rawBody).digest("hex");
  const expected = createHmac("sha256", webhookKey)
    .update(`${taskId}.${timestamp}.${digest}`)
    .digest("base64");

  const sent = Buffer.from(headers["x-webhook-signature"] ?? "", "utf8");
  const mine = Buffer.from(expected, "utf8");
  return sent.length === mine.length && timingSafeEqual(sent, mine);
}

Delivery headers

X-Webhook-Timestamp: 1788652800
X-Webhook-Signature: <base64>
X-Webhook-Payload-Version: 2

{
  "code": 200,
  "msg": "success",
  "request_id": "whd_01k3m8x9q2z4v7n5p6r8s0t1w3",
  "data": { "taskId": "job_01k3…", "state": "succeeded", "settled": true, … }
}

Контракт

Что стоит зафиксировать сразу

Асинхронное медиа-API: задачи, вебхуки, повторы — Что стоит зафиксировать сразу
Поле или заголовокЗначение или предел
Idempotency-Key24 h · scoped to one API key
X-Webhook-Signaturebase64(HMAC-SHA256)
signed stringtaskId.timestamp.sha256(raw_body)
X-Webhook-Payload-Version2 for new integrations

Проведите по горизонтали, чтобы увидеть все столбцы.

Интеграция

От первого вызова до продакшена

  1. 01Создайте и сохраните ключ идемпотентности, затем вызовите createTask с ним и с callBackUrl на HTTPS-адрес по порту 80 или 443.
  2. 02Запишите data.taskId в базу до ответа вызывающей стороне: 202 — единственное место, где он появляется первым.
  3. 03Примите обратный вызов, проверьте подпись по сырому телу, отсейте дубли по request_id и обновите запись.
  4. 04Держите recordInfo с экспоненциальной паузой как резерв и копируйте output.assets до истечения подписанной ссылки.

Применимость

Когда этот путь подходит

Выбирайте, если

  • Всё, что производит медиа. Эндпоинты изображений, видео и звука асинхронны по устройству.
  • Нужна долговечная запись о том, что отправлено, сколько это должно было стоить и чем закончилось.

Ищите другое, если

  • Вы вызываете текстовую модель: она отдаёт поток внутри запроса и рассчитывается по измеренному потреблению.
  • Вы хотите отдать задачу браузеру. Ключи принадлежат серверу, и запись о задаче тоже.

Проверки

Что вы можете проверить сами

В каждом ответе есть request_id

Конверт у всех операций /api/v1 — {code, msg, data, request_id}. В версии полезной нагрузки 2 request_id доставки не меняется при повторной отправке, поэтому он и есть естественный ключ дедупликации.

Резервы видны в записи

cost при settled: false — это зарезервированная оценка, settled: true — итоговое списание. Успех никогда не превышает резерв, а ошибка или истечение срока освобождают его полностью.

Отказы описаны закрытым словарём

errorCode принимает одно из девяти стабильных значений: invalid_request, unsupported_combination, content_rejected, rate_limited, upstream_unavailable, generation_failed, timeout, invalid_asset, upstream_failed. Любое другое значение трактуйте как upstream_failed.

Перед запуском

Поведение платформы, которое стоит подтвердить

  • Храните ключ идемпотентности вместе с задачей, а не в памяти процесса.
  • Сделайте обработчик обратного вызова идемпотентным: доставки повторяются по замыслу.
  • Задайте срок опроса и оповещайте о задачах, которые его превысили.
  • Копируйте файлы до expiresAt и запрашивайте ссылку заново, а не кешируйте подписанную.

Частые вопросы

О чём спрашивают на этой странице

Повторите тот же запрос с тем же Idempotency-Key. В течение 24 часов и с тем же ключом API вы получите исходную задачу без второго резерва. Никогда не отправляйте новое тело после таймаута: это новая задача с отдельным списанием.

Дальше

Построить поток задач, переживающий деплой

Отправляйте долгие медиазадачи с сервера и надёжно доводите до конца: ключи идемпотентности, подписанные вызовы, ограниченный опрос, пять состояний.

Другие руководства