Files
mdm-ordinis/docs/integration/api/webhooks.md
T
Zimin A.N. d5268b9395 docs: Diplodoc setup + полное руководство интеграции по API
Новая документация-as-code на платформе Diplodoc (Yandex open-source) с
YFM (Yandex Flavored Markdown). Build → static HTML, deployable куда угодно.

Что добавлено:
- package.json + .yfm + toc.yaml — Diplodoc setup, навигация для sidebar
- index.md — главная страница с tabs для разных audiences
- README.md — инструкция для contributors

Раздел integration/api/ — 11 страниц про интеграцию consumers:
- index — обзор + глоссарий
- auth — Keycloak service accounts, scope mapping
- read-endpoints — GET /dictionaries, list/by-key, spatial filters
- events — Kafka outbox топики, partition keys, idempotency, DLQ
- webhooks — HMAC, SSRF guard, retry policy, test ping endpoint
- bitemporal — validFrom/validTo + history, future-dated entries, bulk close/export
- x-references — FK syntax, validation errors, scope hiding, orphan scanner, v2 cascade preview
- caching — TTL / push / snapshot strategies, hybrid pattern
- errors — full error code reference (validation/auth/conflict/server), retry strategy
- best-practices — 33 numbered rules для consumers
- bundle-cuod — все 40 справочников с FK графом и сценариями

Обновлены:
- ops/README.md — link на новую integration/api/, убран inline <br>
- ops/slo.md — broken cross-repo links заменены на text references

Build:
  cd docs && pnpm install && pnpm build
  → ./_dist (20 HTML files, 7.1M)
  pnpm serve  # → http://localhost:8088

CI deploy job для GitLab Pages — в roadmap (см. docs/README.md).

Существующий tech/api-integration.md оставлен в toc как legacy
(содержимое мигрировано + расширено в integration/api/).
2026-05-07 23:19:02 +03:00

217 lines
6.9 KiB
Markdown
Raw 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` = все. |
| `active` | bool | Можно временно отключить без удаления. |
## 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.
Метрика `nsi_webhook_ssrf_rejected_total` increments при попытках. См.
prometheus rule `OrdinisWebhookSsrfRejected`.
## 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ч |
| 1000+ | DLQ → ручной разбор |
После 1000 неудач delivery попадает в DLQ (`/api/v1/admin/webhooks/deliveries/dlq`).
Alert: `OrdinisWebhookDLQNonEmpty`.
## 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.
Сравнивай с Ordinis metrics при расследовании.
5. **Whitelist Ordinis IP** на firewall если строгие правила. IP cluster.142
= `192.168.100.142`, но в production будет ingress IP. Спросить у Ordinis
team при registration.
## Alerting (Ordinis side)
| Alert | Trigger |
|---|---|
| `OrdinisWebhookHighFailureRate` | failure rate > 50% за 5 мин |
| `OrdinisWebhookDLQNonEmpty` | events в DLQ > 0 |
| `OrdinisWebhookDispatcherIdle` | 0 deliveries при > 0 active subscriptions |
| `OrdinisWebhookSsrfRejected` | SSRF guard сработал > 0 раз за 15 мин |
## Дальше
- [События (Kafka)](events.md) — primary delivery channel
- [Caching](caching.md) — webhook invalidation pattern