# Webhooks

> Signierte, wiederholte, schlanke Events für Ressourcen-Schreibvorgänge, Einwilligungsänderungen und abgeschlossene Dokumente, mit Signaturprüfung in TypeScript, Dart und Python.

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

Die Anpheros Platform kann Ihren Server aufrufen, wenn in Ihrem Projekt etwas passiert: eine Ressource wurde geschrieben, eine Person hat Zugriff gewährt oder widerrufen, ein Dokument wurde abgeschlossen. Events sind **schlank**: IDs, Typ, Version – niemals klinische Inhalte. Lesen Sie die Ressource über die API, wenn Sie sie benötigen. Jede Zustellung ist signiert; verifizieren Sie die Signatur, bevor Sie ihr vertrauen.

## Endpunkt registrieren

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

Die Antwort enthält `secret` (`whsec_…`) **einmal**. Speichern Sie diese; Sie benötigen sie zur Verifizierung von Signaturen.
`events` akzeptiert `["*"]` oder eine der folgenden Optionen:

| Typ | Wann | `data` |
|---|---|---|
| `resource.created` / `resource.updated` / `resource.deleted` | Ihr Projekt hat eine FHIR-Ressource geschrieben (REST v1, FHIR API oder ein Bundle) — **oder ein anderes Projekt hat in einen Datensatz geschrieben, auf den Sie Zugriff haben** (Sie haben den Patienten erstellt oder halten eine aktive Berechtigung); dann ist `data.external` `true` und `data.source` gibt an, wer geschrieben hat: `author_type` (patient / practitioner / device / import / ai), `organization` (der Anzeigename, für den der Schreiber handelte), `practitioner` (Name aus dem Verzeichnis, falls bekannt), `source_system` | `resource_type`, `id`, `patient` (die ID, die Sie sehen), `version`, `last_updated` und für externe Schreibvorgänge `external`, `source` |
| `consent.granted` | Eine Person hat Ihrer App über die Zustimmungsseite Zugriff gewährt (oder eine vertrauenswürdige App hat ihren Datensatz verknüpft) | `grant_id`, `patient`, `application_id`, `scopes`, `purpose`, `expires_at` |
| `consent.revoked` | Die Person (oder die Plattform) hat eine Berechtigung beendet | wie oben, plus `revoked_at`, `reason` |
| `document.finalized` | Ein Dokumenten-Upload wurde abgeschlossen und die Prüfsumme wurde berechnet | `id`, `patient`, `content_type`, `size`, `sha256` |
| `proposal.created` | Ein KI-Agent hat einen Schreibvorgang in einen Datensatz vorgeschlagen, den Sie sehen können; dieser wartet auf die Genehmigung der Person | `id`, `patient`, `action`, `resource_type` |
| `ping` | Sie haben eine Testlieferung angefordert | `endpoint` |

Endpunkte sind pro Projekt **und Umgebung**: ein `sk_test_`-Schlüssel verwaltet Sandbox-Endpunkte,
ein `sk_live_`-Schlüssel Produktionsendpunkte. URLs müssen öffentliche `https://`-Endpunkte sein. Die Registrierung (und jede spätere Änderung von `url`) lehnt lokale, private, Link-lokale, Metadaten und andere reservierte Adressen ab — `localhost`, `127.0.0.1`, `::1`, `10.0.0.0/8`, `192.168.0.0/16`, `fd00::/8` und ähnliche — sowie Namen, die auf diese auflösen, mit `400` `invalid` im Feld `url`; dies gilt auch in der Sandbox. Jede Lieferung prüft die Adresse erneut und verbindet sich nur mit den IP-Adressen, die sie gerade überprüft hat, sodass ein Name, der später auf ein privates Netzwerk zeigt (DNS-Rebinding), nichts erhält. Um Ereignisse auf Ihrem eigenen Rechner während der Entwicklung zu empfangen, stellen Sie ihn über einen öffentlichen HTTPS-Tunnel bereit und registrieren Sie die Adresse des Tunnels.
Bis zu 10 Endpunkte pro Projekt und Umgebung.

## Die Zustellung

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

Antworten Sie innerhalb von 10 Sekunden mit allen `2xx`. Alles andere (oder ein Timeout) ist ein Fehler und die
Zustellung wird mit Backoff bis zu 24 Stunden (10 Versuche) wiederholt und dann als `exhausted` markiert.
Zustellungen sind "at-least-once" und können außer der Reihe eintreffen: Verwenden Sie `Anpheros-Event-Id` zur
De-Duplizierung und `data.version`, um veraltete Updates zu ignorieren.

## Signatur verifizieren

`v1` ist HMAC-SHA256 mit Ihrem Secret über `<t>.<raw body>`; lehnen Sie ab, wenn `t` älter als 5 Minuten ist.

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

## Betrieb

- `GET /v1/webhooks/{id}/deliveries?status=failed` — das Log, neueste zuerst, mit Statuscode und Fehler.
- `POST /v1/webhooks/{id}/deliveries/{delivery_id}/redeliver` — senden Sie einen jetzt erneut.
- `POST /v1/webhooks/{id}/ping` — ein Testereignis.
- `POST /v1/webhooks/{id}/rotate` — neues Secret (das alte wird sofort ungültig; aktualisieren Sie Ihren Server zuerst, wenn Sie keine Lücke riskieren können: registrieren Sie einen zweiten Endpunkt, dann löschen Sie den alten).
- `PATCH /v1/webhooks/{id}` mit `{"active": false}` pausiert Lieferungen; ausstehende werden verworfen, wenn Sie den Endpunkt löschen.

Beide SDKs stellen dies unter `webhooks` zur Verfügung (`create`, `list`, `get`, `update`, `delete`, `rotateSecret`, `ping`, `deliveries`, `redeliver`).
