Вебхуки
Вебхуки Slicer API — события о загрузках, монтаже, экспорте и публикациях, подпись HMAC-SHA256 и её проверка на Node.js и Python, повторы доставки.
На этой странице
Вебхук — адрес вашего сервера, на который Slicer сам отправляет событие, когда что-то произошло: загрузка готова, монтаж закончился, ролик опубликован. Это быстрее и экономнее опроса: не нужно спрашивать статус каждые 5 секунд, и запросы не расходуют лимит.
Добавить вебхук
Нужна область webhooks. Адрес — только публичный https; до 5 вебхуков на аккаунт.
curl https://api.slicer.media/v1/webhooks \
-H "Authorization: Bearer $SLICER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com/slicer/webhook","events":["project.succeeded","project.failed","export.succeeded"],"description":"Уведомления в рабочий чат"}'{
"ok": true,
"data": {
"webhook": {
"id": "5f8e2a10-6c3b-4d7e-9f21-0a1b2c3d4e5f",
"url": "https://example.com/slicer/webhook",
"events": ["project.succeeded", "project.failed", "export.succeeded"],
"description": "Уведомления в рабочий чат",
"enabled": true,
"disabledReason": null,
"failures": 0,
"lastDeliveryAt": null,
"createdAt": "2026-10-16T09:30:00.000Z"
},
"secret": "whsec_…"
}
}secret — ключ подписи. Он показывается один раз: сохраните его в переменной окружения вашего сервера, например
SLICER_WEBHOOK_SECRET. Вебхуки можно добавлять и в приложении: Профиль → «Для разработчиков» → «Вебхуки».
События
| Событие | Когда приходит | data |
|---|---|---|
upload.ready | загрузка проверена и готова к монтажу (в том числе импорт по адресу) | загрузка |
upload.failed | файл не принят | загрузка с причиной в error |
project.succeeded | монтаж, пересборка или повтор завершились | проект |
project.failed | запуск не получился, резерв токенов вернулся | проект с error |
project.cancelled | запуск остановлен | проект |
project.version_ready | версии ролика ждут выбора — приходит после project.succeeded, только если монтаж отдал несколько версий. Сейчас монтаж отдаёт одну, и этого события нет: ждите project.succeeded | проект с versionChoice |
export.succeeded | экспорт готов, файл обновлён | проект |
export.failed | экспорт не получился, прежний ролик сохранён | проект с error |
publication.published | ролик опубликован в соцсети | публикация с url и projectId |
publication.failed | публикация не удалась | публикация с errorCode и projectId |
ping | проверка вебхука (POST /v1/webhooks/{id}/test) — отправляется сразу, без повторов | webhookId и message |
Список событий может пополняться (Версии и совместимость): пропускайте незнакомые типы, отвечая
2xx.
Формат запроса
Slicer отправляет POST с JSON-телом и служебными заголовками:
POST /slicer/webhook HTTP/1.1
Content-Type: application/json
User-Agent: Slicer-Webhooks/1.0 (+https://docs.slicer.media/api/webhooks/)
Slicer-Signature: t=1792152060,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
Slicer-Event: project.succeeded
Slicer-Delivery: 48213
{
"id": "evt_9a3c1f0e7b2d4c58a1e6f03b7d9c2e41",
"type": "project.succeeded",
"createdAt": "2026-10-16T09:41:00.000Z",
"data": { "id": 318, "title": "Монтаж за 3 минуты", "status": "succeeded", "appUrl": "https://app.slicer.media/projects/318" }
}id— уникальный id события (evt_…); по нему отбрасывайте повторы.type— тип события, он же — в заголовкеSlicer-Event.data— объект в том же виде, что отдаёт API (GET /v1/projects/{id},GET /v1/uploads/{id}или публикация).Slicer-Delivery— номер доставки: по нему доставку легко найти в журналеGET /v1/webhooks/{id}/deliveries.
Проверка подписи
Подпись доказывает, что запрос пришёл от Slicer и не изменён по дороге. Заголовок Slicer-Signature содержит время
отправки t (Unix-секунды) и подпись v1 — HMAC-SHA256 в hex от строки <t>.<тело запроса> на ключе secret.
Как проверять:
- Возьмите сырое тело запроса — байты как пришли, до разбора JSON. После разбора и повторной сериализации подпись не сойдётся.
- Посчитайте HMAC-SHA256 от
t, точки и тела на своём секрете. - Сравните с
v1за постоянное время (timingSafeEqual,hmac.compare_digest), а не обычным==. - Отклоните запрос, если
tотличается от текущего времени больше чем на 5 минут, — это защищает от повторной отправки перехваченного запроса.
// Node.js 20+: npm install express
import { createHmac, timingSafeEqual } from 'node:crypto';
import express from 'express';
const TOLERANCE_SEC = 5 * 60;
/** true, если подпись верна и запрос не старше 5 минут. */
export function verifySlicerSignature(rawBody, header, secret, nowMs = Date.now()) {
if (typeof header !== 'string') return false;
const pairs = header.split(',').map((part) => part.trim().split('='));
const timestamp = pairs.find(([key]) => key === 't')?.[1];
const signatures = pairs.filter(([key]) => key === 'v1').map(([, value]) => value);
if (!timestamp || !/^\d+$/.test(timestamp) || signatures.length === 0) return false;
if (Math.abs(nowMs / 1000 - Number(timestamp)) > TOLERANCE_SEC) return false;
const expected = createHmac('sha256', secret).update(`${timestamp}.`).update(rawBody).digest();
return signatures.some((value) => {
const received = Buffer.from(value, 'hex');
return received.length === expected.length && timingSafeEqual(received, expected);
});
}
const app = express();
const seen = new Set(); // в продакшене — база или Redis
// express.raw: тело приходит Buffer-ом, ровно как его подписал Slicer
app.post('/slicer/webhook', express.raw({ type: 'application/json' }), (req, res) => {
if (!verifySlicerSignature(req.body, req.get('Slicer-Signature'), process.env.SLICER_WEBHOOK_SECRET)) {
return res.status(400).send('invalid signature');
}
const event = JSON.parse(req.body.toString('utf8'));
res.sendStatus(204); // ответить быстро, обработать после
if (seen.has(event.id)) return;
seen.add(event.id);
setImmediate(() => {
if (event.type === 'project.succeeded') console.log(`Ролик готов: ${event.data.title} — ${event.data.appUrl}`);
});
});
app.listen(3000);# pip install flask
import hashlib
import hmac
import json
import os
import time
from flask import Flask, abort, request
TOLERANCE_SEC = 5 * 60
def verify_slicer_signature(raw_body, header, secret, now=None):
"""True, если подпись верна и запрос не старше 5 минут."""
if not header:
return False
pairs = [part.strip().split("=", 1) for part in header.split(",") if "=" in part]
timestamp = next((value for key, value in pairs if key == "t"), None)
signatures = [value for key, value in pairs if key == "v1"]
if timestamp is None or not timestamp.isdigit() or not signatures:
return False
if abs((now or time.time()) - int(timestamp)) > TOLERANCE_SEC:
return False
expected = hmac.new(secret.encode(), timestamp.encode() + b"." + raw_body, hashlib.sha256).hexdigest()
return any(hmac.compare_digest(expected, signature) for signature in signatures)
app = Flask(__name__)
seen = set() # в продакшене — база или Redis
@app.post("/slicer/webhook")
def slicer_webhook():
raw = request.get_data() # сырое тело, до разбора JSON
if not verify_slicer_signature(raw, request.headers.get("Slicer-Signature"), os.environ["SLICER_WEBHOOK_SECRET"]):
abort(400)
event = json.loads(raw)
if event["id"] not in seen:
seen.add(event["id"])
if event["type"] == "project.succeeded":
print(f"Ролик готов: {event['data']['title']} — {event['data']['appUrl']}")
return "", 204Доставка и повторы
- Успех — ответ
2xxне дольше чем за 10 секунд. Отвечайте сразу, а долгую работу делайте после ответа или в очереди. - Повторы — при другом ответе, таймауте или ошибке соединения: через 1 минуту, 5 минут, 30 минут, 2 часа, 6 часов и 12 часов — всего до 7 попыток.
- Перенаправления не выполняются. Ответ
3xxсчитается неудачной доставкой — указывайте окончательный адрес. - Порядок не гарантирован, а одно событие может прийти дважды (например, если ваш ответ потерялся). Отбрасывайте
повторы по
id, а актуальное состояние при сомнениях берите из API. - Отключение — после 50 неудачных попыток доставки подряд вебхук выключается (
enabled: false, причина вdisabledReason), на почту аккаунта приходит письмо. Исправьте сервер и включите вебхук:PATCH /v1/webhooks/{id}с{"enabled": true}. - Адрес проверяется при создании и перед каждой отправкой: если он перестал указывать на публичный адрес, доставки не будет.
Проверка и журнал
# отправить тестовое событие ping
curl -X POST https://api.slicer.media/v1/webhooks/$WEBHOOK_ID/test -H "Authorization: Bearer $SLICER_TOKEN"
# последние 100 доставок: статус, код ответа, попытки
curl https://api.slicer.media/v1/webhooks/$WEBHOOK_ID/deliveries -H "Authorization: Bearer $SLICER_TOKEN"Изменить адрес, события или описание — PATCH /v1/webhooks/{id}, удалить — DELETE. Все эндпоинты — в
справочнике.