<!-- /docs/webhooks (ru) -->

# Вебхуки

Вебхуки — push-уведомления о событиях биллинга и задач. Регистрируйте HTTPS-эндпоинт, подписывайтесь на типы событий — и получайте POST-запросы с подписью HMAC-SHA256.

## Быстрый старт

```bash
# зарегистрировать эндпоинт (секрет показывается один раз!)
curl -X POST -H "X-API-Key: $FRANKLAB_KEY" -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/franklab-hook","eventTypes":["billing.settled","task.succeeded"]}' \
  https://apergrex.ru/franklab/api/v1/webhooks/endpoints

# тестовая доставка
curl -X POST -H "X-API-Key: $FRANKLAB_KEY" \
  https://apergrex.ru/franklab/api/v1/webhooks/endpoints/<id>/test
```

## Типы событий

| Событие | Когда |
|---|---|
| `billing.settled` | подтверждено списание (с финальной суммой) |
| `billing.refunded` | возврат резерва при неудаче задачи |
| `billing.topup.confirmed` | зачислено пополнение |
| `billing.low_balance` | баланс ниже 1 000 ₣ (не чаще раза в 24 ч) |
| `billing.invoice.status_changed` | счёт юрлица сменил статус/оплачен |
| `task.succeeded` / `task.failed` / `task.refunded` | терминальный статус задачи генерации |
| `webhook.test` | тестовое событие (по команде) |

## Формат доставки

Заголовки:

```
X-Franklab-Event: billing.settled
X-Franklab-Event-Id: <uuid события — ключ идемпотентности>
X-Franklab-Timestamp: <unix-секунды>
X-Franklab-Signature: sha256=<HMAC-SHA256(secret, "{timestamp}.{body}")>
```

Тело — общий конверт:

```json
{
  "eventId": "…",
  "eventType": "billing.settled",
  "occurredAt": "2026-09-04T12:00:00.000Z",
  "partnerId": "…",
  "schemaVersion": 1,
  "data": { "transactionId": "…", "amountFranks": 84 }
}
```

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

```js
const expected = crypto.createHmac('sha256', secret)
  .update(`${timestamp}.${rawBody}`)
  .digest('hex');
// сравните с X-Franklab-Signature (без префикса sha256=) и проверите,
// что timestamp не старше ~5 минут (анти-replay)
```

## Гарантии доставки

- **At-least-once**: дубликаты возможны — идемпотентируйте по `eventId`.
- `eventId` событий из источников (биллинг, задачи, счета) — детерминированный uuid: повторная доставка того же бизнес-события приходит с тем же `eventId`, так что дедупликация по нему полная. Исключение — `billing.low_balance` (лимитированная повторная эмиссия раз в 24 ч) и `webhook.test`: у них каждый выпуск — новое событие с новым `eventId`.
- Дубликаты возможны и после рестарта поллера — запасной ключ дедупликации: `eventType` + `data.transactionId` / `data.taskId` / `data.invoiceId`.
- Задержка — до минуты (поллер).
- Повторы при неудаче: 1 мин → 5 мин → 30 мин → 2 ч → 6 ч, затем **dead-letter** (виден в `GET /v1/webhooks/deliveries?status=dead_lettered`).
- Статус `uncertain` — запрос отправлен, но исход неизвестен: такие доставки не повторяются автоматически — повторите их через `POST /v1/webhooks/deliveries/{id}/replay`.
- Отвечайте 2xx быстро; таймаут — 10 секунд.

## Управление

| Операция | Эндпоинт |
|---|---|
| Список | `GET /v1/webhooks/endpoints` |
| Регистрация | `POST /v1/webhooks/endpoints` |
| Удаление | `DELETE /v1/webhooks/endpoints/{id}` |
| Ротация секрета | `POST /v1/webhooks/endpoints/{id}/rotate-secret` |
| Тест | `POST /v1/webhooks/endpoints/{id}/test` |
| История доставок | `GET /v1/webhooks/deliveries?endpointId=…&status=…` |
| Повтор доставки | `POST /v1/webhooks/deliveries/{id}/replay` |

Ограничения: до 10 эндпоинтов на партнёра; URL — только публичный HTTPS (внутренние адреса и IP-литералы отклоняются); редиректы не следуются.
