Tous les articles
webhook debuggingHTTP errorsauthenticationtroubleshooting

Pourquoi votre webhook renvoie 401 ou 403 (et comment corriger)

Webhook qui renvoie 401 ou 403 : un petit nombre de causes. La plupart du temps ce n'est même pas un problème de signature — c'est un middleware ou un default framework qui rejette avant votre code. Voici la checklist de diagnostic qu'on utilise, dans l'ordre.

D'abord, distinguer 401 de 403

401 Unauthorized : le serveur a reçu la requête, regardé les credentials ou la signature, et refusé. Votre handler a probablement tourné.

403 Forbidden : le serveur a reçu et refusé pour une autre raison. Souvent la requête n'a jamais atteint votre handler.

Ouvrez la capture du tunnel et regardez la réponse. Si vous voyez le body de votre handler ("invalid signature"), c'est un 401 de votre code. Sinon, le framework a rejeté avant.

Les cinq causes les plus probables

1. Middleware CSRF (Django, Rails, Laravel)

La protection CSRF par défaut rejette les POSTs sans token de session. Les providers webhook n'en envoient pas. Symptômes : 403, body générique, pas de logs handler.

Solution : exclure la route webhook du CSRF. Django : @csrf_exempt. Rails : skip_before_action :verify_authenticity_token. Laravel : ajoutez à VerifyCsrfToken::$except. Détaillé dans le guide Django.

2. Middleware d'auth trop large

Middleware d'auth global (JWT, session, API key) appliqué à la route webhook. Le provider n'envoie pas votre header d'auth, le middleware renvoie 401 avant votre handler.

Solution : exclure le chemin webhook. L'auth webhook est la signature.

3. Vérification de signature qui échoue

Votre handler a tourné mais la signature ne correspond pas. Sous-causes par fréquence décroissante :

  • Corps parsé par un middleware avant la vérification.
  • Mauvais encodage (hex vs base64). Voir guide de signature.
  • Mauvais secret (test vs live, dashboard vs CLI, env vs runtime).
  • Timestamp trop vieux.
  • Whitespace en fin de secret chargé depuis un fichier env.

4. Mauvaise URL tunnel enregistrée

Tunnel redémarré, URL changée, mais le dashboard provider a encore l'ancienne. Ressemble à un 401 mais la requête atteint un autre serveur.

Solution : confirmez que l'URL du dashboard correspond à votre session tunnel actuelle. Pour URL stable, sous-domaine réservé ou named tunnels.

5. Restrictions CORS, content-type, méthode

Rare pour les webhooks. Route acceptant uniquement application/json + provider en form-encoded (Twilio) : 415 souvent, mais 403 possible si mal configuré.

Flow de diagnostic 90 secondes

  1. Lisez la réponse. Si elle contient les mots de votre handler, la requête est arrivée. Sinon non.
  2. Logs handler. Vos statements logguent-ils ?
  3. URL enregistrée. Dashboard provider = session tunnel actuelle ?
  4. En-têtes entrants. Capture tunnel = ce que votre code lit.
  5. Longueur du corps au moment de la signature. Si zéro, un middleware l'a mangé.
  6. Secret double-check. Length dans le runtime = length dashboard ?

Étapes 1 et 5 attrapent 80% des cas en deux minutes.

Capturez la requête en échec

L'outil le plus utile ici : tunnel avec capture et rejeu. Pas besoin d'attendre le retry — rejouez contre le handler local pendant que vous déboguez. Instant.

Le 401 vu de chaque provider

  • Stripe : 401 = échec de signature côté code. Dashboard affiche delivery failed avec le body de réponse.
  • GitHub : 401 → marqué failed et retenté. Page Recent deliveries affiche la réponse.
  • Shopify : 401 OK pour la sécurité mais Shopify retente. 19 échecs sur 48h → désactivation.

Voir guide debug webhook local. Rejoignez la waitlist PortPreview.

Questions fréquentes

Que signifie un 401 sur un webhook ?
Votre handler a reçu la requête, évalué les credentials ou la signature, et rejeté. Le plus souvent un échec de signature dû au parsing du corps avant vérification, à un mauvais encodage (hex vs base64), ou à un mauvais secret.
Pourquoi les providers voient un 403 depuis mon app ?
Un 403 signifie généralement que la requête n'a jamais atteint votre handler. Cause la plus fréquente : middleware CSRF (Django, Rails, Laravel) ou middleware d'auth trop large. Excluez la route webhook du CSRF et de l'auth générale.
Comment déboguer les échecs d'auth sans re-déclencher ?
Tunnel avec capture et rejeu. Capturez une livraison, rejouez contre le handler local autant que nécessaire. Instant, indépendant du provider.