# Вебхуки

> Подписанные, повторяемые, компактные события для записи ресурсов, изменений согласия и финализированных документов с проверкой подписи на TypeScript, Dart и Python.

Source: https://developers.anpheros.com/ru/guides/webhooks

Платформа Anpheros может вызывать ваш сервер, когда что-то происходит в вашем проекте: ресурс был записан, человек предоставил или отозвал доступ, документ был финализирован. События **компактные**: идентификаторы, тип, версия — никогда клиническое содержимое. Читайте ресурс через API, если он вам нужен. Каждая доставка подписана; проверьте подпись, прежде чем доверять ей.

## Регистрация конечной точки

```bash
curl -X POST $BASE/v1/webhooks -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"url": "https://your.app/anpheros/hook", "events": ["resource.created", "consent.revoked"], "description": "prod"}'
```

Ответ содержит `secret` (`whsec_…`) **один раз**. Сохраните его; он вам понадобится для проверки подписей.
`events` принимает `["*"]` или любой из следующих вариантов:

| тип | когда | `data` |
|---|---|---|
| `resource.created` / `resource.updated` / `resource.deleted` | ваш проект записал ресурс FHIR (REST v1, FHIR API или пакет) — **или другой проект записал в запись, к которой у вас есть доступ** (вы создали пациента или у вас есть активное разрешение); тогда `data.external` равно `true`, а `data.source` указывает, кто записал: `author_type` (пациент / практикующий врач / устройство / импорт / ИИ), `organization` (отображаемое имя, от имени которого действовал автор), `practitioner` (имя из каталога, если известно), `source_system` | `resource_type`, `id`, `patient` (идентификатор, который вы видите), `version`, `last_updated`, а для внешних записей `external`, `source` |
| `consent.granted` | человек предоставил вашему приложению доступ со страницы согласия (или связанная доверенная запись приложения) | `grant_id`, `patient`, `application_id`, `scopes`, `purpose`, `expires_at` |
| `consent.revoked` | человек (или платформа) отозвал разрешение | то же, что и выше, плюс `revoked_at`, `reason` |
| `document.finalized` | загрузка документа была завершена и проверена по контрольной сумме | `id`, `patient`, `content_type`, `size`, `sha256` |
| `proposal.created` | агент ИИ предложил запись в запись, которую вы можете видеть; ожидается одобрение человека | `id`, `patient`, `action`, `resource_type` |
| `ping` | вы запросили тестовую доставку | `endpoint` |

Конечные точки зависят от проекта **и среды**: ключ `sk_test_` управляет конечными точками песочницы,
ключ `sk_live_` — производственными. URL-адреса должны быть общедоступными конечными точками `https://`. Регистрация (и любое последующее изменение `url`) отклоняет локальные, частные, link-local, метаданные и другие зарезервированные адреса — `localhost`, `127.0.0.1`, `::1`, `10.0.0.0/8`, `192.168.0.0/16`, `fd00::/8` и тому подобное — а также имена, которые разрешаются в них, с `400` `invalid` в поле `url`; это применимо и в песочнице. Каждая доставка повторно проверяет адрес и подключается только к только что проверенным IP-адресам, поэтому имя, которое позже указывает на частную сеть (DNS rebinding), ничего не получит. Чтобы получать события на своей машине во время разработки, выведите ее через общедоступный HTTPS-туннель и зарегистрируйте адрес туннеля.
До 10 конечных точек на проект и среду.

## Доставка

```
POST https://your.app/anpheros/hook
Content-Type: application/json
User-Agent: Anpheros-Webhooks/1.0
Anpheros-Event: resource.created
Anpheros-Event-Id: evt_…
Anpheros-Delivery: whd_…
Anpheros-Signature: t=1789800000,v1=5f3a…

{"id":"evt_…","type":"resource.created","created_at":"2026-09-19T12:00:00.000Z","environment":"production",
 "project":"proj_…","data":{"resource_type":"Observation","id":"…","patient":"…","version":1,"last_updated":"…"}}
```

Отвечайте любым `2xx` в течение 10 секунд. Все остальное (или тайм-аут) считается сбоем, и доставка
повторяется с экспоненциальной задержкой до 24 часов (10 попыток), после чего помечается как `exhausted`.
Доставки осуществляются как минимум один раз и могут поступать не по порядку: используйте `Anpheros-Event-Id` для
дедупликации и `data.version` для игнорирования устаревших обновлений.

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

`v1` — это HMAC-SHA256 с вашим секретным ключом поверх `<t>.<raw body>`; отклоните запрос, если `t` старше 5 минут.

```ts
import { verifyWebhookSignature } from '@anpheros/sdk';
app.post('/anpheros/hook', express.raw({ type: 'application/json' }), async (req, res) => {
  const ok = await verifyWebhookSignature({ secret: process.env.ANPHEROS_WEBHOOK_SECRET!, header: req.header('anpheros-signature'), body: req.body });
  if (!ok) return res.status(400).end();
  const event = JSON.parse(req.body.toString());
  // … enqueue and return quickly
  res.status(204).end();
});
```

```dart
import 'package:anpheros_sdk/anpheros_sdk.dart';
final ok = verifyWebhookSignature(secret: secret, header: request.headers['anpheros-signature'], body: rawBodyBytes);
```

```python
import hmac, hashlib, time
def verify(secret: str, header: str, body: bytes, tolerance=300) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(","))
    if abs(time.time() - int(parts["t"])) > tolerance:
        return False
    expected = hmac.new(secret.encode(), f"{parts['t']}.".encode() + body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts["v1"])
```

## Эксплуатация

- `GET /v1/webhooks/{id}/deliveries?status=failed` — журнал, от новых к старым, с кодом состояния и ошибкой.
- `POST /v1/webhooks/{id}/deliveries/{delivery_id}/redeliver` — отправить еще раз сейчас.
- `POST /v1/webhooks/{id}/ping` — тестовое событие.
- `POST /v1/webhooks/{id}/rotate` — новый секрет (старый немедленно перестанет действовать; обновите ваш сервер заранее, если не можете позволить себе перерыв: зарегистрируйте второй конечный пункт, затем удалите старый).
- `PATCH /v1/webhooks/{id}` с `{"active": false}` приостанавливает доставку; ожидающие будут отброшены при удалении конечного пункта.

Оба SDK предоставляют это под `webhooks` (`create`, `list`, `get`, `update`, `delete`, `rotateSecret`, `ping`, `deliveries`, `redeliver`).
