ДокументацияВебхуки
Вебхуки
Настройте уведомления о готовом транскрипте и ошибках. Проверяйте подпись входящих запросов и обрабатывайте повторную доставку.
Чтобы не опрашивать статус, укажите webhook_url при создании задачи. Когда транскрибация завершится успешно или с ошибкой, transcript1 отправит на этот адрес подписанный POST.
Настроить вебхук можно только через REST API. Инструменты MCP-сервера не принимают этот параметр.
Куда отправлять уведомления
При отправке ссылки на видео или создании загрузки по частям добавьте webhook_url в JSON для POST /v1/transcriptions. Если загружаете файл одним запросом, передайте его полем формы:
{
"platform": "youtube",
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"webhook_url": "https://example.com/hooks/transcript1"
}Укажите HTTPS-ссылку без логина и пароля. Она должна разрешаться в публичный адрес: частные, loopback, link-local и другие непубличные адреса дадут 422 invalid_request. Проверяем адрес и при создании задачи, и перед каждой отправкой вебхука.
В одном запросе нельзя передавать webhook_url и reuse_recent=true.
События
type | Что произошло |
|---|---|
transcription.completed | Транскрипт готов, статус задачи — completed. |
transcription.failed | Задача завершилась с failed. Код и описание ошибки находятся в data.error, в полях code и message. |
Если вы отменили транскрибацию, уведомления не будет.
Тело запроса
Уведомление приходит в формате JSON:
{
"type": "transcription.completed",
"created_at": "2026-10-13T09:30:04.000Z",
"data": {
"id": "tr_8fK2Lm0PqRsTuVwXyZaBcD",
"object": "transcription",
"status": "completed",
"transcript_source": "youtube_captions",
"billed_minutes": 4,
"error": null
}
}В created_at указано время события, а в data — объект транскрибации без самого текста, то есть без transcript. Выше приведён сокращённый пример. Возьмите id из data и получите транскрипт запросом GET /v1/transcriptions/:id.
В заголовках будут Content-Type: application/json, User-Agent: transcript1-webhooks/1.0 и подпись. Ниже — как её проверить.
Как проверить подпись
С каждым вебхуком передаём заголовок X-Transcript1-Signature:
X-Transcript1-Signature: t=<unix seconds>,v1=<hex digest>t— Unix-время подписания запроса, в секундах.v1— шестнадцатеричный HMAC-SHA256 для строки"<t>.<raw body>". Соберите её изt, точки и тела запроса без каких-либо изменений. В качестве ключа возьмите секрет подписи целиком, с префиксомwhsec_.
Проверяйте подпись по исходным байтам тела, до разбора JSON. После разбора и обратной сериализации могут измениться пробелы или порядок ключей — подпись уже не совпадёт. Сравнение должно занимать постоянное время. Проверяйте и t: слишком большое расхождение с текущим временем — повод отклонить запрос. В примерах ниже допустимое расхождение составляет 5 минут.
Node.js
import { Buffer } from 'node:buffer';
import crypto from 'node:crypto';
// header: X-Transcript1-Signature, rawBody: the request body exactly as received,
// secret: your full signing secret including the whsec_ prefix.
export const verifySignature = (secret, header, rawBody, nowSeconds = Math.floor(Date.now() / 1000)) => {
const fields = Object.fromEntries(header.split(',').map(part => part.trim().split('=')));
const t = Number(fields.t);
if (!Number.isInteger(t) || !fields.v1 || Math.abs(nowSeconds - t) > 300) {
return false;
}
const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
return fields.v1.length === expected.length
&& crypto.timingSafeEqual(Buffer.from(fields.v1), Buffer.from(expected));
};В Express сначала прочитайте тело как текст, не подключая разбор JSON:
import process from 'node:process';
import express from 'express';
import { verifySignature } from './verify-signature.js';
const app = express();
app.post('/hooks/transcript1', express.text({ type: 'application/json' }), (request, response) => {
const header = request.get('X-Transcript1-Signature') ?? '';
if (!verifySignature(process.env.TRANSCRIPT1_WEBHOOK_SECRET, header, request.body)) {
response.sendStatus(400);
return;
}
const { type, data } = JSON.parse(request.body);
console.log(type, data.id, data.status);
response.sendStatus(200);
});Python
import hashlib
import hmac
import time
def verify_signature(secret, header, raw_body, now=None):
# header: the X-Transcript1-Signature header, raw_body: the request body exactly as received,
# secret: your full signing secret including the whsec_ prefix.
fields = dict(part.strip().split("=", 1) for part in header.split(",") if "=" in part)
try:
t = int(fields["t"])
except (KeyError, ValueError):
return False
now = int(time.time()) if now is None else now
if abs(now - t) > 300:
return False
expected = hmac.new(secret.encode(), f"{t}.{raw_body}".encode(), hashlib.sha256).hexdigest()
return hmac.compare_digest(fields.get("v1", ""), expected)В Flask получите исходное тело через request.get_data(as_text=True) и передайте его в raw_body.
Где взять секрет подписи
У команды один секрет для вебхуков всех её транскрибаций. Он начинается с whsec_; посмотреть или заменить его можно на странице «Webhooks» (/dashboard/webhooks). После замены старый секрет сразу перестаёт действовать. Все последующие вебхуки подписываем новым.
Доставка и повторные попытки
- Верните любой код
2xx, чтобы подтвердить получение вебхука. - Другой статус, ошибка соединения или отсутствие ответа за 10 секунд означают, что попытка не удалась.
- При неудаче повторяем отправку с растущими паузами: 2, 4, 8, 16 и 32 минуты. Всего будет не больше 6 попыток, последняя — примерно через час после первой.
- Сразу указывайте конечный URL. transcript1 не следует редиректам: ответ
3xxтоже считается неудачей. - Один вебхук может прийти несколько раз — например, если ваш сервер ответил уже после таймаута. Отсеивайте дубликаты по сочетанию
data.idиtype.
Дата обновления 14 октября 2026 г.