Analyser
Des livraisons signées, et un courriel qui ne dit rien.
Un webhook transmet chaque nouvelle réponse à une URL https de votre
choix. Chaque livraison porte un en-tête
X-NumoForms-Signature : sha256= suivi d'un
HMAC-SHA256 du corps exact de la requête, calculé avec un secret propre à
ce webhook, afin que votre point de terminaison puisse prouver que la
charge utile vient bien de nous. Séparément, les adresses de courriel de
votre choix peuvent être prévenues à l'arrivée d'une réponse — et ces
courriels ne contiennent délibérément aucune réponse.
Ce qui arrive à votre point de terminaison
Un unique POST JSON par réponse. Le corps contient cinq champs et rien d'autre : vous pouvez donc écrire un analyseur sans avoir à deviner.
| Champ | Ce qu'il contient |
|---|---|
event | response.created |
survey_id | L'enquête à laquelle appartient la réponse. |
response_id | L'identifiant unique de cette réponse. |
created_at | La date et l'heure d'enregistrement de la réponse. |
answers | Les réponses, indexées sur l'identifiant de question. |
| En-tête | À quoi il sert |
|---|---|
X-NumoForms-Event | Le nom de l'événement. Il en existe un aujourd'hui : response.created. |
X-NumoForms-Signature | sha256= suivi du HMAC-SHA256 du corps exact de la requête, en hexadécimal. |
X-NumoForms-Delivery | Un identifiant unique pour cette tentative de livraison, afin de repérer un doublon. |
La livraison est déclenchée par un déclencheur de base de données sur la table des réponses, et non par un écran particulier de l'application. Cela compte plus qu'il n'y paraît : une réponse envoyée depuis un formulaire intégré, depuis un lien hébergé, ou une réponse partielle reprise plusieurs jours plus tard grâce à l'enregistrement avec reprise empruntent toutes le même chemin — il n'existe donc pas de route que nous aurions oublié de raccorder.
Le webhook se déclenche à l'arrivée de la réponse. Si vous avez activé les validations, il n'attend pas la décision d'approbation ou de rejet, et aucune seconde livraison n'est envoyée lorsque cette décision est prise. Traitez une charge utile entrante comme « une réponse existe », non comme « une réponse a été validée ».
Pourquoi la signature n'est pas facultative
Une URL de webhook est un secret qui fuit. Elle finit dans un ticket, un message d'équipe, une capture d'écran d'un document de passation, un historique de navigation sur un portable partagé. Sans signature, quiconque a vu l'URL peut y transmettre ce qu'il veut, et vos systèmes le classeront comme une véritable réponse de consultation. Pour une collectivité qui dénombre les objections à un projet d'urbanisme, ce n'est pas un problème théorique.
Chaque livraison est donc signée. Chaque webhook reçoit son propre secret généré aléatoirement, affiché dans l'éditeur derrière une commande « afficher le secret de signature ». Nous calculons un HMAC-SHA256 sur les octets exacts du corps de la requête à l'aide de ce secret ; vous le recalculez avec votre copie et vous comparez. Une requête falsifiée échoue, parce que son auteur ne détient pas le secret.
L'URL doit être en https. Cela est imposé par une contrainte
de base de données, et non par une simple indication dans le formulaire,
car un secret de signature qui circule sur une connexion non chiffrée
n'est pas un secret.
Vérifier une livraison en Node.js
Deux détails causent l'essentiel des dégâts en pratique. Utilisez le corps brut de la requête, et non un objet analysé puis re-sérialisé — les différences d'espaces modifient l'empreinte. Et comparez en temps constant, afin qu'un attaquant ne puisse pas deviner la signature correcte octet par octet à partir des temps de réponse.
import { createHmac, timingSafeEqual } from 'node:crypto';
const SECRET = process.env.NOISSIME_WEBHOOK_SECRET;
// rawBody must be the exact bytes received, before any JSON parsing.
// Re-serialising a parsed object changes the whitespace and the check fails.
function isFromNoissime(rawBody, header) {
if (typeof header !== 'string') return false;
const expected =
'sha256=' + createHmac('sha256', SECRET).update(rawBody).digest('hex');
const a = Buffer.from(expected);
const b = Buffer.from(header);
// timingSafeEqual throws on a length mismatch, so compare lengths first.
return a.length === b.length && timingSafeEqual(a, b);
}
Appelez cette fonction avant toute analyse. Si elle renvoie faux, répondez 401 et écartez la requête. Si elle renvoie vrai, vous tenez une réponse qui ne peut provenir que de votre enquête.
Le journal de livraison
Un webhook qui a cessé de fonctionner sans bruit ressemble exactement à un webhook qui n'a jamais été appelé : rien ne se passe, et rien ne vous en avertit. Chaque tentative est donc inscrite dans un journal de livraison, et le dernier statut HTTP s'affiche en regard du webhook dans l'éditeur — vert pour un 2xx, rouge pour tout le reste.
La livraison est asynchrone : le statut est donc rapproché après coup plutôt que supposé au moment de l'envoi. Ce que vous voyez est le statut que votre serveur a réellement renvoyé. Chaque webhook peut être suspendu puis réactivé sans être supprimé, ce que l'on veut habituellement pendant le redéploiement du système récepteur.
Une limite assumée : il n'y a pas de nouvelle tentative avec temporisation exponentielle. Une livraison est tentée une fois, le résultat est consigné, et l'affaire s'arrête là. Construire une file de reprise qui respecte l'ordre et l'idempotence est un vrai chantier, et prétendre en avoir une que nous n'avons pas construite serait pire que de le dire. Entre-temps, la réponse n'est jamais perdue : elle est dans la base de données, visible dans les résultats et rapports, et exportable en CSV.
Un webhook défaillant ne peut pas non plus interrompre la collecte. Si la livraison échoue, l'erreur est consignée et la réponse est tout de même enregistrée. La réponse du répondant est ce qui compte ; la livraison se fait au mieux.
Des notifications par courriel, sans les réponses
Ajoutez les adresses qui doivent être averties des nouvelles réponses et elles recevront un courriel dès qu'une réponse arrive. Le message est en HTML aux couleurs de la marque, avec une version texte brut, car les clients texte seul et les filtres anti-spam lisent tous deux cette partie. Il nomme l'enquête, donne le total de réponses en cours et renvoie vers les résultats.
Ce qu'il ne contient pas, ce sont les réponses. C'est une décision, non un oubli. Un courriel de notification est transféré à un collègue, classé automatiquement dans une boîte partagée, synchronisé sur un téléphone et conservé des années dans une sauvegarde. Y placer des réponses libres de consultation disperse des données personnelles dans des systèmes que personne n'a évalués, et défait discrètement les contrôles d'accès décrits sur la page sécurité. Les données des répondants restent dans la base de données, et le courriel vous invite à aller les consulter.
Comme les webhooks, les notifications sont déclenchées par un déclencheur de base de données : elles suivent donc la réponse et non une voie de soumission particulière. Si aucune adresse n'est configurée, rien n'est envoyé et rien n'est tenté.
Ce que cela ne fait pas
- Aucune application Zapier, et aucun connecteur Google Sheets.
- Aucune nouvelle tentative avec temporisation — une tentative par réponse, puis le journal.
- Aucun événement sortant autre que
response.created. Les validations, les modifications et les suppressions ne déclenchent pas de webhook. - Aucune API entrante pour créer des enquêtes ou lire des réponses par programme.
- Aucune liste d'adresses IP autorisées pour les livraisons. La signature tient lieu d'authentification.
Questions courantes
Comment vérifier l'en-tête X-NumoForms-Signature ?
Calculez un HMAC-SHA256 du corps brut de la requête à l'aide du secret de signature de ce webhook, encodez-le en hexadécimal, préfixez-le de « sha256= » et comparez-le à l'en-tête au moyen d'une comparaison à temps constant. Vérifiez avant d'analyser le JSON, et rejetez par un 401 tout ce qui ne correspond pas.
Que se passe-t-il si mon point de terminaison est hors service au moment où une réponse arrive ?
La livraison est tentée une fois et le résultat est consigné dans le journal de livraison avec le statut HTTP. Il n'y a pas de nouvelle tentative automatique avec temporisation progressive. La réponse elle-même n'est jamais en danger : l'échec d'un webhook ne peut pas empêcher l'enregistrement d'une réponse, et vous pouvez toujours récupérer les données depuis l'écran de résultats ou un export CSV.
Un webhook se déclenche-t-il pour une réponse envoyée depuis un formulaire intégré ?
Oui. La livraison est déclenchée par un déclencheur de base de données sur la table des réponses, et non par une page particulière de l'application : elle a donc lieu quelle que soit la provenance de la réponse — lien hébergé, formulaire intégré, ou réponse partielle reprise puis achevée.
Puis-je envoyer un webhook vers une URL en http:// ?
Non. Une contrainte de vérification en base de données impose que l'URL commence par https://, et l'éditeur refuse toute autre valeur. Un secret de signature transmis sur une connexion non chiffrée n'est pas un secret.
Les courriels de notification contiennent-ils les réponses du répondant ?
Non, délibérément. Le courriel indique qu'une réponse est arrivée, nomme l'enquête, donne le total en cours et renvoie vers les résultats. Les notifications se transfèrent et séjournent dans les sauvegardes de messagerie : les données des répondants restent donc dans la base de données, là où se trouvent les contrôles d'accès.
Existe-t-il une application Zapier ou une intégration Google Sheets ?
Non. Les webhooks sont aujourd'hui la seule intégration sortante. Il n'existe ni application Zapier ni connecteur Google Sheets, et aucune date n'a été annoncée pour l'un ou l'autre.
Branchez une enquête sur vos propres systèmes.
Dirigez un webhook vers un point de terminaison https et chaque réponse y arrive signée : votre récepteur peut prouver qu'elle vient bien de nous, et non de quiconque a trouvé l'URL. Indiquez les adresses à prévenir par courriel quand une réponse arrive, et les réponses elles-mêmes restent dans la base, là où s'appliquent les contrôles d'accès.
- Une signature HMAC-SHA256 sur chaque livraison
- Un journal de livraison qui indique le dernier statut HTTP
- Déclenché par la base de données, quelle que soit la voie d'entrée