Files
mdm-ordinis/docs/integration/api/webhooks.md
T
Zimin A.N. bcfb07f547 docs: split integrator guide (Diplodoc) vs internal docs + add build-docs CI
Чёткое разделение audiences:
- docs/ — consumer-facing only, Diplodoc-built. Public contract для
  интеграторов (Альтум, BI, third-party).
- docs-internal/ — operator runbooks, status snapshots, legacy tech pages.
  НЕ в Diplodoc build, НЕ публикуются.

Изменения:
- Move docs/{ops,status,tech} → docs-internal/{ops,status,tech}
- docs/toc.yaml: убраны секции Operations, Архитектура (legacy), Status
- docs/index.md: убран tab "оператор / on-call", focus на integrators
- docs/README.md: rewritten — scope только integrator guide, editorial
  guidelines (no leak internal architecture, stable API contract)
- .yfm: убран явный ignore status/* (теперь не нужно — папка переехала)
- docs-internal/README.md: index для внутренней документации с rationale
  разделения

Scrubbing implementation details из Diplodoc страниц:
- events.md: убраны Strimzi/cluster.142 references, OutboxPoller детали,
  internal alert names (OrdinisOutboxLagHigh, OrdinisOutboxDLQNonEmpty),
  metric names (nsi_outbox_*). Заменены на consumer-observable contract.
- webhooks.md: убраны metric names (nsi_webhook_ssrf_rejected_total),
  alert table (OrdinisWebhookHighFailureRate и т.д.). Retry policy
  переписана на user-side perspective.
- errors.md: убран alert name OrdinisReadApiErrorBudgetBurnFast, Tempo
  endpoint URL, internal connection details.
- x-references.md: OrphanReferenceScanner → general "Ordinis периодически
  детектирует". Убраны metric/alert names. Cascade roadmap без link на
  internal design doc.
- auth.md: убран file path ScopeContext.java.
- bundle-cuod.md: убран Maven module path.
- caching.md: cascade design link заменён на text reference.

CI:
- .gitlab-ci.yml: новый job build-docs (stage: build) — Node 20 alpine,
  pnpm install --frozen-lockfile + pnpm build, artifact docs/_dist на
  неделю. Triggers: auto на docs/**, либо manual web. Готов к follow-up
  pages-deploy job когда host решён.
- Новый pattern .docs-changes для rules.

Build verified clean: 13 HTML pages, 6.9M, no broken links/refs.
2026-05-07 23:27:03 +03:00

6.6 KiB
Raw Blame History

Webhooks

Альтернатива Kafka events для consumer'ов которые не хотят / не могут поддерживать Kafka client. Ordinis отправляет HTTP POST на зарегистрированный URL при изменениях справочников.

{% note info %}

Если у тебя есть Kafka access — используй события. Webhooks проще в развёртывании, но менее надёжны (HTTP retry vs Kafka durable log) и медленнее (HTTP overhead vs Kafka batch).

{% endnote %}

Регистрация подписки

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.

При нарушении registration возвращает 422 + список причин.

Payload

POST на зарегистрированный URL с body:

{
  "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)

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)

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 до регистрации:

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:

{
  "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.

Дальше