# Webhooks

> Υπογεγραμμένα, επαναδοκιμαζόμενα, λιτά συμβάντα για εγγραφές πόρων, αλλαγές συναίνεσης και οριστικοποιημένα έγγραφα, με επαλήθευση υπογραφής σε TypeScript, Dart και Python.

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

Η πλατφόρμα Anpheros μπορεί να καλέσει τον διακομιστή σας όταν συμβαίνει κάτι στο έργο σας: ένας πόρος
γράφτηκε, ένα άτομο παραχώρησε ή ανακάλεσε πρόσβαση, ένα έγγραφο οριστικοποιήθηκε. Τα συμβάντα είναι
**λιτά**: ids, τύπος, έκδοση — ποτέ κλινικό περιεχόμενο. Διαβάστε τον πόρο μέσω του API
αν τον χρειάζεστε. Κάθε παράδοση είναι υπογεγραμμένη· επαληθεύστε την υπογραφή πριν την εμπιστευτείτε.

## Καταχώρηση ενός 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"}'
```

Η απάντηση περιέχει `secret` (`whsec_…`) **μία φορά**. Αποθηκεύστε το. θα το χρειαστείτε για την επαλήθευση υπογραφών.
Το `events` δέχεται `["*"]` ή οποιοδήποτε από τα ακόλουθα:

| τύπος | πότε | `data` |
|---|---|---|
| `resource.created` / `resource.updated` / `resource.deleted` | το project σας έγραψε ένα FHIR resource (REST v1, FHIR API ή ένα bundle) — **ή ένα άλλο project έγραψε σε ένα record στο οποίο έχετε πρόσβαση** (εσείς δημιουργήσατε τον ασθενή, ή έχετε μια ενεργή άδεια). Τότε το `data.external` είναι `true` και το `data.source` λέει ποιος έγραψε: `author_type` (ασθενής / επαγγελματίας υγείας / συσκευή / εισαγωγή / ai), `organization` (το εμφανιζόμενο όνομα για λογαριασμό του οποίου ενήργησε ο συγγραφέας), `practitioner` (όνομα από τον κατάλογο, όταν είναι γνωστό), `source_system` | `resource_type`, `id`, `patient` (το id που βλέπετε), `version`, `last_updated`, και για εξωτερικές εγγραφές `external`, `source` |
| `consent.granted` | ένα άτομο παραχώρησε στην εφαρμογή σας πρόσβαση από τη σελίδα συναίνεσης (ή μια αξιόπιστη εφαρμογή συνέδεσε το record της) | `grant_id`, `patient`, `application_id`, `scopes`, `purpose`, `expires_at` |
| `consent.revoked` | το άτομο (ή η πλατφόρμα) έληξε μια άδεια | ίδια με παραπάνω συν `revoked_at`, `reason` |
| `document.finalized` | η μεταφόρτωση ενός εγγράφου ολοκληρώθηκε και ελέγχθηκε το checksum | `id`, `patient`, `content_type`, `size`, `sha256` |
| `proposal.created` | ένας AI agent πρότεινε μια εγγραφή σε ένα record που μπορείτε να δείτε. Περιμένει την έγκριση του ατόμου. | `id`, `patient`, `action`, `resource_type` |
| `ping` | ζητήσατε μια δοκιμαστική παράδοση | `endpoint` |

Τα endpoints είναι ανά project **και περιβάλλον**: ένα κλειδί `sk_test_` διαχειρίζεται τα sandbox endpoints,
ένα κλειδί `sk_live_` τα production ones. Οι διευθύνσεις URL πρέπει να είναι δημόσια `https://` endpoints. Η καταχώρηση (και οποιαδήποτε μεταγενέστερη αλλαγή του `url`) απορρίπτει τοπικές, ιδιωτικές, link-local, metadata και άλλες δεσμευμένες διευθύνσεις — `localhost`, `127.0.0.1`, `::1`, `10.0.0.0/8`, `192.168.0.0/16`, `fd00::/8` και παρόμοιες — και ονόματα που επιλύονται σε αυτές, με `400` `invalid` στο πεδίο `url`. Αυτό ισχύει και στο sandbox. Κάθε παράδοση ελέγχει ξανά τη διεύθυνση και συνδέεται μόνο με τις IP διευθύνσεις που μόλις έλεγξε, οπότε ένα όνομα που αργότερα δείχνει σε ένα ιδιωτικό δίκτυο (DNS rebinding) δεν λαμβάνει τίποτα. Για να λαμβάνετε συμβάντα στη δική σας μηχανή κατά την ανάπτυξη, εκθέστε την μέσω ενός δημόσιου HTTPS tunnel και καταχωρήστε τη διεύθυνση του tunnel.
Έως 10 endpoints ανά project και περιβάλλον.

## Η παράδοση

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

Απαντήστε με οποιοδήποτε `2xx` εντός 10 δευτερολέπτων. Οτιδήποτε άλλο (ή χρονικό όριο) αποτελεί αποτυχία και η
παράδοση επαναλαμβάνεται με εκθετική αύξηση καθυστέρησης για έως και 24 ώρες (10 προσπάθειες), στη συνέχεια επισημαίνεται ως `exhausted`.
Οι παραδόσεις είναι "τουλάχιστον μία φορά" (at-least-once) και ενδέχεται να φτάσουν εκτός σειράς: χρησιμοποιήστε το `Anpheros-Event-Id` για
απο-διπλοποίηση και το `data.version` για αγνόηση παλιών ενημερώσεων.

## Επαλήθευση της υπογραφής

Το `v1` είναι HMAC-SHA256 με το μυστικό σας πάνω από το `<t>.<raw body>`· απορρίψτε εάν το `t` είναι παλαιότερο από 5 λεπτά.

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

## Λειτουργία

- `GET /v1/webhooks/{id}/deliveries?status=failed` — το αρχείο καταγραφής, με τα νεότερα πρώτα, με κωδικό κατάστασης και σφάλμα.
- `POST /v1/webhooks/{id}/deliveries/{delivery_id}/redeliver` — αποστολή ενός ξανά τώρα.
- `POST /v1/webhooks/{id}/ping` — ένα δοκιμαστικό συμβάν.
- `POST /v1/webhooks/{id}/rotate` — νέο μυστικό (το παλιό σταματά αμέσως· ενημερώστε τον διακομιστή σας πρώτα αν δεν μπορείτε να αντέξετε ένα κενό: καταχωρήστε ένα δεύτερο τελικό σημείο, στη συνέχεια διαγράψτε το παλιό).
- `PATCH /v1/webhooks/{id}` με `{"active": false}` διακόπτει τις παραδόσεις· οι εκκρεμείς παραδόσεις απορρίπτονται όταν διαγράφετε το τελικό σημείο.

Και τα δύο SDK εκθέτουν αυτό κάτω από το `webhooks` (`create`, `list`, `get`, `update`, `delete`, `rotateSecret`, `ping`, `deliveries`, `redeliver`).
