Webhooks erklärt: Signierte Payloads und Retries
Sie haben gerade ein Payment-Gateway oder einen CI/CD-Service integriert und müssen nun Echtzeit-Events empfangen. Webhooks sind die Standardlösung, bringen aber zwei kritische Herausforderungen mit sich: die Verifizierung, dass die Anfrage tatsächlich vom Absender stammt, und die Sicherstellung, dass Events nicht verloren gehen, wenn Ihr Server vorübergehend nicht erreichbar ist. Dieser Artikel erklärt, wie Sie signierte Payloads und Retry-Mechanismen implementieren, um einen robusten Webhook-Empfänger zu erstellen.
Was sind Webhooks?
Ein Webhook ist ein HTTP-Callback: Statt dass Ihre Anwendung eine API nach Updates abfragt (Polling), sendet der Service bei jedem Event eine HTTP-POST-Anfrage an eine von Ihnen bereitgestellte URL. Dieses Push-Modell reduziert Latenz und Serverlast. Da die Anfrage jedoch von einem externen Service stammt, müssen Sie sie als nicht vertrauenswürdige Eingabe behandeln.
Warum Payloads signieren?
Ohne Verifizierung könnte jeder, der Ihren Webhook-Endpunkt entdeckt, gefälschte Events senden und damit möglicherweise unautorisierte Aktionen auslösen. Das Signieren von Payloads mit einem gemeinsamen Secret ermöglicht es Ihnen, die Identität des Absenders zu bestätigen und sicherzustellen, dass der Payload nicht manipuliert wurde. Die gängigste Methode ist HMAC (Hash-based Message Authentication Code) mit SHA-256.
So funktioniert HMAC-Signierung
Der Absender berechnet einen HMAC-Digest des rohen Request-Bodys mit einem Secret-Key und fügt ihn in einen Header ein (z. B. X-Signature). Ihr Server berechnet den HMAC mit demselben Secret neu und vergleicht ihn mit der empfangenen Signatur. Wenn sie übereinstimmen, ist die Anfrage authentisch.
// Node.js example: verifying a webhook signature
const crypto = require('crypto');
function verifySignature(payload, signature, secret) {
const hmac = crypto.createHmac('sha256', secret);
hmac.update(payload);
const digest = hmac.digest('hex');
// Use timingSafeEqual to prevent timing attacks
return crypto.timingSafeEqual(
Buffer.from(digest, 'hex'),
Buffer.from(signature, 'hex')
);
}
Wichtig: Verwenden Sie stets eine Vergleichsfunktion mit konstanter Laufzeit (wie timingSafeEqual), um das Ausspähen von Informationen über Timing-Unterschiede zu vermeiden. Berechnen Sie den HMAC außerdem über den rohen Body, nicht über das geparste JSON, da eine erneute Serialisierung die Byte-Sequenz verändern kann.
Retries implementieren
Selbst mit Signaturen können Netzwerkprobleme oder Serverfehler dazu führen, dass die Webhook-Zustellung fehlschlägt. Ein zuverlässiger Absender wiederholt fehlgeschlagene Zustellungen, typischerweise mit exponentiellem Backoff. Als Empfänger sollten Sie Ihren Endpunkt idempotent gestalten und geeignete Statuscodes zurückgeben.
Retry-Strategie aus Sicht des Absenders
- Retry bei 5xx- und Netzwerkfehlern: Diese deuten auf vorübergehende Probleme hin.
- Kein Retry bei 4xx: Client-Fehler (z. B. 400 Bad Request) sind in der Regel dauerhaft; ein erneuter Versuch hilft nicht.
- Exponentielles Backoff verwenden: Warten Sie zwischen den einzelnen Versuchen länger, um den Empfänger nicht zu überlasten.
- Maximale Anzahl an Retries festlegen: Nach einem Limit die Zustellung als fehlgeschlagen markieren und alarmieren.
Einen idempotenten Empfänger entwerfen
Da Retries zu doppelten Zustellungen führen können, muss Ihr Empfänger dasselbe Event mehrfach ohne negative Auswirkungen verarbeiten können. Fügen Sie eine eindeutige Event-ID in den Payload (oder Header) ein und speichern Sie verarbeitete IDs. Wenn eine Event-ID bereits verarbeitet wurde, geben Sie sofort 200 OK zurück, ohne das Event erneut zu verarbeiten.
// Pseudocode for idempotent webhook handling
function handleWebhook(event) {
if (processedEvents.has(event.id)) {
return { status: 200, body: 'Already processed' };
}
// Process event...
processedEvents.add(event.id);
return { status: 200, body: 'Success' };
}
Best Practices für Webhook-Sicherheit und -Zuverlässigkeit
- HTTPS verwenden: Stellen Sie Ihren Webhook-Endpunkt immer über TLS bereit, um Abhören zu verhindern.
- Timestamp validieren: Wenn die Signatur einen Timestamp enthält, lehnen Sie Anfragen ab, die älter als einige Minuten sind, um Replay-Angriffe zu verhindern.
- Schnell antworten: Bestätigen Sie den Empfang mit einem 2xx-Status, bevor Sie aufwändige Verarbeitung durchführen. Stellen Sie die Arbeit asynchron in eine Queue.
- Alles loggen: Führen Sie Logs über empfangene Webhooks inklusive Headers und Payloads (sensible Daten redigieren), um Probleme zu debuggen.
- Secrets regelmäßig rotieren: Unterstützen Sie mehrere aktive Secrets, um eine nahtlose Rotation zu ermöglichen.
Vergleich: Polling vs. Webhooks
| Aspekt | Polling | Webhooks |
|---|---|---|
| Latenz | Abhängig vom Intervall | Nahezu Echtzeit |
| Serverlast | Hoch (häufige Anfragen) | Gering (nur bei Events) |
| Komplexität | Einfach zu implementieren | Erfordert Signaturverifizierung und Retry-Handling |
| Zuverlässigkeit | Sie kontrollieren Retries | Abhängig von der Retry-Policy des Absenders |
FAQ
Wie verifiziere ich eine Webhook-Signatur?
Berechnen Sie einen HMAC des rohen Request-Bodys mit dem gemeinsamen Secret und vergleichen Sie ihn dann mit der Signatur im Header mithilfe einer Vergleichsfunktion mit konstanter Laufzeit.
Was sollte mein Webhook-Endpunkt zurückgeben?
Geben Sie einen 2xx-Statuscode (z. B. 200 OK) zurück, sobald Sie das Event empfangen haben. Wenn Sie 4xx oder 5xx zurückgeben, wiederholt der Absender die Zustellung möglicherweise oder markiert sie als fehlgeschlagen.
Kann ich Webhooks ohne Signaturen verwenden?
Das ist nicht empfehlenswert. Ohne Signaturen können Sie den Absender nicht verifizieren, was Ihren Endpunkt anfällig für gefälschte Anfragen macht. Verwenden Sie in der Produktion immer Signaturen.
Beim Debuggen von Webhook-Payloads ist JSON-Formatierung unerlässlich. Verwenden Sie unseren JSON-Formatter, um eingehende JSON-Daten schnell übersichtlich darzustellen und zu validieren.