Webhooks
Der volle Auftrags-Lebenszyklus als signierte Push-Benachrichtigungen — statt Polling.
Webhooks benachrichtigen Ihr System über jeden relevanten Statuswechsel
eines Auftrags. Die Verwaltung erfordert den Scope webhooks:manage;
die genauen Endpoint-Pfade stehen in der API-Referenz.
Endpoint anlegen
Sie registrieren eine HTTPS-URL (HTTP wird abgelehnt), optional mit einem Event-Filter (ohne Filter: alle Events). Die Antwort auf das Anlegen enthält das Signatur-Secret — nur dieses eine Mal. Speichern Sie es direkt in Ihrem Secret-Management; es kann später nicht erneut abgerufen, nur der Endpoint neu angelegt werden.
Handshake (Verifizierung)
Beim Anlegen und bei jeder URL-Änderung verifiziert Hemostat den Endpoint, bevor Events zugestellt werden:
- Hemostat ruft
GET <ihre-url>?verification=<nonce>auf. - Ihr Endpoint antwortet mit
2xxund gibt den Wert vonverificationim Response-Body zurück (Echo). - Erst danach wechselt der Endpoint von
PENDING_VERIFICATIONaufENABLED.
Schlägt der Handshake fehl, können Sie ihn über die Endpoint-Verwaltung erneut anstoßen; außerdem lässt sich ein Beispiel-Event zum Testen der Verarbeitung auslösen (siehe API-Referenz).
Zustellung: Header
Jede Zustellung ist ein POST mit JSON-Body und diesen Headern:
| Header | Inhalt |
|---|---|
X-Hemostat-Signature |
t=<unix_ts>,v1=<hex_hmac> — Signatur, siehe unten |
X-Hemostat-Event |
Event-Name, z. B. order.delivered |
X-Hemostat-Delivery |
eindeutige Zustellungs-ID — nutzen Sie sie zur Dedupe, dieselbe Zustellung kann im Fehlerfall mehrfach ankommen |
Signatur verifizieren
Die Signatur ist HMAC-SHA256 über die Zeichenkette
"<t>.<raw_body>" mit Ihrem Endpoint-Secret als Schlüssel
(Stripe-Stil):
X-Hemostat-Signature: t=1758100000,v1=5f6a1c9e…
Regeln:
- HMAC über den rohen, unveränderten Request-Body bilden — nicht über re-serialisiertes JSON.
tgegen die aktuelle Zeit prüfen: mehr als 5 Minuten Abweichung → ablehnen (Schutz vor Replay).- Vergleich in konstanter Zeit (
compare_digest/timingSafeEqual). - Requests mit fehlender oder ungültiger Signatur verwerfen und nicht verarbeiten.
Python
import hashlib
import hmac
import time
TOLERANCE_SECONDS = 300 # 5 Minuten
def verify_signature(secret: str, signature_header: str, raw_body: bytes) -> bool:
"""Verifiziert X-Hemostat-Signature. raw_body ist der unveränderte Request-Body."""
try:
parts = dict(p.split("=", 1) for p in signature_header.split(","))
timestamp = int(parts["t"])
received = parts["v1"]
except (ValueError, KeyError):
return False
if abs(time.time() - timestamp) > TOLERANCE_SECONDS:
return False
signed_payload = f"{timestamp}.".encode() + raw_body
expected = hmac.new(secret.encode(), signed_payload, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, received)
# Beispiel (z. B. in einem Flask-Handler):
# ok = verify_signature(WEBHOOK_SECRET, request.headers["X-Hemostat-Signature"], request.get_data())
if __name__ == "__main__":
secret = "whsec_beispiel"
body = b'{"event":"order.delivered"}'
t = int(time.time())
sig = hmac.new(secret.encode(), f"{t}.".encode() + body, hashlib.sha256).hexdigest()
assert verify_signature(secret, f"t={t},v1={sig}", body)
print("Signatur gültig")
Node.js
const crypto = require("node:crypto");
const TOLERANCE_SECONDS = 300; // 5 Minuten
/**
* Verifiziert X-Hemostat-Signature.
* @param {string} secret Endpoint-Secret
* @param {string} header Wert von X-Hemostat-Signature
* @param {Buffer} rawBody unveränderter Request-Body (kein re-serialisiertes JSON)
*/
function verifySignature(secret, header, rawBody) {
const parts = Object.fromEntries(
header.split(",").map((p) => p.split(/=(.*)/s).slice(0, 2))
);
const timestamp = Number.parseInt(parts.t, 10);
const received = parts.v1;
if (!Number.isFinite(timestamp) || !received) return false;
if (Math.abs(Date.now() / 1000 - timestamp) > TOLERANCE_SECONDS) return false;
const expected = crypto
.createHmac("sha256", secret)
.update(`${timestamp}.`)
.update(rawBody)
.digest("hex");
const a = Buffer.from(expected, "hex");
const b = Buffer.from(received, "hex");
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
// Beispiel Express: Raw-Body sichern, damit die Signatur stimmt.
// app.use(express.json({ verify: (req, res, buf) => { req.rawBody = buf; } }));
// const ok = verifySignature(SECRET, req.get("X-Hemostat-Signature"), req.rawBody);
// Selbsttest
const secret = "whsec_beispiel";
const body = Buffer.from('{"event":"order.delivered"}');
const t = Math.floor(Date.now() / 1000);
const sig = crypto.createHmac("sha256", secret).update(`${t}.`).update(body).digest("hex");
console.log(verifySignature(secret, `t=${t},v1=${sig}`, body) ? "Signatur gültig" : "FEHLER");
Event-Typen
| Event | Bedeutung |
|---|---|
order.submitted |
Der Auftrag wurde eingereicht und ist im System angelegt. |
order.assigned |
Der Auftrag wurde einer Transport-Organisation zugewiesen. |
order.accepted |
Die Transport-Organisation hat den Auftrag angenommen. |
order.driver_assigned |
Dem Auftrag wurde ein Fahrer zugeteilt. |
order.pickup_started |
Die Abholung am Abholort hat begonnen. |
order.sealed |
Das Transportgut wurde versiegelt und das Siegel dokumentiert. |
order.in_transit |
Der Transport ist unterwegs zum Zustellort. |
order.delivered |
Der Auftrag wurde zugestellt. |
order.closed |
Der Auftrag ist abgeschlossen. |
order.cancelled |
Der Auftrag wurde storniert. |
order.updated |
Auftragsdaten wurden geändert. |
order.incident_reported |
Zu dem Auftrag wurde ein Vorkommnis gemeldet. |
order.escalated |
Der Auftrag wurde eskaliert. |
Payload
Payloads sind bewusst schlank: eine Zusammenfassung plus Link.
data.order enthält die Kernfelder des Auftrags (inklusive Ihrer
external_id), links.order den API-Link zum vollständigen Auftrag.
Positions- und Nachweisdaten sind nicht enthalten — holen Sie Details
bei Bedarf über die API (Datensparsamkeit, falls eine Endpoint-URL
kompromittiert wird).
Beispiel (Feldbestand kann additiv wachsen, siehe Versionierung):
{
"event": "order.delivered",
"occurred_at": "2026-09-17T10:41:23Z",
"data": {
"order": {
"id": "6f1c9e2a-…",
"external_id": "BESTELLUNG-4711",
"status": "delivered"
}
},
"links": {
"order": "https://my.hemostat.de/api/partner/v1/orders/6f1c9e2a-…"
}
}
Antworten: die Immer-200-Regel
Antworten Sie auf jede Zustellung schnell mit 2xx — auch wenn Sie
das Event fachlich nicht interessiert oder Sie es als Duplikat
erkennen. Nehmen Sie das Event entgegen, bestätigen Sie es, und
verarbeiten Sie es asynchron. Jede Nicht-2xx-Antwort (und jeder
Timeout) zählt als Fehlschlag und löst Wiederholungen aus.
Retry-Verhalten und Status BROKEN
- Pro Event bis zu 6 Zustellversuche mit wachsenden Abständen —
vom ersten Retry nach etwa 1 Minute bis zum letzten nach etwa
24 Stunden. Danach gilt die Zustellung als erschöpft
(
EXHAUSTED) und wird nicht erneut versucht. - Schlägt ein Endpoint dauerhaft fehl (viele Fehlschläge in Folge bzw.
mehrere Tage ohne erfolgreiche Zustellung), wird er auf
BROKENgesetzt: Es werden keine weiteren Events zugestellt, und die Administratoren Ihrer Organisation werden benachrichtigt. - Nach Behebung der Störung reaktivieren Sie den Endpoint über die
Endpoint-Verwaltung; verpasste Aufträge gleichen Sie über
GET /orders?updated_since=…ab.