Le webhook est la seule fonctionnalité où un client tape une adresse dans un formulaire et où vos serveurs vont l’appeler. Toutes les autres requêtes de votre backend vont là où vous l’avez décidé. Celle-ci va là où le client le dit, aussi souvent que votre produit génère des événements, et aussi longtemps que l’endpoint existe.
Cette semaine, Kookee a lancé les webhooks et, sur cette base, des intégrations avec Slack, Discord, Telegram, GitHub, Jira, Asana et Freshdesk. Vous publiez un article, et votre site statique se reconstruit ; un visiteur laisse un retour, et il arrive dans le canal Slack de votre équipe ou devient une tâche Jira.

Le formulaire avec le champ URL m’a pris un après-midi. Tout ce qui suit a pris le reste de la semaine, et certains de ces points, je ne les ai repérés qu’en relisant le plan, avant qu’une seule ligne de code existe.
Huit choses qu’un émetteur de webhooks ne doit pas rater
1. Vérifiez l’adresse à l’envoi, pas à l’enregistrement
La vérification évidente consiste à refuser http://localhost et 10.0.0.5 quand quelqu’un enregistre un endpoint. C’est aussi celle qui ne sert à rien. L’URL est vérifiée une fois, mais le nom qu’elle contient est résolu à nouveau à chaque livraison, et rien n’empêche hooks.example.com de pointer vers une adresse publique le lundi et vers 169.254.169.254, le service de métadonnées du cloud, le mardi.
La vraie vérification a donc lieu à l’envoi. Kookee résout l’hôte, refuse la livraison si l’une des adresses obtenues est privée, de loopback, link-local ou réservée (y compris une IPv4 écrite dans une IPv6), puis se connecte exactement à l’adresse vérifiée, pour qu’une seconde résolution ne puisse pas la remplacer. Les redirections ne sont pas suivies : un 302 vers une adresse interne n’est qu’une réponse.
Une plage est facile à oublier : les adresses publiques de vos propres serveurs. Derrière un CDN, l’IP de votre serveur d’origine est ce qu’un attaquant cherche le plus à atteindre, et un webhook l’appellerait sans broncher depuis l’intérieur de votre pare-feu. Celles de Kookee figurent sur la liste de blocage.
2. Signez comme tout le monde
J’aurais pu inventer un en-tête de signature. À la place, Kookee signe ses requêtes selon Standard Webhooks : un webhook-id, un webhook-timestamp et un HMAC dans webhook-signature. La personne qui écrit le récepteur dispose d’une bibliothèque dans son langage, d’une fenêtre anti-rejeu de cinq minutes sans rien faire, et d’un format de moins à apprendre.
Le secret de signature est chiffré au repos, et on peut l’afficher et le régénérer depuis le menu de l’endpoint. Le régénérer invalide l’ancien immédiatement, ce qu’on veut après une fuite.
3. Un récepteur lent ne doit ralentir que lui-même
Chaque livraison attend le serveur de quelqu’un d’autre. Si l’endpoint d’un client met neuf secondes à répondre et que vos workers traitent quelques livraisons à la fois, un seul client devient, sans que personne s’en aperçoive, le goulot d’étranglement de tout le monde.
J’impose à chaque requête un délai strict de 10 secondes, résolution DNS comprise, et les livraisons tournent 25 à la fois par processus. Cela ne suffit pas à lui seul, car un endpoint lent relancé toute la journée occupe quand même ces places. Chaque endpoint a donc droit à 2 minutes et demie d’attente par heure, et chaque compte à 7 minutes et demie pour l’ensemble de ses endpoints. Une livraison qui dépasse ce budget est sautée, et l’endpoint l’indique en toutes lettres. Un saut ne compte jamais comme un échec : le récepteur est lent, pas en panne, et le désactiver, ce serait se tromper de cible.
Les événements de contenu et les événements des visiteurs sont comptés séparément. Une avalanche de spam dans les retours ne doit jamais empêcher votre site de se reconstruire après une publication.
4. Relancez pendant des heures, puis arrêtez et prévenez quelqu’un
Les serveurs tombent pendant les déploiements, puis reviennent. Une livraison qui échoue est relancée dix fois sur environ huit heures et demie, avec des attentes de 1, 2, 4 et jusqu’à 256 minutes, et le même webhook-id à chaque fois pour que le récepteur puisse écarter les doublons.
Ce que je ne voulais pas, c’est un endpoint qui échoue en silence depuis mars. Une réponse 410 Gone désactive un endpoint immédiatement. Cinq événements d’affilée qui échouent à toutes les relances le désactivent aussi. Dans les deux cas, le propriétaire du projet reçoit une notification et un e-mail qui indiquent quel endpoint et pourquoi, et le réactiver tient en un clic.
5. Envoyez l’événement quand la page change, pas quand la ligne change
« Entrée mise à jour » paraît simple, jusqu’à ce qu’on compte tout ce qui le déclenche. L’enregistrement automatique, une coquille corrigée dans un brouillon, une traduction qu’on rattache : un hook naïf reconstruit un site statique quarante fois dans l’après-midi.
Les événements de contenu de Kookee suivent ce qu’un visiteur peut voir. entry.published se déclenche quand une entrée devient visible, y compris un article programmé qui paraît à 9 h sans personne au clavier : chaque date future a un job qui l’attend. entry.updated ne se déclenche que pour une entrée qui était visible et qui l’est toujours. Un brouillon enregistré dix fois n’envoie rien.
6. Chaque destination a sa propre façon d’alerter les gens
Les intégrations publient dans les outils de votre équipe un texte écrit par un inconnu, et chaque outil a sa propre syntaxe pour attirer l’attention. Un retour intitulé <!channel> urgent notifierait tout un canal Slack. Dans Discord, la même astuce s’appelle @everyone, plus un lien masqué qui cache sa destination. Dans GitHub, #123 ou owner/repo#1 dans le corps d’une issue laisse une référence croisée sur l’issue de quelqu’un d’autre, depuis votre compte.
Chaque service a donc son propre échappement. Pour Slack, les caractères spéciaux sont échappés et l’aperçu automatique des liens est désactivé. Pour Discord, chaque caractère Markdown est échappé, les mentions sont désactivées et les embeds supprimés. Le texte pour Telegram est échappé en HTML. Dans une issue GitHub, le texte du visiteur est placé dans un bloc de code dont la clôture est plus longue que toute suite de backticks qu’il contient. Freshdesk reçoit du texte échappé en HTML, parce que le corps de ses tickets est en HTML. Et Jira refuse une description qui contient un paragraphe vide : les lignes vides sont donc retirées avant de lui parvenir.
Rien de tout cela ne se voit quand tout fonctionne. C’est la différence entre un outil de retours et un moyen pour n’importe qui d’écrire à toute votre entreprise.
7. Les identifiants ne voyagent jamais dans la file d’attente
Une URL de webhook Slack est un mot de passe : quiconque la possède peut publier dans votre canal. Idem pour un token de bot Telegram, qui figure directement dans l’URL de l’API. La solution tentante consiste à mettre la configuration déchiffrée dans le job de livraison, pour que le worker ait tout sous la main. Chaque token se retrouve alors en clair dans Redis, à chaque relance, pendant des heures.
Les jobs de Kookee transportent un id d’intégration, un id d’événement et l’événement. Le worker charge la ligne, déchiffre la configuration au dernier moment, et n’écrit jamais l’URL d’une requête dans un log. Les tokens restent aussi hors du journal d’audit, qui enregistre qui a modifié quoi, mais pas le secret saisi.
8. Lisez les règles d’en face avant d’écrire les vôtres
Mon premier plan traitait 403 Forbidden comme « cette intégration ne marchera jamais », et désactivait l’intégration. Le client HTTP de Node n’envoie pas d’en-tête User-Agent sauf si vous en définissez un, et GitHub répond 403 à une requête qui n’en a pas. Toutes les intégrations GitHub se seraient désactivées dès leur première livraison.
La relecture du plan l’a repéré, et chaque requête porte désormais content-type et un User-Agent. La leçon allait plus loin qu’un en-tête : GitHub répond aussi 403 quand une limite de débit est atteinte, et dans ce cas Kookee relance ; Telegram signale une conversation supprimée par un 400 accompagné d’un message, et dans ce cas l’intégration se désactive. Chaque service a une courte liste d’exceptions à une règle simple, consignée à côté du code qui lui envoie les requêtes.
Si vous développez des webhooks
Résolvez l’hôte à l’envoi, refusez les adresses privées et réservées, connectez-vous à l’adresse vérifiée et ne suivez pas les redirections.
Mettez les adresses publiques de vos propres serveurs sur la liste de blocage.
Signez avec Standard Webhooks plutôt qu’avec un format maison.
Donnez à chaque livraison un délai strict, et à chaque endpoint et chaque compte un budget de temps, pour qu’un récepteur lent ne bloque pas tout le monde.
Relancez pendant des heures avec un id de message stable, puis désactivez l’endpoint et prévenez son propriétaire.
Déclenchez les événements de contenu sur ce que voient les visiteurs, pas à chaque enregistrement.
Échappez le texte selon la syntaxe de chaque destination avant de le publier.
Gardez les identifiants hors de la file d’attente, des logs et du journal d’audit.
Les webhooks sont inclus dans tous les forfaits Kookee, et les intégrations dans Pro et Ultra. Envoyer des événements à votre site avec les webhooks détaille les événements, le payload et la vérification de la signature.
Pour aller plus loin
Connecter Slack, Discord, Telegram, GitHub, Jira, Asana ou Freshdesk : ce dont chaque intégration a besoin, et ce qu’elle envoie.
Votre widget de retours ne devrait pas exiger d’inscription : comment les retours des visiteurs arrivent jusqu’à vous.
Votre serveur MCP ne devrait pas obliger à coller un token : l’autre moitié de la connexion de Kookee à vos outils.