Files

222 lines
7.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Webhooks
Альтернатива Kafka events для consumer'ов которые не хотят / не могут
поддерживать Kafka client. Ordinis отправляет HTTP POST на зарегистрированный
URL при изменениях справочников.
{% note info %}
Если у тебя есть Kafka access — используй [события](events.md). Webhooks
проще в развёртывании, но менее надёжны (HTTP retry vs Kafka durable log)
и медленнее (HTTP overhead vs Kafka batch).
{% endnote %}
## Регистрация подписки
```bash
curl -X POST -H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
https://ordinis.k8s.264.nstart.cloud/api/v1/admin/webhooks/subscriptions \
-d '{
"name": "altum-cache-invalidation",
"url": "https://altum-backend.altum.local/webhooks/ordinis",
"secret": "<HMAC_SECRET_GENERATED_BY_YOU>",
"scopes": ["PUBLIC", "INTERNAL"],
"dictionaries": ["spacecraft", "satellite_type", "instrument"],
"actions": ["created", "updated", "closed"],
"active": true
}'
```
### Поля
| Поле | Тип | Назначение |
|---|---|---|
| `name` | string | Человекочитаемое имя для admin UI. |
| `url` | string | HTTPS endpoint. HTTP блокируется SSRF guard'ом. |
| `secret` | string | HMAC-SHA256 секрет для подписи payload (см. ниже). |
| `scopes` | array | Фильтр по scope (subset из доступных consumer'у). |
| `dictionaries` | array | Фильтр по dictionary names. `null`/`[]` = все доступные. |
| `actions` | array | `created` / `updated` / `closed`. `null` = все. |
| `categories` | array | Event categories: `dict.record.*` / `dict.definition.*` / `dict.draft.*`. `null` = все. (v2.2.0+) |
| `active` | bool | Можно временно отключить без удаления. |
### Event categories (v2.2.0+)
С v2.2.0 webhook subscriber'ы могут фильтровать по семантическим категориям:
| Category | Маппит outbox events |
|---|---|
| `dict.record.*` | Record CRUD (default до v2.2.0): record.created / .updated / .closed |
| `dict.definition.*` | Schema-level: `DefinitionCreated`, `DefinitionUpdated` (inline PATCH) |
| `dict.draft.*` | Workflow drafts: `SchemaDraftCreated`, `…ReviewRequested`, `…Decided`, `…Published`, `…Withdrawn` |
Если `categories=null` подписчик получает все (legacy behaviour).
Frontend reviewer dashboards обычно подписываются на `dict.draft.*`
для notifications о новых drafts. Bundle consumers (Альтум, BI) —
на `dict.record.*`.
## SSRF guard
Webhook URLs валидируются перед отправкой:
- **Запрещено:** private CIDR (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16),
localhost, link-local (169.254/16), metadata IPs.
- **Запрещено:** non-HTTPS scheme. Только `https://`.
- **Запрещено:** URL > 2048 chars.
При нарушении registration возвращает 422 + список причин.
## Payload
POST на зарегистрированный URL с body:
```json
{
"deliveryId": "wd-abc123-xyz789",
"subscriptionName": "altum-cache-invalidation",
"occurredAt": "2026-05-07T15:23:45.123+03:00",
"events": [
{
"schemaVersion": "1.0.0",
"eventType": "dictionary.changed",
"dictionary": "spacecraft",
"businessKey": "RESURS-P-3",
"action": "updated",
"version": 3,
"validFrom": "2026-05-07T00:00:00+03:00",
"data": { ... }
}
]
}
```
{% note tip %}
Payload может содержать **несколько events** (`events` array) если они
произошли в одной transaction (bulk close, bundle import). Не предполагай
single-event payload.
{% endnote %}
## HMAC подпись
Каждый POST имеет header:
```
X-Ordinis-Signature: sha256=<HEX>
X-Ordinis-Delivery: wd-abc123-xyz789
X-Ordinis-Subscription: altum-cache-invalidation
```
Где `<HEX>` = HMAC-SHA256 от raw body bytes с `secret` который ты задал
при регистрации.
### Verify (Python)
```python
import hmac, hashlib
def verify(body: bytes, signature_header: str, secret: str) -> bool:
expected = "sha256=" + hmac.new(
secret.encode(), body, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature_header)
```
### Verify (Node.js)
```javascript
import crypto from 'crypto'
function verify(body, signatureHeader, secret) {
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(body)
.digest('hex')
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signatureHeader)
)
}
```
{% note warning %}
Verify подпись **до** парсинга JSON. Если invalid — `403 Forbidden`,
не трогай payload. Без verify ты уязвим к подделке events от любого, кто
знает твой URL.
{% endnote %}
## Response contract
Consumer должен ответить `2xx` в течение 10 секунд:
| Status | Семантика |
|---|---|
| `200` / `201` / `202` / `204` | Принято. Ordinis помечает delivered. |
| `4xx` | Permanent failure (твой bug). Ordinis НЕ retry. |
| `5xx` / timeout | Transient. Ordinis retry с exponential backoff. |
## Retry policy
| Попытка | Интервал |
|---|---|
| 1 | сразу |
| 2 | через 30s |
| 3 | через 2 мин |
| 4 | через 10 мин |
| 5 | через 1ч |
| 6-10 | каждые 6ч |
| После всех попыток | Ordinis team помечает delivery как failed для ручного разбора |
Если consumer недоступен > 1000 retry попыток — Ordinis team будет
расследовать. Долгосрочное unavailability стоит coordinate (planned
maintenance window) чтобы не накапливать orphan deliveries.
## Test ping
Endpoint для проверки доступности URL **до** регистрации:
```bash
curl -X POST -H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
https://ordinis.k8s.264.nstart.cloud/api/v1/admin/webhooks/test-ping \
-d '{
"url": "https://altum-backend.altum.local/webhooks/ordinis",
"secret": "<TEST_SECRET>"
}'
```
Response:
```json
{
"success": true,
"statusCode": 200,
"responseBody": "OK",
"elapsedMs": 142
}
```
Либо details про failure (DNS issue, timeout, SSRF reject и т.д.).
## Best practices
1. **Idempotency** — webhook может прийти **дважды** (network retry, partial
failure). Используй `deliveryId` как dedup key.
2. **Async обработка** — отвечай `202 Accepted` сразу, обрабатывай в
background. 10s timeout жёсткий — если БД медленная, лучше принять и
сложить в очередь.
3. **HMAC verify первым** — до парсинга JSON.
4. **Metrics** — track received / processed / errors для своего side.
5. **Whitelist Ordinis IP** на firewall если строгие правила. Точный
адрес запросить у Ordinis team при registration.
## Дальше
- [События (Kafka)](events.md) — primary delivery channel
- [Caching](caching.md) — webhook invalidation pattern