---
title: Вебхуки
description: POST-уведомление о завершении генерации на ваш сервер — формат тела, проверка подписи HMAC-SHA256, сроки ответа и расписание повторных доставок.
icon: Webhook
---

Передайте `webhook_url` при отправке генерации, и когда задача завершится, Clipia отправит `POST` на этот адрес. Так не нужно опрашивать статус. Каждая доставка подписана HMAC-SHA256 секретом вашего ключа, а при сбое повторяется в течение примерно суток.

## Требования к адресу

- Только `https://`. Адреса во внутренних и частных сетях отклоняются при отправке с ошибкой `400 webhook_url_invalid`.
- Редиректы не выполняются: ответ `3xx` считается неуспешной доставкой.

## Тело запроса

Успех:

```json
{
  "request_id": "764cabcf-b745-4b3e-ae38-1200304cf45b",
  "status": "OK",
  "payload": {
    "model": "nano-banana-2",
    "output": { "images": [{ "url": "https://media.clipia.ai/....webp" }] },
    "cost": 12
  }
}
```

Ошибка генерации:

```json
{
  "request_id": "764cabcf-b745-4b3e-ae38-1200304cf45b",
  "status": "ERROR",
  "error": {
    "code": "generation_failed",
    "message": "Generation failed. Please retry or contact support."
  }
}
```

`output` зависит от типа модели: `images: [{ url }]`, `video: { url }` или `audio: { url, format? }` вместе с `audios: [{ url, format? }]` (все варианты трека, первый совпадает с `audio`). `error.code` — из [словаря ошибок генерации](/ru/docs/api-reference/result#failed-codes): `generation_failed`, `content_moderated`, `model_unavailable` и другие; незнакомый код обрабатывайте как `generation_failed`.

<Callout type="warn" title="Тело вебхука короче ответа REST">
В вебхуке нет `original_url` и `thumbnail_url`, а у изображений — только одна ссылка. Полный результат забирайте по [`GET /v1/requests/:id`](/ru/docs/api-reference/result).
</Callout>

<TypeTable
  type={{
    request_id: { type: 'string', description: 'request_id из ответа на отправку', required: true },
    status: { type: '"OK" | "ERROR"', description: 'Итог генерации', required: true },
    'payload.model': { type: 'string', description: 'Slug модели, только при OK' },
    'payload.output': { type: 'object', description: 'Ссылки на результат, только при OK' },
    'payload.cost': { type: 'integer', description: 'Списано кредитов, только при OK' },
    error: { type: 'object', description: '{ code, message }, только при ERROR; code — из словаря ошибок генерации, message — нейтральный текст на английском' }
  }}
/>

## Заголовки

```http
Content-Type: application/json
X-Clipia-Webhook-Id: 1f2e3d4c-5b6a-7890-abcd-ef0123456789
X-Clipia-Timestamp: 1760011200
X-Clipia-Signature: t=1760011200,v1=5257a869e7...
```

| Заголовок | Значение |
|-----------|----------|
| `X-Clipia-Webhook-Id` | идентификатор доставки, одинаковый во всех повторах |
| `X-Clipia-Timestamp` | unix-время отправки этой попытки, в секундах |
| `X-Clipia-Signature` | `t=<timestamp>,v1=<hex HMAC-SHA256>` |

## Секрет подписи

Секрет свой у каждого API-ключа: вебхуки задач, отправленных этим ключом, подписываются им. Секрет показывается один раз при создании ключа в [консоли разработчика](/ru/developer). Истории доставок и повторной отправки в консоли нет. Если секрет потерян, создайте новый ключ и переключите на него интеграцию — см. [«Управление ключами»](/ru/docs/api-reference/keys).

## Проверка подписи

Подпись считается как `HMAC_SHA256(secret, "{timestamp}.{raw_body}")`. Проверяйте её по **сырому** телу запроса, до разбора JSON, сравнивайте в постоянном времени и отклоняйте доставки старше 5 минут. Проще всего взять готовую функцию из SDK.

<Tabs items={['TypeScript SDK', 'Python SDK', 'Node.js без SDK']}>
<Tab value="TypeScript SDK">

```ts
import { verifyWebhookSignature } from 'clipia-ai/webhook';

// rawBody — исходные байты тела, не JSON.stringify(req.body)
const ok = verifyWebhookSignature({
  secret: process.env.CLIPIA_WEBHOOK_SECRET!,
  headers: req.headers,
  body: rawBody,
  toleranceSeconds: 300,
});
if (!ok) return res.status(401).end();
```

</Tab>
<Tab value="Python SDK">

```python
import os
from clipia.webhooks import verify_signature

ok = verify_signature(
    os.environ["CLIPIA_WEBHOOK_SECRET"],
    request.headers,
    raw_body,  # исходные байты тела
    tolerance_seconds=300,
)
if not ok:
    return Response(status=401)
```

</Tab>
<Tab value="Node.js без SDK">

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

function verify(headers, rawBody, secret) {
  const sig = headers['x-clipia-signature'] ?? '';
  const parts = Object.fromEntries(sig.split(',').map((p) => p.split('=')));
  if (!parts.t || !parts.v1) return false;
  const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) <= 300;
  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${parts.t}.${rawBody}`)
    .digest('hex');
  const a = Buffer.from(parts.v1, 'hex');
  const b = Buffer.from(expected, 'hex');
  return fresh && a.length === b.length && crypto.timingSafeEqual(a, b);
}
```

</Tab>
</Tabs>

Обе функции SDK возвращают `true` или `false` и не бросают исключений на испорченных заголовках.

## Доставка и повторы

- Отвечайте кодом `2xx` в течение 10 секунд. Тяжёлую обработку выполняйте после ответа.
- Ответ не `2xx`, `3xx` или таймаут считаются неуспехом. Доставка повторяется до 15 попыток в сумме.
- Пауза между попытками растёт вдвое, начиная с 30 секунд, и не превышает 4 часов: 30 с, 1, 2, 4, 8, 16, 32 мин, 1 ч 04 мин, 2 ч 08 мин, затем по 4 часа — всего около 24 часов 15 минут с первой попытки. Отправка проверяется раз в 30 секунд, поэтому фактическая задержка может быть немного больше.
- После последней неудачи доставка помечается проваленной и больше не повторяется; запросить её заново нельзя. Если обработчик лежал дольше, заберите результат по [`GET /v1/requests/:id`](/ru/docs/api-reference/result) — он хранится независимо от вебхука.

<Callout type="info" title="Обрабатывайте идемпотентно">
Одна и та же доставка может прийти повторно, например если ваш ответ не дошёл. Дедуплицируйте по `X-Clipia-Webhook-Id` или `request_id`, прежде чем выполнять побочные действия.
</Callout>

В [тестовом режиме](/ru/docs/getting-started/sandbox) вебхук тоже приходит и подписан так же — на нём удобно проверить обработчик. Тестовому ключу отправляется не больше одной доставки в минуту: остальные за эту минуту не создаются, а неудачная доставка повторяется не дальше трёх попыток.
