# Webhooks

> Événements signés, retentés et légers pour les écritures de ressources, les changements de consentement et les documents finalisés, avec vérification de signature en TypeScript, Dart et Python.

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

La plateforme Anpheros peut appeler votre serveur lorsqu'un événement se produit dans votre projet : une ressource a été écrite, une personne a accordé ou révoqué l'accès, un document a été finalisé. Les événements sont **légers** : identifiants, type, version — jamais de contenu clinique. Lisez la ressource via l'API si vous en avez besoin. Chaque livraison est signée ; vérifiez la signature avant de lui faire confiance.

## Enregistrer un point de terminaison

```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 réponse contient `secret` (`whsec_…`) **une seule fois**. Stockez-le ; vous en aurez besoin pour vérifier les signatures.
`events` accepte `["*"]` ou l'un des éléments suivants :

| type | quand | `data` |
|---|---|---|
| `resource.created` / `resource.updated` / `resource.deleted` | votre projet a écrit une ressource FHIR (REST v1, API FHIR ou un bundle) — **ou un autre projet a écrit dans un enregistrement auquel vous avez accès** (vous avez créé le patient, ou vous détenez une autorisation active) ; alors `data.external` est `true` et `data.source` indique qui a écrit : `author_type` (patient / praticien / appareil / import / ia), `organization` (le nom d'affichage au nom duquel l'auteur a agi), `practitioner` (nom du répertoire, lorsque connu), `source_system` | `resource_type`, `id`, `patient` (l'identifiant que vous voyez), `version`, `last_updated`, et pour les écritures externes `external`, `source` |
| `consent.granted` | une personne a accordé à votre application l'accès depuis la page de consentement (ou une application de confiance a lié son enregistrement) | `grant_id`, `patient`, `application_id`, `scopes`, `purpose`, `expires_at` |
| `consent.revoked` | la personne (ou la plateforme) a mis fin à une autorisation | identique à ci-dessus plus `revoked_at`, `reason` |
| `document.finalized` | un téléchargement de document a été finalisé et sa somme de contrôle vérifiée | `id`, `patient`, `content_type`, `size`, `sha256` |
| `proposal.created` | un agent IA a proposé une écriture dans un enregistrement que vous pouvez voir ; cela attend l'approbation de la personne | `id`, `patient`, `action`, `resource_type` |
| `ping` | vous avez demandé une livraison de test | `endpoint` |

Les points de terminaison sont par projet **et par environnement** : une clé `sk_test_` gère les points de terminaison sandbox,
une clé `sk_live_` les points de terminaison de production. Les URL doivent être des points de terminaison publics `https://`. L'enregistrement (et tout changement ultérieur de `url`) refuse les adresses locales, privées, link-local, de métadonnées et autres adresses réservées — `localhost`, `127.0.0.1`, `::1`, `10.0.0.0/8`, `192.168.0.0/16`, `fd00::/8` et similaires — ainsi que les noms qui s'y résolvent, avec `400` `invalid` sur le champ `url` ; cela s'applique également dans le sandbox. Chaque livraison vérifie à nouveau l'adresse et ne se connecte qu'aux adresses IP qu'elle vient de vérifier, de sorte qu'un nom qui pointe plus tard vers un réseau privé (détournement DNS) ne reçoit rien. Pour recevoir des événements sur votre propre machine pendant le développement, exposez-la via un tunnel HTTPS public et enregistrez l'adresse du tunnel.
Jusqu'à 10 points de terminaison par projet et par environnement.

## La livraison

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

Répondez avec tout `2xx` dans les 10 secondes. Tout le reste (ou un délai d'attente) est un échec et la
livraison est retentée avec un backoff jusqu'à 24 heures (10 tentatives), puis marquée `exhausted`.
Les livraisons sont "au moins une fois" et peuvent arriver dans le désordre : utilisez `Anpheros-Event-Id` pour
la déduplication et `data.version` pour ignorer les mises à jour obsolètes.

## Vérifier la signature

`v1` est HMAC-SHA256 avec votre secret sur `<t>.<raw body>` ; rejeter si `t` a plus de 5 minutes.

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

## Opérer

- `GET /v1/webhooks/{id}/deliveries?status=failed` — le journal, le plus récent en premier, avec le code de statut et l'erreur.
- `POST /v1/webhooks/{id}/deliveries/{delivery_id}/redeliver` — renvoyer un maintenant.
- `POST /v1/webhooks/{id}/ping` — un événement de test.
- `POST /v1/webhooks/{id}/rotate` — nouveau secret (l'ancien s'arrête immédiatement ; mettez à jour votre serveur d'abord si vous ne pouvez pas vous permettre une interruption : enregistrez un deuxième point de terminaison, puis supprimez l'ancien).
- `PATCH /v1/webhooks/{id}` avec `{"active": false}` met les livraisons en pause ; les livraisons en attente sont supprimées lorsque vous supprimez le point de terminaison.

Les deux SDK exposent ceci sous `webhooks` (`create`, `list`, `get`, `update`, `delete`, `rotateSecret`, `ping`, `deliveries`, `redeliver`).
