# Webhooks

> Eventos firmados, reintentados y delgados para escrituras de recursos, cambios de consentimiento y documentos finalizados, con verificación de firma en TypeScript, Dart y Python.

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

La Plataforma Anpheros puede llamar a su servidor cuando algo sucede en su proyecto: se escribió un recurso, una persona otorgó o revocó acceso, se finalizó un documento. Los eventos son **delgados**: ids, tipo, versión — nunca contenido clínico. Lea el recurso a través de la API si lo necesita. Cada entrega está firmada; verifique la firma antes de confiar en ella.

## Registrar 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 respuesta contiene `secret` (`whsec_…`) **una vez**. Guárdelo; lo necesitará para verificar firmas.
`events` acepta `["*"]` o cualquiera de:

| type | when | `data` |
|---|---|---|
| `resource.created` / `resource.updated` / `resource.deleted` | su proyecto escribió un recurso FHIR (REST v1, FHIR API o un bundle) — **o otro proyecto escribió en un registro al que tiene acceso** (usted creó el paciente, o tiene una concesión activa); entonces `data.external` es `true` y `data.source` dice quién lo escribió: `author_type` (patient / practitioner / device / import / ai), `organization` (el nombre para mostrar en nombre de quién actuó el escritor), `practitioner` (nombre del directorio, cuando se conoce), `source_system` | `resource_type`, `id`, `patient` (el id que ve), `version`, `last_updated`, y para escrituras externas `external`, `source` |
| `consent.granted` | una persona otorgó acceso a su aplicación desde la página de consentimiento (o una aplicación de confianza vinculó su registro) | `grant_id`, `patient`, `application_id`, `scopes`, `purpose`, `expires_at` |
| `consent.revoked` | la persona (o la plataforma) finalizó una concesión | igual que arriba más `revoked_at`, `reason` |
| `document.finalized` | se finalizó y se verificó la suma de comprobación de una carga de documento | `id`, `patient`, `content_type`, `size`, `sha256` |
| `proposal.created` | un agente de IA propuso una escritura en un registro que puede ver; espera la aprobación de la persona | `id`, `patient`, `action`, `resource_type` |
| `ping` | solicitó una entrega de prueba | `endpoint` |

Los endpoints son por proyecto **y entorno**: una clave `sk_test_` gestiona los endpoints de sandbox,
una clave `sk_live_` los de producción. Las URLs deben ser endpoints públicos `https://`. El registro (y cualquier cambio posterior de `url`) rechaza direcciones locales, privadas, link-local, de metadatos y otras reservadas — `localhost`, `127.0.0.1`, `::1`, `10.0.0.0/8`, `192.168.0.0/16`, `fd00::/8` y similares — y nombres que resuelven a ellas, con `400` `invalid` en el campo `url`; esto se aplica también en el sandbox. Cada entrega vuelve a verificar la dirección y se conecta solo a las direcciones IP que acaba de verificar, por lo que un nombre que luego apunta a una red privada (DNS rebinding) no recibe nada. Para recibir eventos en su propia máquina mientras desarrolla, expóngala a través de un túnel HTTPS público y registre la dirección del túnel.
Hasta 10 endpoints por proyecto y entorno.

## La entrega

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

Responda con cualquier `2xx` en un plazo de 10 segundos. Cualquier otra cosa (o un tiempo de espera agotado) es un fallo y la
entrega se reintenta con retroceso (backoff) hasta por 24 horas (10 intentos), luego se marca como `exhausted`.
Las entregas son "al menos una vez" (at-least-once) y pueden llegar desordenadas: utilice `Anpheros-Event-Id` para
deduplicar y `data.version` para ignorar actualizaciones obsoletas.

## Verificar la firma

`v1` es HMAC-SHA256 con tu secreto sobre `<t>.<raw body>`; rechaza si `t` es anterior a 5 minutos.

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

## Operar

- `GET /v1/webhooks/{id}/deliveries?status=failed` — el registro, más reciente primero, con código de estado y error.
- `POST /v1/webhooks/{id}/deliveries/{delivery_id}/redeliver` — enviar uno de nuevo ahora.
- `POST /v1/webhooks/{id}/ping` — un evento de prueba.
- `POST /v1/webhooks/{id}/rotate` — secreto nuevo (el antiguo se detiene inmediatamente; actualice su servidor primero si no puede permitirse una interrupción: registre un segundo endpoint, luego elimine el antiguo).
- `PATCH /v1/webhooks/{id}` con `{"active": false}` pausa las entregas; las pendientes se descartan cuando elimina el endpoint.

Ambos SDK exponen esto bajo `webhooks` (`create`, `list`, `get`, `update`, `delete`, `rotateSecret`, `ping`, `deliveries`, `redeliver`).
