Webhooks erklärt: Signierte Payloads und Retries
Sie haben einen Webhook-Endpunkt erstellt, um Ereignisse von einem Drittanbieterdienst zu empfangen. Im Test funktioniert alles, aber in der Produktion stellen Sie doppelte Ereignisse, verpasste Benachrichtigungen und gelegentliche Sicherheitsbedenken hinsichtlich gefälschter Anfragen fest. Dies sind häufige Fallstricke bei der Implementierung von Webhooks ohne geeignete Schutzmaßnahmen.
Webhooks sind HTTP-Callbacks, mit denen Dienste Ihre Anwendung benachrichtigen, wenn etwas passiert. Im Gegensatz zum Polling bieten sie Echtzeit-Updates. Um sie zuverlässig zu nutzen, müssen Sie zwei kritische Aspekte berücksichtigen: Authentizität (Sicherstellung, dass die Anfrage vom erwarteten Absender stammt) und Zustellung (Umgang mit Fehlern und Wiederholungen). Dieser Artikel erklärt beide mit praktischen Codebeispielen.
Wie Webhooks funktionieren
Ein Webhook ist ein benutzerdefinierter HTTP-POST-Endpunkt. Wenn ein Ereignis eintritt, sendet der Quellendienst eine Anfrage an diese URL mit einer Payload, die das Ereignis beschreibt. Beispielsweise könnte ein Zahlungsabwickler einen Webhook senden, wenn eine Belastung erfolgreich ist.
Der Ablauf ist einfach:
- Sie registrieren eine Webhook-URL beim Dienst.
- Der Dienst sendet einen HTTP-POST an diese URL, wenn ein Ereignis ausgelöst wird.
- Ihr Endpunkt verarbeitet die Payload und gibt einen 2xx-Status zurück, um den Empfang zu bestätigen.
Ohne ordnungsgemäße Verifizierung kann jeder Anfragen an Ihren Endpunkt senden. Ohne Retry-Handling können vorübergehende Fehler zu verlorenen Ereignissen führen. Packen wir beides an.
Webhooks mit signierten Payloads absichern
Um sicherzustellen, dass ein Webhook vom erwarteten Absender stammt, signieren Dienste die Payload mit einem gemeinsamen Secret. Die Signatur ist typischerweise ein HMAC (Hash-based Message Authentication Code) des Request-Bodys, der in einem Header wie X-Signature gesendet wird.
So funktioniert HMAC-Signierung
Der Absender berechnet einen HMAC mit einem geheimen Schlüssel und dem rohen Request-Body und fügt dann die hex-kodierte Signatur in einen Header ein. Ihr Endpunkt berechnet den HMAC mit demselben Secret neu und vergleicht ihn mit der empfangenen Signatur. Wenn sie übereinstimmen, ist die Anfrage authentisch.
Hier ist ein minimales Beispiel in Node.js mit Express:
const crypto = require('crypto');
const express = require('express');
const app = express();
// Use raw body for signature verification
app.use(express.raw({ type: 'application/json' }));
app.post('/webhook', (req, res) => {
const signature = req.headers['x-signature'];
const secret = process.env.WEBHOOK_SECRET;
const payload = req.body;
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
return res.status(401).send('Invalid signature');
}
// Process the event
const event = JSON.parse(payload);
// ... handle event
res.status(200).send('OK');
});
Wichtige Punkte:
- Verwenden Sie zur Verifizierung immer den rohen Request-Body, nicht ein geparstes Objekt. Das Parsen kann die Payload verändern (z. B. Leerzeichen) und die Signatur brechen.
- Verwenden Sie einen zeitkonstanten Vergleich, um Timing-Angriffe zu verhindern.
- Speichern Sie Secrets sicher (Umgebungsvariablen, Secret-Manager) und rotieren Sie sie regelmäßig.
Zusätzliche Sicherheitsmaßnahmen
- Zeitstempelvalidierung: Fügen Sie einen Zeitstempel in die Signatur ein (z. B.
t=timestamp,v1=signature) und lehnen Sie Anfragen ab, die älter als ein paar Minuten sind, um Replay-Angriffe zu verhindern. - IP-Allowlisting: Wenn der Anbieter IP-Bereiche veröffentlicht, beschränken Sie Ihren Endpunkt auf diese IPs.
- HTTPS: Verwenden Sie immer HTTPS, um die Payload während der Übertragung zu schützen.
Zuverlässige Wiederholungen implementieren
Die Webhook-Zustellung ist nicht garantiert. Netzwerke fallen aus, Ihr Server könnte nicht erreichbar sein oder Sie geben einen Nicht-2xx-Status zurück. Um dies zu handhaben, wiederholen Absender typischerweise mit exponentiellem Backoff. Als Empfänger müssen Sie Ihren Endpunkt so gestalten, dass er idempotent ist und schnell antwortet.
Auf der Absenderseite (wenn Sie Webhooks senden)
Wenn Sie einen Dienst erstellen, der Webhooks sendet, implementieren Sie Wiederholungen mit Backoff. Eine gängige Strategie:
- Wiederholen Sie bei Netzwerkfehlern oder 5xx-Antworten.
- Wiederholen Sie nicht bei 4xx-Antworten (außer 429 Too Many Requests).
- Verwenden Sie exponentielles Backoff mit Jitter: warten Sie 1s, 2s, 4s, 8s usw. bis zu einem Maximum.
- Legen Sie eine maximale Anzahl von Wiederholungen fest (z. B. 5) und ziehen Sie eine Dead-Letter-Queue für Fehler in Betracht.
Beispiel für Retry-Logik in Python:
import time
import requests
from requests.exceptions import RequestException
def send_webhook(url, payload, max_retries=5):
for attempt in range(max_retries):
try:
response = requests.post(url, json=payload, timeout=5)
if 200 <= response.status_code < 300:
return True
elif 400 <= response.status_code < 500 and response.status_code != 429:
# Client error, don't retry
return False
except RequestException:
pass
# Exponential backoff with jitter
delay = (2 ** attempt) + random.uniform(0, 1)
time.sleep(delay)
return False
Auf der Empfängerseite
Ihr Endpunkt sollte:
- Schnell bestätigen: Geben Sie innerhalb weniger Sekunden einen 2xx-Status zurück. Wenn die Verarbeitung länger dauert, stellen Sie das Ereignis in eine Warteschlange und verarbeiten Sie es asynchron.
- Idempotent sein: Verwenden Sie eine Ereignis-ID aus der Payload zur Deduplizierung. Speichern Sie verarbeitete IDs und ignorieren Sie Wiederholungen.
- Nebenläufigkeit handhaben: Verwenden Sie Datenbanktransaktionen oder Sperren, um Race Conditions bei der gleichzeitigen Verarbeitung desselben Ereignisses zu vermeiden.
Idempotenz-Beispiel mit einer eindeutigen Datenbankeinschränkung:
CREATE TABLE processed_events (
event_id VARCHAR(255) PRIMARY KEY,
processed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
Versuchen Sie vor der Verarbeitung, die Ereignis-ID einzufügen. Wenn sie den Primärschlüssel verletzt, überspringen Sie die Verarbeitung.
Webhooks lokal testen
Während der Entwicklung benötigen Sie eine öffentliche URL, damit der Dienst Ihren lokalen Server erreichen kann. Tools wie ngrok oder localtunnel machen Ihren localhost verfügbar. Verwenden Sie immer Test-Secrets und verifizieren Sie Signaturen in der Entwicklung, um Probleme frühzeitig zu erkennen.
Vergleich: Polling vs. Webhooks
| Aspekt | Polling | Webhooks |
|---|---|---|
| Latenz | Abhängig vom Intervall | Nahezu Echtzeit |
| Serverlast | Hoch (häufige Anfragen) | Niedrig (nur bei Ereignissen) |
| Komplexität | Einfach zu implementieren | Erfordert Signaturverifizierung und Retry-Handling |
| Zuverlässigkeit | Sie kontrollieren Wiederholungen | Abhängig von der Retry-Richtlinie des Absenders |
FAQ
Was ist eine signierte Webhook-Payload?
Eine signierte Payload enthält eine kryptografische Signatur (normalerweise HMAC), die mit einem gemeinsamen Secret generiert wird. Sie ermöglicht dem Empfänger zu überprüfen, dass die Anfrage vom erwarteten Absender stammt und nicht manipuliert wurde.
Wie gehe ich mit doppelten Webhook-Zustellungen um?
Machen Sie Ihren Endpunkt idempotent, indem Sie eine eindeutige Ereignis-ID aus der Payload verwenden. Speichern Sie verarbeitete IDs und ignorieren Sie Duplikate. Dadurch wird sichergestellt, dass Wiederholungen oder Netzwerkprobleme keine doppelte Verarbeitung verursachen.
Sollte ich fehlgeschlagene Webhook-Zustellungen wiederholen?
Ja, aber nur bei vorübergehenden Fehlern (Netzwerkprobleme, 5xx). Verwenden Sie exponentielles Backoff mit Jitter und legen Sie ein maximales Wiederholungslimit fest. Vermeiden Sie Wiederholungen bei 4xx-Fehlern (außer 429), da diese auf ein Problem mit der Anfrage selbst hinweisen.
Beim Debuggen von Webhook-Payloads müssen Sie oft JSON-Daten inspizieren. Der JSON Formatter kann Ihnen helfen, JSON-Payloads schnell übersichtlich zu formatieren und zu validieren.