# Webhooki

> Podpisywane, ponawiane, szczupłe zdarzenia dotyczące zapisu zasobów, zmian zgód i zatwierdzonych dokumentów, z weryfikacją podpisu w TypeScript, Dart i Python.

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

Platforma Anpheros może wywołać Twój serwer, gdy coś wydarzy się w Twoim projekcie: zasób został zapisany, osoba udzieliła lub cofnęła dostęp, dokument został zatwierdzony. Zdarzenia są **szczupłe**: identyfikatory, typ, wersja — nigdy treść kliniczna. Odczytaj zasób przez API, jeśli go potrzebujesz. Każda dostawa jest podpisana; zweryfikuj podpis przed zaufaniem.

## Zarejestruj punkt końcowy

```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"}'
```

Odpowiedź zawiera `secret` (`whsec_…`) **raz**. Zapisz ją; będzie potrzebna do weryfikacji podpisów.
`events` akceptuje `["*"]` lub dowolny z poniższych:

| typ | kiedy | `data` |
|---|---|---|
| `resource.created` / `resource.updated` / `resource.deleted` | Twój projekt zapisał zasób FHIR (REST v1, FHIR API lub pakiet) — **lub inny projekt zapisał dane do rekordu, do którego masz dostęp** (stworzyłeś pacjenta lub posiadasz aktywną zgodę); wtedy `data.external` to `true`, a `data.source` mówi, kto to zapisał: `author_type` (pacjent / praktyk / urządzenie / import / AI), `organization` (nazwa wyświetlana, w imieniu której działał autor), `practitioner` (nazwa z katalogu, jeśli znana), `source_system` | `resource_type`, `id`, `patient` (identyfikator, który widzisz), `version`, `last_updated`, a dla zapisów zewnętrznych `external`, `source` |
| `consent.granted` | osoba udzieliła Twojej aplikacji dostępu ze strony zgody (lub zaufana aplikacja powiązała swój rekord) | `grant_id`, `patient`, `application_id`, `scopes`, `purpose`, `expires_at` |
| `consent.revoked` | osoba (lub platforma) zakończyła udzielanie zgody | tak samo jak powyżej plus `revoked_at`, `reason` |
| `document.finalized` | przesłanie dokumentu zostało zakończone i zweryfikowane sumą kontrolną | `id`, `patient`, `content_type`, `size`, `sha256` |
| `proposal.created` | agent AI zaproponował zapis do rekordu, który możesz zobaczyć; oczekuje na zatwierdzenie przez osobę | `id`, `patient`, `action`, `resource_type` |
| `ping` | poprosiłeś o testowe dostarczenie | `endpoint` |

Punkty końcowe są per projekt **i środowisko**: klucz `sk_test_` zarządza punktami końcowymi piaskownicy,
klucz `sk_live_` produkcyjnymi. Adresy URL muszą być publicznymi punktami końcowymi `https://`. Rejestracja (i wszelkie późniejsze zmiany `url`) odrzuca adresy lokalne, prywatne, link-local, metadane i inne zarezerwowane adresy — `localhost`, `127.0.0.1`, `::1`, `10.0.0.0/8`, `192.168.0.0/16`, `fd00::/8` i podobne — oraz nazwy, które się do nich rozwiązują, z `400` `invalid` w polu `url`; dotyczy to również piaskownicy. Każde dostarczenie ponownie sprawdza adres i łączy się tylko z adresami IP, które właśnie sprawdziło, więc nazwa, która później wskazuje na sieć prywatną (DNS rebinding), niczego nie otrzyma. Aby odbierać zdarzenia na własnej maszynie podczas tworzenia, udostępnij ją poprzez publiczny tunel HTTPS i zarejestruj adres tunelu.
Do 10 punktów końcowych na projekt i środowisko.

## Dostarczenie

```
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":"…"}}
```

Odpowiadaj z wszelkimi `2xx` w ciągu 10 sekund. Cokolwiek innego (lub przekroczenie limitu czasu) jest błędem, a dostarczenie jest ponawiane z wykładniczym wycofywaniem przez maksymalnie 24 godziny (10 prób), a następnie oznaczane jako `exhausted`.
Dostarczenia są co najmniej raz (at-least-once) i mogą przybywać w nieuporządkowanej kolejności: użyj `Anpheros-Event-Id` do
de-duplikacji i `data.version` do ignorowania nieaktualnych aktualizacji.

## Weryfikacja podpisu

`v1` to HMAC-SHA256 z Twoim kluczem tajnym na `<t>.<raw body>`; odrzuć, jeśli `t` jest starszy niż 5 minut.

```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"])
```

## Operacje

- `GET /v1/webhooks/{id}/deliveries?status=failed` — log, najnowsze najpierw, z kodem statusu i błędem.
- `POST /v1/webhooks/{id}/deliveries/{delivery_id}/redeliver` — wyślij ponownie teraz.
- `POST /v1/webhooks/{id}/ping` — zdarzenie testowe.
- `POST /v1/webhooks/{id}/rotate` — nowy sekret (stary przestaje działać natychmiast; zaktualizuj swój serwer, jeśli nie możesz sobie pozwolić na przerwę: zarejestruj drugi punkt końcowy, a następnie usuń stary).
- `PATCH /v1/webhooks/{id}` z `{"active": false}` wstrzymuje dostarczanie; oczekujące elementy są usuwane po usunięciu punktu końcowego.

Oba SDK udostępniają to w ramach `webhooks` (`create`, `list`, `get`, `update`, `delete`, `rotateSecret`, `ping`, `deliveries`, `redeliver`).
