Signaturverifizierung
Verifizieren Sie Webhook-Signaturen, um die Authentizität eingehender Webhook-Payloads sicherzustellen
Jede Webhook-Zustellung enthält eine kryptografische Signatur, mit der Sie verifizieren können, dass die Anfrage von MailLand stammt und während der Übertragung nicht manipuliert wurde. Verifizieren Sie Signaturen in der Produktion grundsätzlich.
Funktionsweise der Signierung
MailLand signiert jede Webhook-Payload mit HMAC-SHA256 unter Verwendung des geheimen Schlüssels Ihres Webhooks. Die Signatur wird im Header X-MLND-Signature übermittelt.
Signaturformat:
X-MLND-Signature: t=1708171200,v1=b9f747ea899eafe911777060ca8f3c90828d37f1e16ee6e9ee98dacf8b316b67| Komponente | Beschreibung |
|---|---|
t | Unix-Zeitstempel (Sekunden) zum Zeitpunkt der Signaturerstellung |
v1 | Hexadezimale HMAC-SHA256-Signatur (Kleinbuchstaben) |
Signierte Payload:
{timestamp}.{raw_json_body}Die Signatur wird über den Zeitstempel berechnet, der mit einem Punkt und dem rohen JSON-Anfrage-Body verkettet wird.
Durchgerechnetes Beispiel
Prüfen Sie Ihre Implementierung vor dem Produktivstart gegen genau diese Werte. Alles unten ist echt: Die Signatur ist die, die MailLand für dieses Secret, diesen Zeitstempel und diesen Body erzeugt.
| Eingabe | Wert |
|---|---|
| Secret | whsec_docs_example_secret_do_not_use |
Zeitstempel (t) | 1708171200 |
| Roher Body | {"event":"message.delivered","event_id":"evt_a1b2c3d4e5f6"} |
Signierte Zeichenkette: Zeitstempel, ein Punkt, dann der Body unverändert:
1708171200.{"event":"message.delivered","event_id":"evt_a1b2c3d4e5f6"}HMAC-SHA256 dieser Zeichenkette mit dem Secret als Schlüssel, Kleinbuchstaben-Hex:
b9f747ea899eafe911777060ca8f3c90828d37f1e16ee6e9ee98dacf8b316b67Der Header dieser Zustellung wäre also:
X-MLND-Signature: t=1708171200,v1=b9f747ea899eafe911777060ca8f3c90828d37f1e16ee6e9ee98dacf8b316b67Reproduzieren Sie es in einer Zeile:
printf '%s' '1708171200.{"event":"message.delivered","event_id":"evt_a1b2c3d4e5f6"}' \
| openssl dgst -sha256 -hmac 'whsec_docs_example_secret_do_not_use'Der Zeitstempel dieses Beispiels liegt in der Vergangenheit, ein echter Verifier würde ihn also wegen der Toleranz ablehnen. Testen Sie zuerst das HMAC mit deaktivierter Zeitprüfung und schalten Sie sie danach wieder ein.
Gesendete Header
Jede Webhook-Zustellung enthält diese Header:
| Header | Beispiel | Beschreibung |
|---|---|---|
X-MLND-Signature | t=1708171200,v1=abc123... | Zeitstempel und HMAC-SHA256-Signatur |
X-MLND-Event-Id | evt_a1b2c3d4e5f6... | Eindeutige Ereignis-ID zur Deduplizierung |
X-MLND-Event | message.delivered | Ereignistyp |
X-MLND-Event-Version | 1 | Payload-Schema-Version |
X-MLND-Webhook-Id | wh_abc123def456 | Öffentliche ID Ihres Webhooks |
X-MLND-Attempt | 1 | Zustellversuchsnummer (beginnt bei 1) |
X-MLND-Delivery-Id | p8r2kx7m9wq4rt | ID dieses Versuchs, bei jeder Wiederholung neu; die publicId im Zustellverlauf. Nicht zur Deduplizierung: siehe Versionierung |
X-MLND-Delivery-Timestamp | 1708171200 | Sendezeit dieses Versuchs (Unix-Sekunden); immer gleich t in der Signatur |
Zwei weitere Header sind davon abhängig, welches Ereignis Sie abonniert haben:
| Header | Beispiel | Gesendet wenn |
|---|---|---|
X-MLND-Source | inbound | Nur bei inbound.received-Zustellungen vorhanden |
X-MLND-Inbound-Email-Id | inb_a1b2c3d4e5f6 | Nur bei inbound.received-Zustellungen vorhanden |
X-MLND-Message-Id wird derzeit bei message.*-Zustellungen nicht gesendet. Verwenden Sie stattdessen das Feld data.message_id in der Ereignis-Payload.
Verifizierungsschritte
Zeitstempel und Signatur extrahieren
Parsen Sie den Header X-MLND-Signature. Teilen Sie bei ,, um die Komponenten t=... und v1=... zu erhalten.
Die signierte Payload vorbereiten
Verketten Sie den Zeitstempel, einen Punkt (.) und den rohen Request-Body:
signed_payload = timestamp + "." + raw_bodyDie erwartete Signatur berechnen
Generieren Sie einen HMAC-SHA256-Hash, wobei Ihr Webhook-Secret als Schlüssel und die signierte Payload als Nachricht verwendet wird. Konvertieren Sie das Ergebnis in Kleinbuchstaben-Hexadezimal.
Signaturen vergleichen
Verwenden Sie einen Constant-Time-Vergleich, um zu prüfen, ob die berechnete Signatur mit dem v1-Wert aus dem Header übereinstimmt.
Zeitstempel validieren
Überprüfen Sie, ob der Zeitstempel innerhalb von 5 Minuten der aktuellen Zeit liegt, um Replay-Angriffe zu verhindern.
Codebeispiele
#!/usr/bin/env bash
# Compute and verify an HMAC-SHA256 webhook signature using openssl
SIGNATURE_HEADER="t=1708171200,v1=b9f747ea899eafe911777060ca8f3c90828d37f1e16ee6e9ee98dacf8b316b67"
RAW_BODY='{"event":"message.delivered","event_id":"evt_a1b2c3d4e5f6"}'
SECRET="whsec_docs_example_secret_do_not_use"
TOLERANCE=300
# Parse timestamp and signature from header
TIMESTAMP=$(echo "$SIGNATURE_HEADER" | grep -oP '(?<=t=)[^,]+')
RECEIVED_SIG=$(echo "$SIGNATURE_HEADER" | grep -oP '(?<=v1=).+')
# Check timestamp tolerance
NOW=$(date +%s)
DIFF=$(( NOW - TIMESTAMP ))
if [ "${DIFF#-}" -gt "$TOLERANCE" ]; then
echo "Error: Timestamp outside tolerance window" >&2
exit 1
fi
# Compute expected signature
SIGNED_PAYLOAD="${TIMESTAMP}.${RAW_BODY}"
EXPECTED=$(printf '%s' "$SIGNED_PAYLOAD" | openssl dgst -sha256 -hmac "$SECRET" | awk '{print $2}')
# Compare signatures
if [ "$EXPECTED" = "$RECEIVED_SIG" ]; then
echo "Signature valid"
else
echo "Signature invalid" >&2
exit 1
fiSchutz vor Replay-Angriffen
Der Zeitstempel im Signatur-Header verhindert Replay-Angriffe. Wenn ein Angreifer eine gültige Webhook-Zustellung abfängt und später erneut sendet, wird die Zeitstempel-Prüfung sie ablehnen.
Die Standardtoleranz von MailLand beträgt 5 Minuten (300 Sekunden). Sie können dies in Ihrem Verifizierungscode anpassen, aber halten Sie es kurz genug, um bedeutsame Replay-Angriffe zu verhindern.
Zusätzlich hat jedes Ereignis eine eindeutige event_id (im Header X-MLND-Event-Id). Speichern Sie verarbeitete Ereignis-IDs und lehnen Sie Duplikate für zusätzlichen Schutz ab.
MailLand dedupliziert das Auslösen auf seiner Seite: Ein doppelt erzeugtes Ereignis wird pro Webhook innerhalb von 24 Stunden nur einmal versandt. Das ist nicht dasselbe wie „höchstens einmal zugestellt“: eine fehlgeschlagene Zustellung wird wiederholt, und jeder Versuch trägt dieselbe event_id und denselben Body; nur X-MLND-Attempt und der Signatur-Zeitstempel ändern sich. Deduplizieren Sie über event_id und rechnen Sie mit wiederholten Zustellungen.
Jede Zustellung ist signiert
Es gibt keinen unsignierten Zustellpfad. Hat ein Webhook kein brauchbares Signing-Secret, lässt
MailLand die Zustellung fehlschlagen und protokolliert den Fehler, statt die Nutzdaten ohne
X-MLND-Signature-Header zu senden: ein fehlender Signatur-Header ist also nie etwas, das Ihr
Verifizierer tolerieren muss. Lehnen Sie jede Anfrage ab, die keinen trägt.
Secrets neu generieren
Wenn Sie vermuten, dass Ihr Webhook-Secret kompromittiert wurde:
- Rufen Sie
POST /v1/webhooks/{id}/regenerate-secretauf, um ein neues Secret zu erhalten - Aktualisieren Sie den Verifizierungscode Ihres Servers mit dem neuen Secret
- Das alte Secret wird sofort ungültig
Beim Neuerstellen von Secrets gibt es keine Übergangsfrist. Aktualisieren Sie Ihren Verifizierungscode vor der nächsten Webhook-Zustellung, um zu vermeiden, dass gültige Ereignisse abgelehnt werden.
Häufige Fehler
- Den Body als JSON parsen, bevor man verifiziert: die Signatur wird über die rohen Bytes berechnet, nicht über ein re-serialisiertes JSON-Objekt. Erst verifizieren, dann parsen.
- Stringvergleich statt Constant-Time-Vergleich verwenden: normaler Stringvergleich ist anfällig für Timing-Angriffe.
- Den Zeitstempel ignorieren: ohne Zeitstempel-Validierung können aufgefangene Payloads unbegrenzt wiedergegeben werden.
- Das Secret fest kodieren: verwenden Sie Umgebungsvariablen oder einen Secret Manager.
Nächste Schritte
- Retry-Logik: Wiederholungsverhalten und Fehlerbehandlung verstehen
- Ereignisse: vollständige Ereignistyprreferenz
- Webhooks-Übersicht: Webhooks erstellen und verwalten