Webhook-Unterschriften Payloads überprüfen und gefälschte Anfragen stoppen
Erfahren Sie, wie HMAC-SHA256-Webhook-Unterschriften funktionieren, wie eine konstante Zeitverifikation in Python, Node.js und PHP umgesetzt wird, und die häufigen Fehler, die zu stummen Fehlern in der Produktion führen.
Jeder kann eine POST-Anfrage an Ihren Webhook-Endpunkt senden. Ohne Unterschriftsverifikation kann Ihr Server nicht sicherstellen, ob die Anfrage tatsächlich von Stripe, GitHub oder Ihrer eigenen Infrastruktur stammt – oder von einem Angreifer, der eine gültige Ereignis-Verarbeitung nachspielt.
Die Verifikation von Webhook-Unterschriften löst dieses Problem. Sie ist die Methode, mit der Zahlungssysteme, Versionskontrollplattformen und E-Commerce-Systeme Ihnen ermöglichen, die Authentizität von eingehenden Daten vor der Verarbeitung zu beweisen. Dieser Artikel erklärt, wie HMAC-SHA256-Unterschriften funktionieren, das Schritt-für-Schritt-Verifikationsmuster und die feinen Fehler, die in der Produktion zu Fehlern führen.
Warum unauthentifizierte Webhooks eine Sicherheitsgefahr darstellen
Ein Webhook-Endpunkt ist einfach ein HTTP-Handler, der dem Internet offen ist. Ohne Verifikation wird jede Anfrage, die darauf trifft, verarbeitet. Zwei Angriffe sind besonders auffällig:
- Spoofing — ein Angreifer erstellt ein gefälschtes Payload, das wie ein gültiges Ereignis (z. B. eine Zahlung wurde erfolgreich abgeschlossen, eine Abonnement wurde erneuert) aussieht, und löst auf Ihrer Seite Aktionen aus, ohne dass eine echte Transaktion stattgefunden hat.
- — es sei denn, Sie überprüfen auch einen Zeitstempel oder einen Nonce — ein gültiger Antrag wird während der Übertragung abgefangen und später erneut eingereicht. Wenn Ihr Endpunkt idempotent ist, aber nicht geschützt ist, wird das Ereignis mehrmals ausgelöst.
Beide Angriffe werden durch eine gemeinsame Geheimschrift und einen kryptographischen Signaturenverfahren verhindert. Der Absender signiert das Payload; Sie verifizieren die Signatur, bevor Sie irgendetwas verarbeiten.
Wie HMAC-SHA256-Unterschriften funktionieren
HMAC (Hash-basierte Nachrichtenauthentifizierung) nimmt zwei Eingaben: eine Geheimschrift und einen Nachricht. Es führt sie durch eine Hash-Funktion – in der meisten Webhook-Implementierungen SHA-256 – und erzeugt eine feste Länge Signatur.
Die zentrale Eigenschaft: Die gleiche Geheimschrift und Nachricht erzeugen immer die gleiche Signatur, und eine Änderung eines einzigen Bytes in der Nachricht führt zu einer völlig anderen Ausgabe. Jeder, der die Geheimschrift nicht kennt, kann keine gültige Signatur erzeugen, selbst wenn er den Inhalt der Nachricht vollständig sieht.
In der Praxis sieht der Ablauf wie folgt aus:
- Sie registrieren Ihren Webhook bei einem Dienst (z. B. Stripe). Der Dienst gibt Ihnen eine Signiergeheimnis.
- Wenn der Dienst ein Ereignis sendet, berechnet er
HMAC-SHA256(secret, payload)und fügt das Ergebnis in einen Anfrageheader ein. - Ihr Server erhält die Anfrage, berechnet dieselbe HMAC mit der gespeicherten Geheimschrift und dem Rohinhalt der Anfrage und vergleicht dann die beiden Signatur.
- Wenn sie übereinstimmen, ist die Anfrage authentisch. Wenn nicht, wird sie abgelehnt.
Das Verifikationsmuster, Schritt für Schritt
Hier ist eine Python-Implementierung, die das Verhalten jeder großen Dienstleistung widerspiegelt:
import hmac
import hashlib
def verify_signature(payload: bytes, secret: str, received_sig: str) -> bool:
expected = hmac.new(
key=secret.encode("utf-8"),
msg=payload,
digestmod=hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, received_sig)
Zwei Dinge sind hier außerhalb des HMAC-Aufrufs wichtig. Erstens: payload Ist bytes, nicht eine decodierte Zeichenkette – Sie müssen die Rohanfragekörper genau wie von dem Netzwerk empfangen. Zweitens: Die Vergleich verwendet hmac.compare_digest anstatt ==. Dies ist absichtlich.
Warum eine konstante Zeit-Vergleich nicht optional ist
String-Vergleiche in den meisten Sprachen kürzen sich: Sie geben false sofort, sobald ein nicht übereinstimmender Zeichen gefunden wird. Ein Angreifer, der die Antwortzeit messen kann, kann dies ausnutzen – indem er Tausende von Anfragen mit variierenden Signaturen sendet und die Zeitunterschiede nutzt, um den korrekten Wert zeichenweise zu erraten. Dies ist ein Zeitangriff.
hmac.compare_digest in Python, hash_equals in PHP und crypto.timingSafeEqual in Node.js benötigen die gleiche Zeit unabhängig davon, wo die Zeichen sich unterscheiden. Verwenden Sie sie jedes Mal, wenn Sie eine Signatur vergleichen – ohne Ausnahmen.
Real-World-Header-Formate: Stripe, GitHub, Shopify
Jeder Dienst hat einen leicht unterschiedlichen Header-Namen und Signaturen-Format. Hier ist das zu parsende Format für die drei häufigsten Integrationen.
Stripe
Stripe sendet einen Stripe-Signature Header mit diesem Format:
Stripe-Signature: t=1492774577,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a05bd539e6d5b9d2a2d2fbe
Der t Feld ist die Unix-Zeitstempel, wann das Ereignis gesendet wurde. Der v1 Wert ist die HMAC-SHA256 von timestamp + "." + raw_body. Wenn Sie manuell implementieren:
- Analysieren Sie den Header, um
tundv1. - Berechnen Sie
HMAC-SHA256(secret, t + "." + raw_body). - Vergleichen Sie mit
v1mit konstanter Zeit-Gleichheit. - Lehnen Sie ab, wenn
tmehr als 300 Sekunden (5 Minuten) vom aktuellen Zeitpunkt abweicht.
GitHub
GitHub verwendet den X-Hub-Signature-256 Header:
X-Hub-Signature-256: sha256=6ffbb59b2300aae63f272406069a9788598b770f698a48021f99b32f8de06bb3
Entfernen Sie den sha256= Vorhang, dann vergleichen Sie den Rest mit Ihrem HMAC des Rohkörpers. GitHub embeddet keinen Zeitstempel in die signierte Inhalte, daher implementieren Sie eine Ereignis-ID-Deduplizierung separat, wenn eine Replay-Schutz für Ihre Anwendung wichtig ist.
Shopify
Shopify verwendet X-Shopify-Hmac-Sha256 mit dem Digest in Base64-gekodiert statt hex:
X-Shopify-Hmac-Sha256: b6LPiZidmXnJQf0Ff/p7MZQPIBPN9TqAqLMgAfn2YLQ=
Decodieren Sie sowohl Ihren berechneten HMAC als auch den Header-Wert aus Base64 zu Rohbytes, dann vergleichen Sie mit einer konstanten Zeit-Funktion. Ein direkter Vergleich der Base64-Zeichenketten kann feine Encoding-Normalisierung-Fehler einführen.
Der häufigste Fehler: Rohkörper gegen geparseden JSON
Hier passieren die meisten Fehlern bei der Verifikation von Webhook-Unterschriften in der Produktion. Wenn Ihr Framework den Anfragekörper automatisch in ein Dict oder Objekt parsen, sind die Rohbytes verschwunden. Der HMAC wurde gegen diese ursprünglichen Bytes berechnet – nicht gegen das, was Ihre JSON-Bibliothek erzeugt, wenn sie die geparseden Struktur erneut serialisiert.
Auch wenn die Daten semantisch identisch sind, {"amount": 100} und {"amount":100} (kein Leerzeichen nach dem Doppelpunkt) erzeugen unterschiedliche HMAC-Werte. Unterschiede in der Feldreihenfolge, Unicode-Normalisierung und Gleitkomma-Precision können alle fehlerhafte Verifikation ohne offensichtliche Fehler verursachen.
Die Lösung: Buffer den Rohanfragekörper vor jeglicher Parsierung und übergeben diese Bytes an Ihre Verifikationsfunktion. In Express.js registrieren Sie die Route mit express.raw() Middleware anstatt express.json():
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const rawBody = req.body; // Buffer — not a parsed object
const sig = req.headers['stripe-signature'];
// Verify rawBody against sig before JSON.parse()
});
In Django verwenden Sie request.body (Bytes). In Laravel verwenden Sie $request->getContent(). Das Muster ist konsistent: Zugriff auf die Rohbytes direkt aus der Anfrage, nie auf eine erneut serialisierte Darstellung.
Replay-Schutz mit Zeitstempeln
Eine gültige Signatur beweist nur, dass der Payload während der Übertragung nicht verändert wurde. Sie verhindert nicht, dass ein Angreifer ein gültiges Ereignis aufzeichnet und es Stunden später erneut einreicht – die Signatur wird weiterhin korrekt verifiziert, weil nichts geändert wurde.
Stripe löst dies durch die Einbeziehung eines Unix-Timestamps in den signierten Payload und empfiehlt einen 5-minütigen Toleranzbereich. Nach der Extraktion von t aus dem Stripe-Signature Header, lehnen Sie Anfragen ab, bei denen der Zeitstempel außerhalb dieses Zeitfensters liegt:
import time
def is_within_tolerance(timestamp: int, tolerance_seconds: int = 300) -> bool:
return abs(time.time() - timestamp) <= tolerance_seconds
# In your handler:
if not is_within_tolerance(stripe_timestamp):
return HttpResponse(status=403) # Reject stale request
Für Dienste wie GitHub und Shopify, die keinen Zeitstempel in der Signaturen-Struktur integrieren, implementieren Sie Ihren eigenen Replay-Schutz, indem Sie bereits verarbeitete Ereignis-IDs (in der Payload enthalten) speichern und jede ID, die Sie bereits verarbeitet haben, abweisen. Ein kurze Cache oder ein Redis-Set mit einer TTL, die dem Verarbeitungszeitfenster entspricht, funktioniert gut.
Unterschriften verifizieren, ohne Code zu schreiben
Beim Debuggen einer Webhook-Integration – oder beim Auditing eines bereits empfangenen Payloads – ermöglicht der Webhook-Signatur-Validator Ihnen, einen Payload, einen Geheimtext und einen empfangenen Signaturen zu kopieren, um die Verifikation sofort zu überprüfen, ohne eine lokale Umgebung aufzubauen.
Um HMAC-Unterschriften für Tests Ihrer Endpunkte mit künstlich erstellten Payloads zu generieren, verwenden Sie die HMAC-Generator. Es erzeugt hex- und Base64-Ausgaben für SHA-256, SHA-512 und mehrere andere Hash-Algorithmen – nützlich, um Testfälle zu erstellen, die die Formate der jeweiligen Dienste abdecken.
Schnellübersicht
- HMAC-SHA256 mit einem gemeinsamen Geheimtext ist das Standard-Unterschriftsschema bei Stripe, GitHub, Shopify und den meisten anderen Diensten.
- Verwenden Sie immer eine konstante Zeit-Vergleich (
hmac.compare_digest,hash_equals,crypto.timingSafeEqual) – nicht==. - Übergeben Sie die Rohanfragekörper-Bytes an Ihre HMAC-Funktion, nie eine erneut serialisierte JSON-Objekt.
- Prüfen Sie den Zeitstempel bei Diensten, die einen Zeitstempel enthalten; Stripes Toleranzfenster beträgt 5 Minuten.
- Deduplizieren Sie durch Ereignis-ID bei Diensten, die keinen zeitbasierten Replay-Schutz bieten.
- Der Headername und die Codierung (hex vs. Base64) unterscheiden sich je nach Dienst – prüfen Sie immer die Integrationsdokumentation.
Erweiterungen installieren
IO-Tools zu Ihrem Lieblingsbrowser hinzufügen für sofortigen Zugriff und schnellere Suche
恵 Die Anzeigetafel ist eingetroffen!
Anzeigetafel ist eine unterhaltsame Möglichkeit, Ihre Spiele zu verfolgen. Alle Daten werden in Ihrem Browser gespeichert. Weitere Funktionen folgen in Kürze!
Unverzichtbare Tools
Alle Neuheiten
AlleAktualisieren: Unser neuestes Werkzeug was added on Juni 22, 2026
