Практическое руководство для разработчиков
Проверка подписи webhook video API: как принять callback без подмены
Проверка подписи webhook video API нужна потому, что callback приходит из сети, а не из доверенной очереди вашего приложения. Если обработчик сразу меняет статус задачи по любому JSON-запросу, злоумышленник или случайный повтор может отметить чужое видео готовым, запустить выдачу ссылки или перегрузить базу. Надёжная схема сохраняет исходное тело, проверяет подпись по актуальному контракту, устраняет дубли и передаёт работу в очередь. Нельзя угадывать заголовок или формат: поддержка webhook зависит от конкретной модели и конфигурации RussiaAPI.
RUSSIAAPI_API_KEY на сервере; не передавайте внешние ключи, cookie, пароли, коды подтверждения или лишние персональные данные.Уточните контракт до написания обработчика
Сначала проверьте, доступны ли callback для нужного video-сценария, как называется событие, какой URL регистрации разрешён, где находится ID доставки и как формируется подпись. Это относится к текущей документации RussiaAPI и вашему договору; OpenAI-совместимый endpoint сам по себе не означает, что webhook существуют или имеют единый формат. Если callback не поддержан, используйте допустимый polling со сроком ожидания, а не выдумывайте заголовки.
Хороший контракт описывает raw body, кодировку, время события, идентификатор доставки, алгоритм подписи, секрет и стратегию повторов. Полезно сразу задать, что является подтверждением доставки: обычно быстрый контролируемый 2xx после устойчивого принятия события, а не после тяжёлой обработки видео. Не включайте секрет webhook в URL, query-параметр, клиентский код или общий лог. Он хранится только в серверном secret store, отдельно от ключа RussiaAPI.
Почему нужен raw body и постоянное время сравнения
Подпись обычно вычисляется от точной последовательности байтов. Если framework уже разобрал JSON и затем сериализовал объект заново, пробелы, порядок полей или кодировка могут отличаться — корректная подпись не пройдёт. Поэтому middleware должен сохранить raw body до JSON-парсинга. Затем сервер вычисляет ожидаемое значение по алгоритму из договора и сравнивает его функцией постоянного времени, чтобы не выдавать лишний сигнал о частичном совпадении секрета.
Ниже пример для Node.js 18+ показывает HMAC-SHA-256 как шаблон application-level проверки. Используйте его только если ваш актуальный webhook-контракт действительно задаёт этот алгоритм, кодировку и заголовок x-webhook-signature. Имена заголовка и формат могут отличаться, поэтому их нельзя переносить в production без сверки. Значение WEBHOOK_SIGNING_SECRET — ваш секрет callback; это не upstream key и его тоже нельзя логировать.
import { createHmac, timingSafeEqual } from 'node:crypto';
import { createServer } from 'node:http';
const seen = new Set(); // В production: уникальная запись delivery_id в БД.
const sign = (raw) => createHmac('sha256', process.env.WEBHOOK_SIGNING_SECRET).update(raw).digest('hex');
createServer(async (req, res) => {
if (req.method !== 'POST' || req.url !== '/video-callback') return res.writeHead(404).end();
const chunks = []; for await (const chunk of req) chunks.push(chunk);
const raw = Buffer.concat(chunks);
const received = String(req.headers['x-webhook-signature'] || ''); // Сверьте имя с вашим контрактом.
const expected = sign(raw);
if (received.length !== expected.length || !timingSafeEqual(Buffer.from(received), Buffer.from(expected))) {
return res.writeHead(401).end();
}
const event = JSON.parse(raw.toString('utf8'));
if (typeof event.delivery_id !== 'string' || seen.has(event.delivery_id)) return res.writeHead(204).end();
seen.add(event.delivery_id);
// Здесь поместите event.delivery_id в устойчивую очередь до ответа 202.
res.writeHead(202, { 'content-type': 'application/json' }).end(JSON.stringify({ accepted: true }));
}).listen(3000);Обработчик подтверждает событие только после верификации и сохраняет delivery ID в демонстрационный набор. В production дедупликацию сделайте уникальной записью в БД или очереди: память процесса исчезает при рестарте и не защищает несколько экземпляров. Полезно также ограничить размер тела, IP-политику лишь как дополнительный слой и deadline на приём.
Отделите подлинность от бизнес-статуса
Корректная подпись доказывает только то, что сообщение соответствует контракту секретного канала. Она не даёт право выдать результат первому пользователю, который знает task ID. После приёма найдите внутреннюю операцию, проверьте tenant и допустимый переход состояния: например, queued → processing → completed. Не принимайте переход назад из completed в processing только из-за позднего повторного callback.
Содержимое события всё равно валидируйте: тип, task ID, разрешённый status, безопасный URL результата и срок его действия. Не выводите поля callback в HTML без экранирования и не передавайте его как prompt модели. Пользователь получает результат через ваш авторизованный маршрут, который сверяет владельца операции и срок, а не прямую веру в данные сети. Эта граница дополняет правила работы с контентом видео.
Повторы, таймауты и наблюдаемость
Доставка может повториться из-за timeout, сетевой ошибки или ответа 5xx. Ваша система должна быть идемпотентна по delivery ID и, если он доступен, по task ID плюс типу события. Не отвечайте 2xx до того, как событие можно восстановить: иначе вы подтвердите доставку, а затем потеряете его при сбое процесса. Но и не запускайте рендеринг, скачивание или тяжёлый анализ синхронно в handler — быстрый durable enqueue уменьшает повторную нагрузку.
В метриках достаточно хранить безопасный ID доставки, внутренний ID операции, тип события, итог проверки, время и число дублей. Подпись, raw body, URL с токеном, файлы и заголовки авторизации не должны попадать в общие логи. Если событие не проходит проверку, верните нейтральный 401 или 400, зафиксируйте краткий код причины и проверьте настройку секрета в защищённом контуре. Для самой видео-задачи также полезна идемпотентность создания.
Чек-лист callback для video API
- Поддержка webhook, формат подписи и заголовок подтверждены текущим контрактом, а не взяты из чужого примера.
- Server-side handler сохраняет raw body и сравнивает подпись постоянным временем.
- Delivery ID защищён уникальной записью, которая переживает рестарт и несколько экземпляров.
- После проверки система валидирует переход статуса и права tenant, а не только подпись.
- Handler быстро сохраняет событие в очередь; тяжёлая обработка и выдача результата идут отдельно.
Подпись не предназначена для обхода доступа, лимитов или условий сервиса. Если callback недоступен или не согласован, покажите пользователю честный статус и применяйте разрешённый способ проверки задачи.
Проверьте сценарий в RussiaAPI
Создайте собственный тестовый ключ в консоли, проверьте текущий каталог моделей и контракт callback, затем выполните минимальный server-side тест на синтетических данных. Расширяйте доступ и нагрузку только после измеримой проверки.
Открыть консоль RussiaAPIFAQ
Всегда ли video API присылает webhook?
Нет. Наличие callback, события, заголовки и повторы зависят от текущего контракта конкретного сервиса и модели. Если webhook не поддержан, используйте разрешённый polling и не имитируйте событие.
Можно ли проверить подпись после JSON.parse?
Нежелательно. Подпись часто строится по исходным байтам, а повторная сериализация меняет представление данных. Сначала сохраните raw body, проверьте подпись, затем разбирайте JSON.
Достаточно ли подписи, чтобы выдать видео?
Нет. После проверки подписи нужно сверить внутреннюю задачу, tenant, допустимый статус и право пользователя на результат. Подпись подтверждает канал сообщения, а не разрешение на доступ.