Webhooks erklärt: Signierte Payloads und Retry-Strategien
Sie haben eine API entwickelt und müssen nun andere Dienste benachrichtigen, wenn Ereignisse eintreten. Polling ist ineffizient, also implementieren Sie Webhooks. Bald stehen Sie jedoch vor zwei häufigen Problemen: Wie stellen Sie sicher, dass der Payload nicht manipuliert wurde, und was tun Sie, wenn der empfangende Endpunkt nicht erreichbar ist? Dieser Artikel erklärt, wie Sie mit signierten Payloads und Retry-Strategien Ihre Webhooks sicher und zuverlässig machen.
Warum Webhooks Signaturen und Retries benötigen
Webhooks sind HTTP-Callbacks, die durch Ereignisse ausgelöst werden. Sie sind einfach zu implementieren, bringen aber Herausforderungen in Bezug auf Sicherheit und Zuverlässigkeit mit sich. Ohne Signaturen könnte ein Angreifer Anfragen an Ihren Endpunkt fälschen, was zu Datenkorruption oder unbefugten Aktionen führen kann. Ohne Retries führen vorübergehende Fehler (wie Netzwerkaussetzer oder temporäre Serverfehler) zu verpassten Ereignissen.
Signierte Payloads lösen das Authentizitätsproblem: Der Sender signiert den Payload mit einem Secret, und der Empfänger verifiziert ihn. Retries lösen das Zuverlässigkeitsproblem: Der Sender wiederholt fehlgeschlagene Zustellungen mit Backoff.
Wie signierte Payloads funktionieren
Ein signierter Payload enthält eine kryptografische Signatur, die aus dem Request-Body und einem gemeinsamen Secret generiert wird. Die gebräuchlichste Methode ist HMAC (Hash-based Message Authentication Code) mit SHA-256.
Generieren der Signatur
Der Sender berechnet den HMAC des rohen Request-Bodys unter Verwendung des Secret Keys. Die Signatur wird in einem Header gesendet, oft zusammen mit einem Zeitstempel, um Replay-Angriffe zu verhindern.
// Node.js Beispiel: HMAC-Signatur generieren
const crypto = require('crypto');
function signPayload(payload, secret) {
const timestamp = Math.floor(Date.now() / 1000);
const signedPayload = `${timestamp}.${JSON.stringify(payload)}`;
const signature = crypto
.createHmac('sha256', secret)
.update(signedPayload)
.digest('hex');
return { timestamp, signature };
}
Fügen Sie den Zeitstempel und die Signatur in Headern ein, z. B. X-Webhook-Timestamp und X-Webhook-Signature.
Verifizieren der Signatur
Der Empfänger muss den signierten Payload unter Verwendung des empfangenen Zeitstempels und des rohen Bodys rekonstruieren, dann den HMAC berechnen und sicher vergleichen.
// Node.js Beispiel: Signatur verifizieren
function verifySignature(rawBody, timestamp, receivedSignature, secret) {
const signedPayload = `${timestamp}.${rawBody}`;
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(signedPayload)
.digest('hex');
// Timing-sicheren Vergleich verwenden
return crypto.timingSafeEqual(
Buffer.from(expectedSignature, 'hex'),
Buffer.from(receivedSignature, 'hex')
);
}
Verwenden Sie immer einen timing-sicheren Vergleich, um Timing-Angriffe zu verhindern. Überprüfen Sie außerdem, ob der Zeitstempel aktuell ist (z. B. innerhalb von 5 Minuten), um Replay-Angriffe zu mildern.
Entwerfen einer Retry-Strategie
Retries sind essenziell für eine zuverlässige Webhook-Zustellung. Aber naive Retries können den Empfänger überlasten oder doppelte Verarbeitung verursachen. Eine gute Retry-Strategie umfasst:
- Exponentielles Backoff: Warten Sie länger zwischen jedem Retry (z. B. 1s, 2s, 4s, 8s...).
- Maximale Retries: Begrenzen Sie die Anzahl der Versuche (z. B. 5), um Endlosschleifen zu vermeiden.
- Dead-Letter-Queue: Nach maximalen Retries speichern Sie das Ereignis zur manuellen Überprüfung.
- Idempotenz: Fügen Sie eine eindeutige Ereignis-ID hinzu, damit der Empfänger Duplikate entfernen kann.
Hier ist eine einfache Retry-Schleife in Pseudocode:
function deliverWithRetry(url, payload, maxRetries = 5) {
let attempt = 0;
while (attempt < maxRetries) {
try {
const response = await fetch(url, { method: 'POST', body: payload });
if (response.ok) return;
} catch (error) {
// Fehler protokollieren
}
attempt++;
const delay = Math.pow(2, attempt) * 1000; // exponentielles Backoff
await new Promise(resolve => setTimeout(resolve, delay));
}
// In Dead-Letter-Queue verschieben
}
Erwägen Sie, dem Backoff Jitter hinzuzufügen, um Thundering-Herd-Probleme zu vermeiden.
Vergleich: Polling vs. Webhooks mit Retries
| Aspekt | Polling | Webhooks mit Retries |
|---|---|---|
| Latenz | Hoch (abhängig vom Intervall) | Niedrig (nahezu Echtzeit) |
| Serverlast | Konstant (auch wenn keine Ereignisse) | Ereignisgesteuert |
| Zuverlässigkeit | Hoch (Empfänger kontrolliert) | Abhängig von der Retry-Strategie |
| Sicherheit | Standard-API-Authentifizierung | Erfordert signierte Payloads |
Schritt für Schritt: Implementierung von Webhooks mit Signaturen und Retries
- Generieren Sie einen Secret Key für jeden Webhook-Endpunkt und teilen Sie ihn sicher.
- Beim Eintreten eines Ereignisses erstellen Sie den Payload (JSON) und berechnen Sie die Signatur mit HMAC-SHA256 unter Verwendung des Secrets und eines Zeitstempels.
- Senden Sie die POST-Anfrage an den Endpunkt mit Headern:
Content-Type: application/json,X-Webhook-TimestampundX-Webhook-Signature. - Auf der Empfängerseite verifizieren Sie die Signatur unter Verwendung des rohen Bodys und des Zeitstempels. Lehnen Sie ab, wenn ungültig oder der Zeitstempel zu alt ist.
- Wenn die Zustellung fehlschlägt (Nicht-2xx-Antwort oder Netzwerkfehler), wiederholen Sie mit exponentiellem Backoff bis zu einer maximalen Anzahl von Versuchen.
- Nach maximalen Retries protokollieren Sie den Fehler und alarmieren Sie optional; speichern Sie das Ereignis in einer Dead-Letter-Queue zur manuellen Wiederholung.
- Stellen Sie Idempotenz sicher auf der Empfängerseite, indem Sie eine Ereignis-ID verwenden, um Duplikate zu ignorieren.
Häufige Fallstricke und Best Practices
- Kein roher Body: Viele Frameworks parsen JSON und verändern den Body. Verwenden Sie den rohen Body für die Signaturverifizierung.
- Zeitstempel ignorieren: Ohne Zeitstempelvalidierung sind Replay-Angriffe möglich.
- Keine Idempotenz: Retries können doppelte Verarbeitung verursachen. Verwenden Sie Idempotenzschlüssel.
- Endlose Retries: Setzen Sie immer ein maximales Retry-Limit und eine Dead-Letter-Queue.
- Keine Protokollierung: Protokollieren Sie Zustellversuche und Fehler zum Debuggen.
FAQ
Was ist ein signierter Webhook-Payload?
Ein signierter Webhook-Payload enthält eine kryptografische Signatur (z. B. HMAC), die es dem Empfänger ermöglicht zu überprüfen, dass die Anfrage vom erwarteten Sender stammt und nicht manipuliert wurde.
Wie oft sollte ich einen fehlgeschlagenen Webhook wiederholen?
Eine gängige Praxis sind 3–5 Wiederholungen mit exponentiellem Backoff. Die genaue Anzahl hängt von Ihrem SLA und der Kritikalität des Ereignisses ab. Verwenden Sie immer eine Dead-Letter-Queue für Ereignisse, die das Retry-Limit überschreiten.
Kann ich Webhooks ohne Signaturen verwenden?
Es wird nicht empfohlen. Ohne Signaturen ist Ihr Endpunkt anfällig für gefälschte Anfragen. Verwenden Sie immer Signaturen (wie HMAC) und validieren Sie Zeitstempel, um Replay-Angriffe zu verhindern.
Beim Debuggen von Webhook-Zustellungen ist die Inspektion von Server-Logs entscheidend. Wenn Sie Nginx verwenden, kann der Nginx Log Analyzer Ihnen helfen, Zugriffslogs zu parsen und zu analysieren, um Webhook-Anfragen zu verfolgen und Fehler zu identifizieren.