# Webhooks

> Eventi firmati, ritentati e leggeri per scritture di risorse, modifiche di consensi e documenti finalizzati, con verifica della firma in TypeScript, Dart e Python.

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

Anpheros Platform può chiamare il tuo server quando accade qualcosa nel tuo progetto: una risorsa è stata scritta, una persona ha concesso o revocato l'accesso, un documento è stato finalizzato. Gli eventi sono **leggeri**: ID, tipo, versione — mai contenuti clinici. Leggi la risorsa tramite l'API se ne hai bisogno. Ogni consegna è firmata; verifica la firma prima di fidarti.

## Registra un endpoint

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

La risposta contiene `secret` (`whsec_…`) **una volta**. Memorizzala; ti servirà per verificare le firme.
`events` accetta `["*"]` o uno qualsiasi dei seguenti:

| type | when | `data` |
|---|---|---|
| `resource.created` / `resource.updated` / `resource.deleted` | il tuo progetto ha scritto una risorsa FHIR (REST v1, FHIR API o un bundle) — **o un altro progetto ha scritto in un record a cui hai accesso** (hai creato il paziente, o detieni una concessione attiva); quindi `data.external` è `true` e `data.source` dice chi ha scritto: `author_type` (paziente / professionista / dispositivo / importazione / IA), `organization` (il nome visualizzato per conto del quale ha agito lo scrittore), `practitioner` (nome dalla directory, quando noto), `source_system` | `resource_type`, `id`, `patient` (l'ID che vedi), `version`, `last_updated`, e per scritture esterne `external`, `source` |
| `consent.granted` | una persona ha concesso alla tua app l'accesso dalla pagina del consenso (o un'app attendibile ha collegato il suo record) | `grant_id`, `patient`, `application_id`, `scopes`, `purpose`, `expires_at` |
| `consent.revoked` | la persona (o la piattaforma) ha terminato una concessione | come sopra più `revoked_at`, `reason` |
| `document.finalized` | è stato finalizzato e verificato il checksum di un caricamento di documenti | `id`, `patient`, `content_type`, `size`, `sha256` |
| `proposal.created` | un agente IA ha proposto una scrittura in un record che puoi vedere; attende l'approvazione della persona | `id`, `patient`, `action`, `resource_type` |
| `ping` | hai richiesto una consegna di prova | `endpoint` |

Gli endpoint sono per progetto **e ambiente**: una chiave `sk_test_` gestisce gli endpoint sandbox,
una chiave `sk_live_` quelli di produzione. Gli URL devono essere endpoint pubblici `https://`. La registrazione (e qualsiasi successiva modifica di `url`) rifiuta indirizzi locali, privati, link-local, metadata e altri indirizzi riservati — `localhost`, `127.0.0.1`, `::1`, `10.0.0.0/8`, `192.168.0.0/16`, `fd00::/8` e simili — e nomi che si risolvono in essi, con `400` `invalid` sul campo `url`; questo si applica anche nella sandbox. Ogni consegna controlla nuovamente l'indirizzo e si connette solo agli indirizzi IP che ha appena controllato, quindi un nome che in seguito punta a una rete privata (reindirizzamento DNS) non riceve nulla. Per ricevere eventi sulla tua macchina durante lo sviluppo, esponila tramite un tunnel HTTPS pubblico e registra l'indirizzo del tunnel.
Fino a 10 endpoint per progetto e ambiente.

## La consegna

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

Rispondere con qualsiasi `2xx` entro 10 secondi. Qualsiasi altra cosa (o un timeout) è un fallimento e la
consegna viene ritentata con backoff per un massimo di 24 ore (10 tentativi), quindi contrassegnata come `exhausted`.
Le consegne sono "at-least-once" e possono arrivare fuori ordine: utilizzare `Anpheros-Event-Id` per
de-duplicare e `data.version` per ignorare aggiornamenti obsoleti.

## Verifica della firma

`v1` è HMAC-SHA256 con il tuo segreto su `<t>.<raw body>`; rifiuta se `t` è più vecchio di 5 minuti.

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

## Operare

- `GET /v1/webhooks/{id}/deliveries?status=failed` — il log, dal più recente al meno recente, con codice di stato ed errore.
- `POST /v1/webhooks/{id}/deliveries/{delivery_id}/redeliver` — invia nuovamente ora.
- `POST /v1/webhooks/{id}/ping` — un evento di test.
- `POST /v1/webhooks/{id}/rotate` — nuovo segreto (il precedente cessa immediatamente; aggiorna prima il tuo server se non puoi permetterti una interruzione: registra un secondo endpoint, quindi elimina il precedente).
- `PATCH /v1/webhooks/{id}` con `{"active": false}` mette in pausa le consegne; quelle in sospeso vengono eliminate quando si elimina l'endpoint.

Entrambi gli SDK espongono questo sotto `webhooks` (`create`, `list`, `get`, `update`, `delete`, `rotateSecret`, `ping`, `deliveries`, `redeliver`).
