Webhooks und die Idempotenz von Veranstaltungen
TL; DR
Ein guter Webhook ist ein signiertes (HMAC/mTLS), zusammenfassendes und idempotentes Ereignis, das nach dem At-Least-Once-Modell mit exponentiellem Backoff und Deduplizierung beim Empfänger geliefert wird. Vereinbaren Sie einen Umschlag ('event _ id', 'type', 'ts', 'version', 'attempt', 'signature'), ein Zeitfenster (≤5 min), Antwortcodes, Retrays, DLQ und einen Status-Endpunkt.
1) Rollen und Liefermodell
Absender (Sie/Anbieter): formt das Ereignis, signiert, versucht bis zu 2xx zu liefern, retrait bei 3xx/4xx/5xx (außer dem expliziten „nicht akzeptieren“), führt DLQ, gibt replay API.
Empfänger (Partner/Ihr Service): überprüft die Signatur/das Zeitfenster, macht dedup und idempotente Verarbeitung, antwortet mit korrektem Code, liefert/status und/ack replay durch 'event _ id'.
Garantien: at-least-once. Der Empfänger muss in der Lage sein, Duplikate und Auftragsänderungen zu verarbeiten.
2) Ereignis-Umschlag (envelope)
json
{
"event_id": "01HF7H9J9Q3E7DYT5Y6K3ZFD6M",
"type": "payout.processed",
"version": "2025-01-01",
"ts": "2025-11-03T12:34:56.789Z",
"attempt": 1,
"producer": "payments",
"tenant": "acme",
"data": {
"payout_id": "p_123",
"status": "processed",
"amount_minor": 10000,
"currency": "EUR"
}
}
Erforderliche Felder: 'event _ id', 'type', 'version', 'ts', 'attempt'.
Evolutionsregeln: Felder hinzufügen; Löschen/Ändern von Typen - nur mit neuer 'Version'.
3) Sicherheit: Signaturen und Bindung
3. 1 HMAC-Signatur (standardmäßig empfohlen)
Überschriften:
X-Signature: v1=base64(hmac_sha256(<secret>, <canonical>))
X-Timestamp: 2025-11-03T12:34:56Z
X-Event-Id: 01HF7...
Kanonische Zeichenfolge:
<timestamp>\n<method>\n<path>\n<sha256(body)>
Prüfung beim Empfänger:
- abs(now − `X-Timestamp`) ≤ 300s
- 'X-Event-Id' nicht früher verarbeitet (dedup)
- „X-Signatur“ ist identisch (mit zeitsicherem Vergleich)
3. 2 Dop. Maßnahmen
mTLS für hochsensible Webhooks.
IP/ASN allow-list.
DPoP (optional) für sender-constrained, wenn das Webhook Callbacks auslöst.
4) Idempotenz und Deduplizierung
4. 1 Idempotenz des Ereignisses
Ein Ereignis mit der gleichen 'event _ id' darf den Status nicht erneut ändern. Empfänger:- speichert „event _ id“ im idempotenten Cache (KV/Redis/DB) auf der TTL ≥ 24-72 Stunden;
- speichert das Ergebnis der Verarbeitung (Erfolg/Fehler, Artefakte) für eine erneute Rückgabe.
4. 2 Teamidempotenz (Callbacks)
Wenn der Webhook den Kunden zwingt, die API zu ziehen (z. B. „Bestätigen Sie die Auszahlung“), verwenden Sie den 'Idempotency-Key' auf dem REST-Anruf, speichern Sie das Ergebnis auf der Service-Seite (exactly-once outcome).
KV-Modell (Minimum):
key: idempotency:event:01HF7...
val: { status: "ok", processed_at: "...", handler_version: "..." }
TTL: 3d
5) Retrays und Backoff
Empfohlener Graph (exponentiell mit Jitter):- '5s, 15s, 30s, 1m, 2m, 5m, 10m, 30m, 1h, 3h, 6h, 12h, 24h' (weiter täglich bis N Tage)
- 2xx - Erfolg, Stop-Retrays.
- „400/ 401/403/404/422“ - kein Retraim, wenn Signatur/Format ok (Client-Fehler).
- '429' - Retraim durch 'Retry-After' oder Backoff.
- 5xx/Netzwerk - retraim.
Absenderüberschriften: „User-Agent“, „X-Webhook-Producer“, „X-Attempt“.
6) Verarbeitung auf Empfängerseite
Pseudopipeline:pseudo verify_signature()
if abs(now - X-Timestamp) > 300s: return 401
if seen(event_id):
return 200 // идемпотентный ответ
begin transaction if seen(event_id): commit; return 200 handle(data) // доменная логика mark_seen(event_id) // запись в KV/DB commit return 200
Transaktionalität: Die Markierung „gesehen“ sollte atomar mit der Wirkung der Operation (oder nach der Fixierung des Ergebnisses) gesetzt werden, um eine doppelte Verarbeitung bei einem Fehler zu vermeiden.
7) Auftragsgarantien und Schnappschüsse
Die Reihenfolge ist nicht garantiert. Verwenden Sie' ts' und Domain 'seq '/' version' in 'data', um die Relevanz abzustimmen.
Für lange Verzögerungen/Verluste - Fügen Sie/replay beim Absender und/resync beim Empfänger hinzu (erhalten Sie Snapshot und Deltas durch das Zeitfenster/ID).
8) Status, Replay und DLQ
8. 1 Endpunkte des Absenders
'POST/webhooks/replay' - durch die' event _ id 'Liste oder durch das Zeitfenster.
'GET/webhooks/events/: id' - Zeigt das Quellpaket und den Verlauf der Versuche an.
DLQ: „tote“ Ereignisse (das Limit der Retrays ist erschöpft) → separater Speicher, Alerts.
8. 2 Endpunkte des Empfängers
`GET /webhooks/status/:event_id` — `seen=true/false`, `processed_at`, `handler_version`.
'POST/webhooks/ack' - (optional) Bestätigung der manuellen Bearbeitung durch DLQ.
9) Fehlerverträge (Antwort des Empfängers)
http
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json
Retry-After: 120
X-Trace-Id: 4e3f...
{
"error": "invalid_state",
"error_description": "payout not found",
"trace_id": "4e3f..."
}
Empfehlungen: Geben Sie immer einen klaren Code und, wenn möglich, „Retry-After“ zurück. Geben Sie keine detaillierten Sicherheitsdetails zurück.
10) Überwachung und SLO
Metriken (Absender):- delivery p50/p95, success rate, retrays/event, drop-rate DLQ, share 2xx/4xx/5xx, delay window to 2xx.
- verify fail rate (Signatur/Zeit), dup-rate, latency handler p95, 5xx.
- Lieferung: ≥ 99. 9% der Ereignisse erhalten 2xx <3 c p95 (nach dem ersten erfolgreichen Versuch).
- Kryptoprüfung: Validierung der Signatur ≤ 2-5 ms p95.
- Dedup: 0 Wiederholungseffekte (exactly-once outcome auf Domain-Ebene).
11) Datensicherheit und Datenschutz
Übertragen Sie PAN/PII nicht im Webhook-Körper; Verwenden Sie IDs und nachfolgendes Pull für Teile über eine autorisierte API.
Maskieren Sie empfindliche Felder in den Protokollen; Speichern Sie Ereigniskörper nur auf ein Minimum, mit TTL.
Verschlüsseln Sie DLQ- und Replikatspeicher.
12) Versionierung und Kompatibilität
Version in 'version' (Umschlag) und unterwegs: '/webhooks/v1/payments'.
Neue Felder - optional; Entfernung - erst nach dem Zeitraum „Sonnenuntergang“.
Dokumentieren Sie die Änderungen im maschinenlesbaren Changelog (für Autoprüfungen).
13) Testfälle (UAT Checkliste)
- Re-Lieferung der gleichen 'event _ id' → einen Effekt und '200' auf Duplikate.
- Signatur: sicherer Schlüssel, falscher Schlüssel, alter Schlüssel (Rotation), 'X-Timestamp' außerhalb des Fensters.
- Backoff: Der Empfänger gibt '429' mit 'Retry-After' → korrekte Pause.
- Reihenfolge: Ereignisse'... processed 'kommt vor'... created '→ korrekte Verarbeitung/Erwartung.
- DB-Fehler beim Empfänger zwischen Effekt und 'mark _ seen' → Atomarität/Wiederholung.
- DLQ und manuelle Replay → erfolgreiche Lieferung.
- Ein massiver „Sturm“ (der Anbieter schickt Pakete) → ohne Verlust, die Grenzen ersticken das Kritische nicht.
14) Mini-Schnipsel
Signatur des Absenders (Pseudo):pseudo body = json(event)
canonical = ts + "\n" + "POST" + "\n" + path + "\n" + sha256(body)
sig = base64(hmac_sha256(secret, canonical))
headers = {"X-Timestamp": ts, "X-Event-Id": event.event_id, "X-Signature": "v1="+sig}
POST(url, body, headers)
Verifizierung und Dedupe des Empfängers (Pseudo):
pseudo assert abs(now - X-Timestamp) <= 300 assert timingSafeEqual(hmac(secret, canonical), sig)
if kv.exists("idemp:"+event_id): return 200
begin tx if kv.exists("idemp:"+event_id): commit; return 200 handle(event.data) // доменная логика kv.set("idemp:"+event_id, "ok", ttl=259200)
commit return 200
15) Häufige Fehler
Kein Deduplex → wiederholte Effekte (doppelte Refands/Peyouts).
Eine Signatur ohne Zeitstamp/Fenster → eine Replay-Schwachstelle.
Speicherung eines HMAC-Geheimnisses für alle Partner.
Die Antworten '200' vor der Aufzeichnung des Ergebnisses → der Verlust von Crash-Ereignissen.
„Auswaschen“ von Sicherheitsdetails in Antworten/Protokolle.
Kein DLQ/Replay - Vorfälle sind unlösbar.
16) Einführung Spickzettel
Sicherheit: HMAC v1 + 'X-Timestamp' + 'X-Event-Id', Fenster ≤ 5 min; mTLS/IP allow-list nach Bedarf.
Конверт: `event_id`, `type`, `version`, `ts`, `attempt`, `data`.
Lieferung: an-least-once, backoff mit jitter, 'Retry-After', DLQ + replay API.
Idempotenz: KV-Cache 24-72 h, atomare Fixierung des Effekts + 'mark _ seen'.
Beobachtbarkeit: Liefermetriken, Signaturen, Duplikate; trace' trace _ id'.
Dokumentation: Version, Antwortcodes, Beispiele, UAT-Checkliste.
Zusammenfassung
Persistente Webhooks bauen auf drei Säulen auf: unterschriebener Umschlag, At-Least-Once-Lieferung und idempotente Verarbeitung. Formalisieren Sie den Vertrag, aktivieren Sie HMAC/mTLS und das Zeitfenster, implementieren Sie Retrays + DLQs und Replikate, speichern Sie idempotente Tags und erfassen Sie Effekte atomar. Dann bleiben die Ereignisse auch bei Netzausfällen, Lastspitzen und seltenen „Schicksalsduplikaten“ verlässlich.