Alle Artikel
webhook debuggingHTTP errorsauthenticationtroubleshooting

Warum dein Webhook 401 oder 403 zurückgibt (und wie du es behebst)

Ein Webhook, der 401 oder 403 zurückgibt, fällt in eine kleine Zahl von Schubladen. Meist ist es nicht einmal ein Signaturproblem — es ist ein Middleware- oder Framework-Default, der den Request ablehnt, bevor dein Code läuft. Das ist die Diagnose-Checkliste, die wir nutzen, in der Reihenfolge, in der wir sie nutzen.

Zuerst: 401 von 403 trennen

Sie bedeuten Unterschiedliches und die Lösung ist anders.

401 Unauthorized: Der Server hat den Request bekommen, die Credentials (oder die Signatur) angeschaut und nicht akzeptiert. Dein Handler ist wahrscheinlich gelaufen, hat eine erwartete Signatur berechnet und abgelehnt.

403 Forbidden: Der Server hat den Request bekommen und aus einem anderen Grund nicht verarbeitet. Oft hat der Request nie deinen Handler erreicht — ein Middleware- oder Framework-Default hat die 403 gesendet.

Öffne das Request-Capture deines Tunnels und sieh dir die Antwort an. Wenn du den Antwort-Body deines Handlers siehst („invalid signature"), ist es eine 401 aus deinem Code. Ist die Antwort generisch und deine Handler-Logs zeigen nichts, hat das Framework abgelehnt, bevor dein Handler lief.

Die fünf wahrscheinlichsten Ursachen

1. CSRF-Middleware (Django, Rails, Laravel)

Der CSRF-Standardschutz lehnt POSTs ohne Session-Token ab. Webhook-Provider senden keins. Symptome: 403, generischer Antwort-Body, keine Handler-Logs.

Lösung: Schließe die Webhook-Route vom CSRF-Schutz aus. In Django @csrf_exempt. In Rails skip_before_action :verify_authenticity_token, only: [:webhook]. In Laravel füge den Pfad zu VerifyCsrfToken::$except hinzu. Der Django-Webhook-Leitfaden zeigt die Django-Variante von Anfang bis Ende.

2. Auth-Middleware zu breit angewendet

Du hast Auth-Middleware global hinzugefügt (JWT, Session-Check, API-Key-Pflicht) und die Webhook-Route hat sie geerbt. Der Provider sendet deinen Auth-Header nicht, also schickt die Middleware eine 401, bevor dein Handler läuft.

Lösung: Schließe den Webhook-Pfad von deiner Auth-Middleware aus. Webhook-Authentifizierung ist die Signatur, nicht das Schema, das den Rest deiner API schützt.

3. Signaturprüfung schlägt fehl

Dein Handler ist gelaufen, hat eine erwartete Signatur berechnet und nicht gematcht. Fünf Unterursachen, grob nach absteigender Häufigkeit:

  • Body von Middleware geparst, bevor die Verifizierung lief (der rohe Body ist weg).
  • Falsche Kodierung (hex vs. base64). Siehe den Leitfaden zur Signaturprüfung.
  • Falsches Secret (Test vs. Live, Dashboard vs. CLI, Env-Datei vs. Runtime).
  • Timestamp zu alt (die Signatur ist gültig, aber veraltet — wahrscheinlich testest du mit einem alten, wiederholten Payload).
  • Nachlaufende Leerzeichen im Secret, das aus einer Env-Datei geladen wurde.

4. Falsche Tunnel-URL registriert

Du hast deinen Tunnel neu gestartet und die URL hat rotiert, aber das Provider-Dashboard hat noch die alte. Das Symptom sieht aus wie eine 401, weil der Request dich nie erreicht hat — tatsächlich erreicht aber ein anderer Request einen anderen Server (oft die vorherige Tunnel-Session, die jetzt ablehnt oder 401 zurückgibt).

Lösung: Stelle sicher, dass die URL im Provider-Dashboard mit deiner aktuellen Tunnel-Session übereinstimmt. Brauchst du eine stabile URL, sieh dir benannte Tunnel oder eine reservierte Subdomain an.

5. CORS-, Content-Type- oder Methoden-Einschränkungen

Bei Webhooks seltener, aber möglich. Wenn deine Route nur application/json akzeptiert und der Provider application/x-www-form-urlencoded sendet (Twilio etwa), liefern manche Frameworks 415 — ein falsch konfiguriertes aber vielleicht 403. Oder deine Route ist für GET registriert und der Provider macht POST.

Der 90-Sekunden-Diagnose-Flow

Das ist die Reihenfolge, die wir durchgehen:

  1. Lies den Antwort-Body. Stehen die Worte deines Handlers drin, hat der Request den Handler erreicht. Weiter zum Signatur-Debugging. Ist es eine generische Framework-Fehlerseite, hat der Request den Handler nicht erreicht. Weiter zum Middleware-Debugging.
  2. Prüfe die Handler-Logs. Feuern irgendwelche Log-Statements aus deinem Handler? Bestätigt, ob der Request dich erreichte.
  3. Prüfe die registrierte URL. Öffne das Provider-Dashboard. Bestätige, dass die URL zu deinem aktuellen Tunnel passt. Bestätige, dass sie auf den richtigen Pfad zeigt.
  4. Vergleiche die eingehenden Header. Das Tunnel-Capture zeigt dir die genauen Header, die der Provider gesendet hat. Vergleiche mit dem, was dein Code liest. Header sind case-insensitiv, aber der Zugriff unterscheidet sich je nach Framework — request.headers.get('Stripe-Signature') in manchen, request.META['HTTP_STRIPE_SIGNATURE'] in anderen.
  5. Verifiziere, dass der Body zur Signaturzeit roh ist. Gib die Länge des Bodys direkt vor der Signaturberechnung aus. Ist sie null oder überraschend klein, hat eine Middleware ihn gefressen.
  6. Prüfe das Secret doppelt. Vergleiche die Env-Variable in deiner Runtime mit dem Dashboard. console.log(process.env.WEBHOOK_SECRET.length) — passt die Länge zu dem, was das Dashboard zeigt?

Unserer Erfahrung nach erwischen die Schritte 1 und 5 80 % der Fälle in den ersten zwei Minuten.

Erfasse den fehlschlagenden Request

Das mit Abstand nützlichste Tool hier ist ein Tunnel mit Request-Capture und Replay. Du musst nicht warten, bis der Provider erneut versucht — du spielst den erfassten Request gegen deinen lokalen Handler ab, während du debuggst. Jeder Versuch ist sofort da.

Nutzt du einen Tunnel, der keine Requests erfasst, debuggst du mit einer Hand auf dem Rücken. Auf einen umzusteigen, der es kann (oder tcpdump zu fahren oder nginx vor deinen Dev-Server zu setzen), zahlt sich beim ersten Mal aus, wenn du eine Stunde sparst.

Wie 401 bei jedem Provider aussieht

  • Stripe: 401 aus deinem Code heißt, die Signaturprüfung ist fehlgeschlagen. Das Stripe-Dashboard zeigt die Delivery als fehlgeschlagen und enthält deinen Antwort-Body.
  • GitHub: Gibt dein Handler 401 zurück, markiert GitHub die Delivery als fehlgeschlagen und wiederholt. Die Seite mit den letzten Deliveries zeigt die Antwort.
  • Shopify: 401 aus einem Webhook-Handler ist für die Sicherheit in Ordnung, aber Shopify wiederholt. Nach 19 aufeinanderfolgenden Fehlern über 48 Stunden deaktiviert Shopify das Abo.

Für den breiteren Webhook-Debugging-Kontext siehe wie man Webhooks lokal debuggt. Tritt der PortPreview-Warteliste bei für einen Tunnel mit eingebautem Capture.

Häufig gestellte Fragen

Was bedeutet eine 401 bei einem Webhook?
Dein Handler hat den Request bekommen, die Credentials oder die Signatur bewertet und abgelehnt. Am häufigsten ist das ein Fehlschlag der Signaturprüfung, verursacht durch Parsen des Bodys vor der Verifizierung, falsche Kodierung (hex vs. base64) oder das falsche Secret.
Warum sehen Webhook-Provider eine 403 von meiner App?
Eine 403 bedeutet meist, dass der Request nie deinen Handler erreicht hat. Häufigste Ursache ist CSRF-Middleware (in Django, Rails, Laravel) oder zu breit angewendete Auth-Middleware. Schließe die Webhook-Route vom CSRF und von der allgemeinen API-Auth aus, damit der Request deinen Verifizierungscode erreicht.
Wie debugge ich Webhook-Auth-Fehler, ohne Events neu auszulösen?
Nutze einen Tunnel mit Request-Capture und Replay. Erfasse eine Delivery vom Provider, dann spiele sie gegen deinen lokalen Handler ab, so oft du beim Debuggen brauchst. Jeder Replay ist sofort da und hängt nicht vom Provider ab.