# Webhooks

> Eventos assinados, com retentativas e reduzidos para escritas de recursos, alterações de consentimento e documentos finalizados, com verificação de assinatura em TypeScript, Dart e Python.

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

A Plataforma Anpheros pode chamar o seu servidor quando algo acontece no seu projeto: um recurso foi escrito, uma pessoa concedeu ou revogou acesso, um documento foi finalizado. Os eventos são **reduzidos**: IDs, tipo, versão — nunca conteúdo clínico. Leia o recurso através da API se precisar dele. Cada entrega é assinada; verifique a assinatura antes de confiar nela.

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

A resposta contém `secret` (`whsec_…`) **uma vez**. Armazene-o; necessita dele para verificar assinaturas.
`events` aceita `["*"]` ou qualquer um dos seguintes:

| tipo | quando | `data` |
|---|---|---|
| `resource.created` / `resource.updated` / `resource.deleted` | o seu projeto escreveu um recurso FHIR (REST v1, FHIR API ou um bundle) — **ou outro projeto escreveu num registo ao qual tem acesso** (criou o paciente, ou detém uma concessão ativa); então `data.external` é `true` e `data.source` indica quem escreveu: `author_type` (paciente / praticante / dispositivo / importação / ai), `organization` (o nome de exibição em nome do qual o escritor agiu), `practitioner` (nome do diretório, quando conhecido), `source_system` | `resource_type`, `id`, `patient` (o id que vê), `version`, `last_updated`, e para escritas externas `external`, `source` |
| `consent.granted` | uma pessoa concedeu à sua aplicação acesso a partir da página de consentimento (ou uma aplicação confiável ligou o seu registo) | `grant_id`, `patient`, `application_id`, `scopes`, `purpose`, `expires_at` |
| `consent.revoked` | a pessoa (ou a plataforma) terminou uma concessão | o mesmo que acima mais `revoked_at`, `reason` |
| `document.finalized` | o upload de um documento foi finalizado e o seu checksum verificado | `id`, `patient`, `content_type`, `size`, `sha256` |
| `proposal.created` | um agente de IA propôs uma escrita num registo que pode ver; aguarda a aprovação da pessoa | `id`, `patient`, `action`, `resource_type` |
| `ping` | solicitou uma entrega de teste | `endpoint` |

Os endpoints são por projeto **e ambiente**: uma chave `sk_test_` gere os endpoints sandbox,
uma chave `sk_live_` os de produção. Os URLs devem ser endpoints públicos `https://`. O registo (e qualquer alteração posterior de `url`) recusa endereços locais, privados, link-local, de metadados e outros endereços reservados — `localhost`, `127.0.0.1`, `::1`, `10.0.0.0/8`, `192.168.0.0/16`, `fd00::/8` e similares — e nomes que se resolvam a eles, com `400` `invalid` no campo `url`; isto aplica-se também na sandbox. Cada entrega verifica novamente o endereço e conecta-se apenas aos endereços IP que acabou de verificar, pelo que um nome que mais tarde aponte para uma rede privada (rebind DNS) não recebe nada. Para receber eventos na sua própria máquina durante o desenvolvimento, exponha-a através de um túnel HTTPS público e registe o endereço do túnel.
Até 10 endpoints por projeto e ambiente.

## A 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 com qualquer `2xx` em 10 segundos. Qualquer outra coisa (ou um timeout) é uma falha e a
entrega é retentada com backoff por até 24 horas (10 tentativas), depois marcada como `exhausted`.
As entregas são "at-least-once" e podem chegar fora de ordem: use `Anpheros-Event-Id` para
desduplicar e `data.version` para ignorar atualizações desatualizadas.

## Verificar a assinatura

`v1` é HMAC-SHA256 com o seu segredo sobre `<t>.<raw body>`; rejeitar se `t` for mais antigo que 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` — o registo, mais recente primeiro, com código de estado e erro.
- `POST /v1/webhooks/{id}/deliveries/{delivery_id}/redeliver` — enviar um novamente agora.
- `POST /v1/webhooks/{id}/ping` — um evento de teste.
- `POST /v1/webhooks/{id}/rotate` — novo segredo (o antigo para imediatamente; atualize o seu servidor primeiro se não puder suportar uma interrupção: registe um segundo endpoint, depois elimine o antigo).
- `PATCH /v1/webhooks/{id}` com `{"active": false}` pausa as entregas; as pendentes são descartadas quando elimina o endpoint.

Ambos os SDKs expõem isto em `webhooks` (`create`, `list`, `get`, `update`, `delete`, `rotateSecret`, `ping`, `deliveries`, `redeliver`).
