Sécuriser un webhook n8n exposé publiquement : authentification, HMAC et bonnes pratiques
Publié le 17 juillet 2026 · 7 min de lecture
Un node Webhook n8n génère une URL et l'expose sur Internet — c'est tout l'intérêt du node. Mais « exposé sur Internet » veut dire exactement ce que ça dit : n'importe qui possédant cette URL peut envoyer une requête, déclencher le workflow, et consommer les ressources qui vont avec. Pour un webhook qui interroge un LLM à chaque appel, comme l'API du Pack Assistant RAG, cela peut vite se traduire par une facture OpenAI qui grimpe sans raison. Voici comment fermer ce trou, avec les outils natifs de n8n et sans complexité inutile.
Ce que n8n expose par défaut
Créez un node Webhook, réglez son champ Authentication sur None, et l'URL générée (https://votre-instance.app/webhook/un-identifiant) répond à quiconque l'appelle avec la bonne méthode HTTP. n8n rend cet identifiant difficile à deviner par force brute, mais un identifiant difficile à deviner n'est pas un secret au sens propre : il peut fuiter dans un log d'accès, un historique de navigateur partagé, une capture d'écran envoyée sur Slack, ou un simple copier-coller dans le mauvais canal.
Le vrai signal d'alerte : le champ Authentication du node Webhook est sur None par défaut à la création. Rien ne vous empêche de le laisser ainsi — n8n ne vous avertit pas. C'est un choix actif à faire, pas une case cochée automatiquement pour vous.
Les trois options natives du node Webhook
Avant d'écrire la moindre ligne de code, le node Webhook propose trois mécanismes d'authentification intégrés, accessibles dans son panneau de configuration.
Basic Auth
Un nom d'utilisateur et un mot de passe, envoyés dans l'en-tête Authorization. Simple à mettre en place, mais c'est l'option la plus faible des trois : à réserver aux cas où l'appelant ne sait littéralement rien faire d'autre (un vieux service tiers, un test manuel rapide).
Header Auth : le bon réglage par défaut
Vous définissez un nom d'en-tête (par exemple X-API-Key) et une valeur secrète, stockés dans un credential n8n dédié. n8n rejette automatiquement toute requête qui n'inclut pas exactement cet en-tête avec cette valeur — avant même que le workflow ne démarre. Pour la grande majorité des cas d'usage (votre propre frontend qui appelle le webhook, une intégration interne, un service tiers qui accepte un en-tête personnalisé), Header Auth est le bon choix par défaut : rapide à configurer, suffisamment robuste, et le secret ne transite jamais en clair dans l'URL. C'est notamment le réglage à adopter quand le formulaire d'une landing page poste ses leads vers votre webhook — et si la page elle-même reste à construire, les templates de landing pages de LanderKit livrent des formulaires optimisés qu'il suffit de pointer vers n8n.
Générez la valeur avec un outil dédié (openssl rand -hex 32, jamais un mot choisi à la main), et stockez-la comme n'importe quel secret : dans le gestionnaire de credentials n8n, jamais en dur dans un node ou committé dans un dépôt Git.
JWT Auth
n8n vérifie un jeton JWT signé, via une phrase secrète (HMAC) ou une clé publique (RSA/ECDSA). Pertinent quand l'appelant émet déjà des JWT — un fournisseur d'identité, votre propre backend applicatif qui gère des sessions utilisateur — et que vous préférez une vérification cryptographique à un secret statique qu'il faudrait faire tourner manuellement en cas de fuite. Pour une intégration simple entre deux services que vous contrôlez, c'est souvent plus de mécanique que nécessaire ; Header Auth suffit dans ce cas.
Aller plus loin : vérifier une signature HMAC
Ces trois options protègent un webhook que vous exposez pour que d'autres l'appellent. Le problème inverse se pose aussi : un webhook qui reçoit des notifications d'un service tiers — Stripe, GitHub, Typeform — n'a généralement pas d'en-tête d'authentification personnalisable côté émetteur. À la place, ces services signent le corps de la requête avec une clé secrète partagée, dans un en-tête du type Stripe-Signature ou X-Hub-Signature-256.
Vérifier cette signature demande un node Code placé juste après le Webhook, avant tout traitement métier :
const crypto = require('crypto');
const secret = $credentials.webhookSecret; // stocké en credential, jamais en dur
const signature = $request.headers['x-hub-signature-256'];
const payload = JSON.stringify($input.item.json.body);
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
const valid = crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
if (!valid) {
throw new Error('Signature invalide');
}
return $input.all();
Le détail qui compte : crypto.timingSafeEqual plutôt qu'une comparaison === classique. Une comparaison de chaînes standard s'arrête au premier caractère différent, ce qui laisse fuiter une information de timing exploitable pour deviner la signature octet par octet. timingSafeEqual compare en temps constant, indépendamment du contenu. C'est un détail de cryptographie appliquée facile à zapper — et c'est justement pour ça qu'il vaut la peine d'être noté ici.
Branchez ce node Code sur une sortie d'erreur explicite (voir notre guide gérer les erreurs dans n8n) plutôt que de laisser l'exception remonter telle quelle : une requête à signature invalide mérite un 403 propre, pas un 500 qui ressemble à un bug interne.
Limiter le débit : n8n ne le fait pas pour vous
Le node Webhook n'a pas de rate limiting intégré. Si votre webhook appelle un LLM à chaque requête — le cas de l'API question-réponse du Pack Assistant RAG — une authentification correcte empêche les inconnus d'appeler le endpoint, mais ne protège pas contre un appelant légitime mal configuré qui boucle, ou un credential qui fuite malgré tout.
Deux approches raisonnables, sans ajouter de service externe :
- Un compteur dans Supabase : à chaque appel, incrémentez une ligne
rate_limit(clé = IP ou identifiant d'appelant, valeur = nombre d'appels dans la fenêtre courante). Un node IF en tête de workflow coupe court au-delà du seuil, avec une réponse429. - Une limite au niveau du reverse proxy, si votre instance n8n est self-hosted derrière Nginx ou Traefik (
limit_reqcôté Nginx, par exemple) : c'est la couche la plus efficace, car elle bloque avant même que n8n ne démarre une exécution — donc avant toute consommation de ressources.
Si vous êtes sur n8n Cloud, seule la première option est disponible : vous ne contrôlez pas la couche réseau devant l'instance. C'est un des arbitrages à peser dans notre comparatif n8n self-hosted ou cloud.
Restreindre par IP quand c'est possible
Si le ou les appelants ont des IP fixes connues — un partenaire, un autre service interne, un webhook sortant d'un SaaS qui publie sa plage d'IP (c'est le cas de Stripe, GitHub et la plupart des grands fournisseurs) — un allowlist au niveau du reverse proxy ou du pare-feu du VPS ajoute une couche gratuite, indépendante de toute logique applicative. Elle ne remplace pas l'authentification (les IP peuvent être usurpées dans certains contextes réseau), mais elle réduit fortement la surface de tentatives aléatoires.
Les erreurs qui coûtent cher
- Authentication sur
None« pour tester », jamais changé : le cas le plus fréquent. Un webhook de test devient un webhook de production oublié, exposé sans rien. - Secret en dur dans le node plutôt qu'en credential : il finit dans l'export JSON du workflow, potentiellement partagé ou committé sans y penser.
- Comparaison de signature avec
===au lieu detimingSafeEqual: une faille de timing subtile, invisible en test, exploitable en théorie. - Aucune limite de débit sur un webhook qui appelle un LLM : un bug côté appelant ou une fuite de credential se traduit directement en euros sur la facture OpenAI ou Anthropic.
- Pas de journalisation des appels rejetés : sans trace des tentatives d'authentification échouées, impossible de détecter une attaque en cours. Le principe est le même que pour la piste d'audit RGPD : ce qui n'est pas journalisé n'existe pas au moment où on en a besoin.
Cas concret : sécuriser l'API du Pack Assistant RAG
Le workflow api-question-reponse-rag du Pack Assistant RAG (119 €) expose un webhook POST /ask qui interroge votre base documentaire et répond via un LLM — décrit en détail dans notre guide Supabase pgvector + n8n. Sorti de la boîte, ce webhook n'a pas d'authentification : c'est un choix délibéré pour que vous puissiez tester immédiatement au curl, sans credential à configurer avant même d'avoir vu le workflow fonctionner.
Avant toute mise en production, la marche à suivre est directe : ouvrez le node POST /ask (Webhook), réglez Authentication sur Header Auth, créez un credential avec un en-tête X-API-Key et une valeur générée aléatoirement, puis mettez à jour votre frontend ou votre script d'appel pour inclure cet en-tête. Cinq minutes de configuration, et votre assistant documentaire n'est plus consultable — ni exploitable côté facturation — par n'importe qui sur Internet.
En résumé
Un webhook n8n non protégé n'est pas une faille exotique : c'est le réglage de départ, et il reste ainsi jusqu'à ce que vous le changiez. Header Auth couvre l'immense majorité des cas en quelques minutes ; JWT Auth et la vérification HMAC prennent le relais pour des besoins plus spécifiques. Ajoutez une limite de débit dès qu'un LLM est dans la boucle, et journalisez ce que vous rejetez. Le reste — cloisonnement réseau, allowlist IP — vient renforcer une base déjà saine, pas la remplacer.
FAQ
Questions fréquentes
Le node Webhook de n8n est-il protégé par défaut ?
Non. Par défaut, l'option Authentication du node Webhook est réglée sur None : toute personne qui devine ou récupère l'URL peut appeler le webhook, sans aucune vérification. n8n masque l'URL derrière un identifiant aléatoire, mais un identifiant aléatoire n'est pas un secret — il peut fuiter dans un log, un historique de navigateur ou un copier-coller malheureux.
Header Auth ou JWT Auth : lequel choisir ?
Header Auth suffit pour la grande majorité des cas : intégration interne, appel depuis votre propre frontend, service tiers qui accepte un en-tête personnalisé. Passez à JWT Auth quand l'appelant émet déjà des jetons signés (un fournisseur d'identité, votre propre backend applicatif) et que vous voulez une vérification cryptographique plutôt qu'un secret partagé à faire tourner manuellement.
Faut-il vérifier une signature HMAC en plus de Header Auth ?
Cela dépend de qui appelle le webhook. Pour un service que vous contrôlez, Header Auth est suffisant. Pour un webhook entrant d'un tiers qui signe ses payloads (Stripe, GitHub, Typeform), la vérification HMAC est la méthode recommandée par ces fournisseurs eux-mêmes : elle garantit que le corps de la requête n'a pas été altéré en chemin, ce qu'un simple en-tête statique ne prouve pas.
Pack Conformité & Audit
149 €