# Webhooks

Получайте уведомления о завершении задач без polling'а

Webhooks — это HTTP POST-запросы, которые IskraGen отправляет на ваш URL, когда задача завершается (успехом или неудачей). Полезно для длинных генераций — видео, музыка, аудио — чтобы не опрашивать `GET /v1/generations/:id` в цикле.

## Зачем использовать

Три основных use case'а:

- **Уведомление пользователя о завершении.** Запустили рендер видео на 30 секунд → ушли по своим делам → webhook прилетел → отправили push / email / in-app сообщение «готово».
- **Запись результата в свою БД.** При `generation.succeeded` получаете финальный URL медиа из payload, сохраняете в свою таблицу или копируете на свой S3 для долговременного хранения.
- **Триггер пайплайна пост-обработки.** Сразу после генерации запускаете апскейл, лицензирование, водяной знак, монтаж в общий ролик — без задержки на polling.

## Как создать webhook

Управление endpoint'ами происходит через API. UI в личном кабинете пока в разработке.

### 1. Создать endpoint

```bash
curl https://api.iskragen.ru/v1/webhook-endpoints \
  -H "Authorization: Bearer $ISKRAGEN_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-app.com/webhooks/iskragen",
    "eventTypes": ["generation.succeeded", "generation.failed"]
  }'
```

Ответ `201`:

```json
{
  "id": "01HQ...uuid",
  "url": "https://your-app.com/webhooks/iskragen",
  "secret": "whsec_BASE64SECRET...",
  "eventTypes": ["generation.succeeded", "generation.failed"]
}
```

**Сохраните `secret` сразу** — он показывается только один раз. Им вы будете проверять подпись входящих запросов.

### 2. Список endpoint'ов

```bash
curl https://api.iskragen.ru/v1/webhook-endpoints \
  -H "Authorization: Bearer $ISKRAGEN_KEY"
```

### 3. Ротация секрета

```bash
curl -X POST https://api.iskragen.ru/v1/webhook-endpoints/<id>/rotate-secret \
  -H "Authorization: Bearer $ISKRAGEN_KEY"
```

Старый секрет инвалидируется немедленно — переключите проверку в коде до ротации или сразу после.

### 4. Удаление

```bash
curl -X DELETE https://api.iskragen.ru/v1/webhook-endpoints/<id> \
  -H "Authorization: Bearer $ISKRAGEN_KEY"
```

## События

- `generation.succeeded` — задача завершилась успешно. В payload — финальный URL результата.
- `generation.failed` — задача провалилась окончательно (после всех retry). Списанные средства возвращены на баланс автоматически.

## Подпись

Каждый запрос подписан по схеме [Standard Webhooks](https://www.standardwebhooks.com). Заголовки:

| Заголовок | Что внутри |
|---|---|
| `webhook-id` | Уникальный ID сообщения (UUID), для идемпотентности на вашей стороне. |
| `webhook-timestamp` | Unix-время (секунды), когда подпись посчитана. |
| `webhook-signature` | `v1,<base64-hmac-sha256>` — HMAC от `${msgId}.${timestamp}.${body}` с вашим секретом. |

Проверьте, что `webhook-timestamp` отличается от текущего не более чем на 5 минут — иначе считайте подпись невалидной (защита от replay-атак).

## Пример обработчика (Node.js + Express)

```js
import crypto from 'node:crypto';
import express from 'express';

const SECRET = process.env.ISKRAGEN_WEBHOOK_SECRET; // "whsec_..."

const app = express();
// raw body нужен для побайтовой подписи — НЕ используйте express.json() до проверки
app.post('/webhooks/iskragen', express.raw({ type: 'application/json' }), (req, res) => {
  const msgId = req.header('webhook-id');
  const timestamp = req.header('webhook-timestamp');
  const signatureHeader = req.header('webhook-signature');
  const body = req.body.toString('utf8');

  if (!msgId || !timestamp || !signatureHeader) return res.status(400).end();
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return res.status(400).end();

  const key = Buffer.from(SECRET.replace(/^whsec_/, ''), 'base64');
  const expected = crypto.createHmac('sha256', key)
    .update(`${msgId}.${timestamp}.${body}`)
    .digest('base64');

  const valid = signatureHeader.split(' ').some((sig) => {
    const [scheme, sigB64] = sig.split(',');
    return scheme === 'v1' && crypto.timingSafeEqual(
      Buffer.from(sigB64), Buffer.from(expected),
    );
  });

  if (!valid) return res.status(401).end();

  const event = JSON.parse(body);
  // event.type === "generation.succeeded" | "generation.failed"
  // event.data.generation — финальный объект генерации с URL результата

  // Ответьте 2xx как можно быстрее, тяжёлую работу делайте в фоне
  res.status(204).end();
});

app.listen(3000);
```

## Ретраи

При ошибке (HTTP ≥ 400, кроме 410, или таймаут 10 секунд) IskraGen ретраит до 15 раз с экспоненциальным бэк-оффом — от 5 секунд до 3 суток. Если ваш endpoint вернёт `HTTP 410 Gone`, он будет автоматически отключён. После 30 × 24 подряд неудач endpoint также деактивируется.

## Идемпотентность

При ретраях один и тот же `webhook-id` будет повторяться. Сохраняйте обработанные `msgId` (например, в Redis с TTL 24 часа) и сразу отвечайте `2xx` без обработки на дубликат — иначе рискуете дважды сохранить результат или дважды дёрнуть пост-обработку.

См. также [Interactive API Reference](/reference) → раздел Webhook Endpoints.
