Skip to main content

Webhook-Sicherheit

Jede Webhook-Zustellung für ein Abonnement mit webhook_secret enthält eine Signatur, die Sie überprüfen sollten, um sicherzustellen, dass die Anfrage von RxScale stammt und nicht manipuliert wurde.

Signaturüberprüfung

Jede Webhook-Anfrage enthält ihre Signatur im Header X-Webhook-Signature:
  • Die Signatur ist der HMAC-SHA256 des unveränderten Anfragekörpers (genau so, wie er empfangen wurde), kodiert als Hexadezimalzeichenkette in Kleinbuchstaben (64 Zeichen). Es gibt kein Präfix wie sha256=.
  • Der Schlüssel ist das webhook_secret, das Sie bei der Registrierung des Abonnements erhalten haben, als UTF-8-Zeichenkette genau so verwendet, wie es zurückgegeben wurde. Dekodieren Sie es nicht.
  • Jedes Abonnement (Ziel-URL, Ereignistyp und Apotheken-Zuordnung) hat ein eigenes webhook_secret. Wenn Sie mehrere Ereignistypen am selben Endpoint empfangen, wählen Sie anhand des Headers X-Webhook-Event das Secret des passenden Abonnements.
  • Es gibt keinen separaten Zeitstempel-Header. Das Feld timestamp im Anfragekörper ist durch die Signatur abgedeckt.
Berechnen Sie den HMAC über die Rohbytes, bevor Sie das JSON parsen. Wenn Sie den Anfragekörper parsen und erneut serialisieren, ändern sich die Bytes und die Signatur stimmt nicht mehr.

Beispiel (Python)

Beispiel (Node.js)

Test-Webhooks enthalten den Header X-Webhook-Test: true, damit Sie sie von echten Zustellungen unterscheiden können. Über die Karte Webhook testen im Apothekenportal gesendete Test-Webhooks sind nicht signiert. Über die Management API gesendete Test-Webhooks werden wie eine echte Zustellung signiert, sofern ein aktives Abonnement zu Ziel-URL und Ereignistyp passt; andernfalls sind sie unsigniert.

Webhook-Secret

  • Das webhook_secret wird nur einmal zurückgegeben: in der Antwort auf POST /v1/external_pharmacy_api/webhooks/ oder im Apothekenportal direkt nach der Registrierung eines Webhooks. Es wird verschlüsselt gespeichert und kann später nicht abgerufen werden — GET /v1/external_pharmacy_api/webhooks/ gibt es nie zurück.
  • Jedes Abonnement hat ein eigenes Secret. X-Webhook-Event nennt den Ereignistyp einer Zustellung, aber nichts identifiziert das Abonnement selbst. Abonnements mit demselben Ereignistyp — zum Beispiel eines pro Apotheke einer Gruppe — lassen sich daher nicht unterscheiden. Verwenden Sie für diese jeweils eine eigene Ziel-URL (zum Beispiel einen anderen Pfad oder Query-Parameter), damit Sie immer wissen, mit welchem Secret Sie prüfen müssen.
  • Auch der Wert eines benutzerdefinierten Headers (header_value) wird nie zurückgegeben, nur sein Name (header_key).

Secret rotieren

Es gibt keinen eigenen Endpoint für die Rotation. Sie haben zwei Möglichkeiten: Dasselbe Abonnement erneut registrieren. Senden Sie POST /v1/external_pharmacy_api/webhooks/ erneut mit denselben Werten für notification_type, target und pharmacy_uid. RxScale behält die uid des Abonnements bei und gibt ein neues webhook_secret zurück, das sofort gilt — das alte Secret funktioniert nicht mehr. Jede Zustellung wird beim Versand signiert, daher verwenden alle Zustellungen nach dieser Anfrage, auch Wiederholungen früherer Ereignisse, das neue Secret. Speichern Sie es sofort. Die Anfrage ersetzt außerdem den benutzerdefinierten Header: Wenn Sie header_key und header_value weglassen, wird er entfernt. Mit Überlappung rotieren. Registrieren Sie ein zweites Abonnement für dasselbe Ereignis mit einer anderen Ziel-URL und prüfen Sie die Zustellungen an diese URL mit dem Secret des neuen Abonnements. Löschen Sie anschließend das alte Abonnement mit DELETE /v1/external_pharmacy_api/webhooks/{subscription_uid}. Solange beide Abonnements bestehen, wird jedes Ereignis an beide zugestellt, jeweils signiert mit dem Secret des jeweiligen Abonnements. Details zu den Endpoints finden Sie unter Webhooks (External Pharmacy API).

Best Practices

Verarbeiten Sie niemals Webhook-Payloads, ohne vorher die Signatur zu überprüfen. Dies schützt vor gefälschten Anfragen.
Verwenden Sie immer hmac.compare_digest (Python) oder crypto.timingSafeEqual (Node.js), um Timing-Angriffe zu verhindern.
Sie müssen innerhalb des Zustellungs-Timeouts von 30 Sekunden eine 2xx-Antwort zurücksenden. Eine langsamere Antwort (oder keine Antwort) gilt als fehlgeschlagene Zustellung und wird mit exponentiellem Backoff wiederholt — siehe Wiederholungsrichtlinie.Wir empfehlen dennoch, deutlich schneller zu bestätigen — ein guter Zielwert liegt unter 5 Sekunden —, indem Sie 2xx zurückgeben, sobald Sie die Payload gespeichert haben, und aufwändige Verarbeitung anschließend asynchron durchführen. Die 5 Sekunden sind eine Empfehlung, keine Anforderung; erzwungen wird nur das Timeout von 30 Sekunden.
Derselbe Stand kann mehrfach zugestellt werden, und Zustellungen können in anderer Reihenfolge eintreffen. Verarbeiten Sie Ereignisse anhand von data.uid und behandeln Sie jede Payload als aktuellen Gesamtstand. Deduplizieren Sie nicht anhand von timestamp — der Wert ändert sich bei jedem Zustellversuch. Siehe Ereignisse idempotent verarbeiten.
Der timestamp im Anfragekörper wird beim Versand eines Zustellversuchs gesetzt und ist durch die Signatur abgedeckt. Um das erneute Einspielen einer abgefangenen Anfrage zu erschweren, können Sie Zustellungen ablehnen, deren timestamp um mehr als einige Minuten von Ihrer Serverzeit abweicht. Wiederholungen sind davon nicht betroffen, da jeder Versuch einen neuen timestamp trägt.