Aller au contenu

Make et n8n

Extension payante. Make et n8n reçoivent les soumissions par l’envoi sortant signé, qui existe déjà. Il n’y a pas de module « Make » ni de module « n8n », et ce document explique pourquoi, puis comment brancher chacun des deux.

Pourquoi aucun module n’a été écrit

Le plan prévoyait un AutomationPlatformsAddOn spécialisant l’envoi sortant avec un préréglage par plateforme. En le confrontant à ce que les deux services attendent réellement, il ne restait rien à écrire.

Make et n8n ne sont pas des services d’arrivée comme Mailchimp ou Xero : ce sont des récepteurs génériques. Un webhook personnalisé Make et un nœud Webhook n8n acceptent n’importe quel JSON posté à une adresse, et c’est exactement ce que SolisForms envoie. Les deux préréglages auraient eu le même contenu : la même adresse à coller, le même corps, la même signature.

Les quatre apports annoncés par le plan existaient déjà :

AnnoncéOù c’est déjà
Corps JSON versionné, sélection de champsPanneau « Envoi sortant », avec son format et ses en-têtes
Signature HMACX-SolisForms-Signature, horodatée
Journal, reprise, renvoi manuelJournal des envois, RetryPolicy, renvoi depuis la soumission
Condition par formulaireLogique conditionnelle du panneau

Un module en plus aurait donné deux chemins pour le même envoi, deux journaux à consulter quand rien n’arrive, et deux fois le même code à maintenir — pour un intitulé différent dans une liste déroulante. Le plan le disait lui-même : « La fonctionnalité ne doit pas dupliquer l’ancien webhook lorsque le preset n’apporte aucune valeur. » C’est le cas, pour les deux.

Ce qui manquait n’était pas du code : c’était ce document.

Zapier, lui, a un module. La différence est que Zapier interroge le site — il lui faut un point d’entrée REST, une liste d’abonnements, un échantillon à montrer dans son éditeur et une déduplication sur un identifiant de livraison. Make et n8n ne demandent rien de tout cela : ils attendent.

Le corps transmis

Toujours le même, quelle que soit la destination :

{
  "entry_id": 42,
  "form_id": 7,
  "form_title": "Demande de devis",
  "created_at": "2026-10-05 09:00:00",
  "fields": [
    { "id": "entreprise", "label": "Entreprise", "value": "Atelier Rousseau" },
    { "id": "besoin", "label": "Besoin", "value": "Un devis détaillé" }
  ]
}

Reformaté ici pour la lecture : sur le réseau, c’est une seule ligne, et les caractères non ASCII y sont échappés — ce qui compte dès qu’on vérifie la signature.

fields est une liste, pas un objet indexé par identifiant. C’est volontaire : l’ordre est celui du formulaire, et deux champs peuvent porter le même libellé. Dans Make comme dans n8n, on retrouve une valeur en filtrant la liste sur id.

Chaque champ porte son identifiant et son libellé parce qu’un destinataire tiers ne connaît pas nos identifiants internes, et qu’un corps réduit à champ_17 serait inexploitable sans ouvrir le formulaire.

Les valeurs sont celles que l’administration montre : le libellé d’une option choisie, pas sa valeur technique. Un champ de téléversement porte le nom du fichier, pas le fichier.

Ce qui ne sort pas : les mots de passe, écartés de toute sortie du plugin, et les blocs de mise en page, qui n’ont pas de valeur.

created_at est en temps universel, au format Y-m-d H:i:s.

La signature

Si un secret est renseigné dans le panneau, chaque envoi porte :

X-SolisForms-Signature: t=1767225600,v1=75bf80ca…

v1 est le HMAC-SHA256, en hexadécimal, de la chaîne t + . + le corps tel qu’il a été envoyé, avec le secret comme clé.

Le schéma est celui des passerelles de paiement : un destinataire déjà outillé pour Stripe n’a rien de nouveau à écrire, et l’horodatage borne la fenêtre de rejeu — un corps capté puis renvoyé plus tard ne vaut plus.

Le piège, et c’est le même des deux côtés

La signature porte sur les octets reçus. Une plateforme qui analyse le JSON puis le ré-encode pour vous le présenter produit un corps différent : SolisForms échappe les caractères non ASCII : détaillé part en d\u00e9taill\u00e9. Une plateforme qui ré-encode le rend à détaillé — des octets différents, donc une empreinte différente, et la signature ne correspond plus, avec pourtant le bon secret.

C’est le symptôme numéro un. Il faut donc demander à la plateforme le corps brut.

Un vecteur pour éprouver votre code

Secret secret-partage, horodatage 1767225600, et pour corps exactement cette ligne — les \u00e9 sont littéralement ce qui circule, et il n’y a pas de retour à la ligne final :

{"entry_id":42,"form_id":7,"form_title":"Demande de devis","created_at":"2026-10-05 09:00:00","fields":[{"id":"entreprise","label":"Entreprise","value":"Atelier Rousseau"},{"id":"besoin","label":"Besoin","value":"Un devis d\u00e9taill\u00e9"}]}

La signature attendue est :

75bf80ca124f942ad44964e55cfc2403b8c7ffc13da59a20477c14795fb2f05c

Si votre implémentation rend cette valeur, elle est juste. Sinon, c’est presque toujours que vous hachez un corps ré-encodé plutôt que celui qui est arrivé.

Ce vecteur est tenu par un test du dépôt : s’il cessait d’être exact, la suite échouerait avant que quelqu’un ne cherche l’erreur dans son propre code.

Le raccourci qui suffit à la plupart

Vérifier un HMAC dans un scénario visuel est pénible des deux côtés. Pour la plupart des usages, il existe plus simple et aussi sûr :

  1. L’adresse de réception est déjà un secret : qui ne l’a pas ne peut rien poster. Make en engendre une au hasard. n8n, lui, vous laisse choisir le chemin du nœud Webhook — il propose un identifiant aléatoire, et le remplacer par /webhook/formulaire rend l’adresse devinable. Gardez celui qu’il propose.
  2. Si cela ne suffit pas, le champ En-têtes supplémentaires du panneau accepte une ligne X-Jeton-Partage: <valeur longue et aléatoire>. La plateforme compare cet en-tête à une constante et rejette tout le reste. C’est un contrôle en une ligne, là où la signature en demande une dizaine.

La signature garde un avantage réel : elle prouve que le corps n’a pas été modifié en route, et son horodatage ferme le rejeu. Un jeton partagé ne fait ni l’un ni l’autre. Prenez-la quand ce que déclenche le scénario est irréversible — un paiement, un envoi, une écriture comptable.

Brancher Make

  1. Dans Make, créez un scénario et ajoutez le module Webhooks → Custom webhook. Copiez l’adresse qu’il affiche, sur hook.<région>.make.com.
  2. Dans SolisForms, ouvrez le formulaire, panneau Envoi sortant : activez-le, collez l’adresse, laissez la méthode POST et le format JSON.
  3. Make doit apprendre la forme des données avant de pouvoir les mapper. Laissez son module en écoute (« Determine data structure »), puis soumettez le formulaire une fois. Le panneau n’a pas de bouton d’essai : la soumission est l’essai.
  4. Les champs deviennent alors mappables dans les modules suivants.

Pour vérifier la signature, activez JSON pass-through sur le webhook : Make vous remet alors le corps brut au lieu de la structure analysée, et c’est sur lui que porte le calcul. Sans cette option, la vérification échouera quoi que vous écriviez.

Make expose des fonctions de hachage avec clé, qui produisent le HMAC attendu. Plutôt que de recopier une signature d’appel qui change avec les versions, éprouvez la vôtre sur le vecteur ci-dessus : c’est pour cela qu’il est publié.

Brancher n8n

  1. Ajoutez un nœud Webhook, méthode POST. Copiez l’URL de production ; pendant l’édition, n8n écoute sur l’URL de test, qui est une autre adresse.
  2. Mêmes réglages côté SolisForms que pour Make.
  3. Pour vérifier la signature, activez l’option Raw Body du nœud Webhook, sinon n8n vous remet un objet analysé et le calcul portera sur autre chose.

Un nœud Code suffit ensuite :

const crypto = require( 'crypto' );

const secret = 'votre-secret';
const entete = $input.first().json.headers['x-solisforms-signature'] || '';
const corps  = $input.first().json.body;

const parties = Object.fromEntries(
	entete.split( ',' ).map( ( p ) => p.split( '=' ) )
);

const attendue = crypto
	.createHmac( 'sha256', secret )
	.update( parties.t + '.' + corps )
	.digest( 'hex' );

if (
	! parties.v1 ||
	! crypto.timingSafeEqual(
		Buffer.from( attendue ),
		Buffer.from( parties.v1 )
	)
) {
	throw new Error( 'Signature invalide' );
}

// L'horodatage ferme le rejeu : au-delà de cinq minutes, on refuse.
if ( Math.abs( Date.now() / 1000 - Number( parties.t ) ) > 300 ) {
	throw new Error( 'Signature périmée' );
}

return [ { json: JSON.parse( corps ) } ];

timingSafeEqual exige deux tampons de même longueur : une signature tronquée le fait lever, ce qui est le comportement voulu.

Sur n8n hébergé par l’éditeur, les modules Node.js externes sont restreints ; le nœud Crypto fait le même calcul sans require.

Ce que fait SolisForms quand ça se passe mal

L’envoi est planifié, jamais exécuté pendant la soumission : un scénario lent ou en panne ne retarde pas le visiteur, et ne fait pas échouer son envoi.

Une panne réseau, un 408, un 429 ou un 5xx sont rejoués : trois tentatives en tout, à une puis cinq puis trente minutes. Un 4xx autre que ces deux-là n’est pas rejoué — un corps refusé ne sera pas mieux accepté à la troisième tentative, et insister ne ferait qu’inonder la plateforme de requêtes déjà jugées mauvaises.

Attention au quota : les deux plateformes comptent les exécutions, et une reprise en consomme une. Un scénario qui échoue pour une raison qui lui est propre sera rejoué jusqu’à trois fois.

Chaque tentative est consignée au journal des envois, visible dans le détail de la soumission avec son code HTTP et le début de la réponse. Un renvoi manuel s’y trouve également.

L’adresse est contrôlée avant chaque appel : elle doit être publique et en http(s). Une adresse pointant vers un service interne est refusée, et le refus est écrit au journal — un compte compromis ou l’importation d’un formulaire venu d’ailleurs pourraient sinon faire atteindre au serveur ce que personne d’autre ne peut joindre.

N’envoyer que certaines soumissions

La logique conditionnelle du panneau décide à l’arrivée de la soumission. Filtrer dans SolisForms plutôt que dans le scénario a deux avantages : les soumissions écartées ne consomment aucune exécution, et elles ne quittent pas le site.

Ce qui n’existe pas, et ne viendra pas par cette voie

Pas de réception de commandes venant de Make ou n8n : le site envoie, il n’écoute pas. Pas de scénario bidirectionnel, pas de déclencheur autre que l’arrivée d’une soumission.

Un paiement confirmé ne déclenche pas d’envoi distinct — un formulaire qui encaisse refuse la soumission si le règlement ne convient pas, donc une soumission enregistrée est déjà une soumission payée.

Pour écrire dans une comptabilité, un agenda, un CRM ou un tableur, les modules dédiés font mieux que ces plateformes : ils connaissent les contraintes du service visé — idempotence, rapprochement, options de liste, fuseaux — qu’un scénario générique doit redécouvrir. Voir integration-comptabilite.md, integration-calendriers.md et les autres.