Skip to content

Make and n8n

Paid add-on. Make and n8n receive submissions through the signed outbound webhook, which already exists. There is no “Make” module and no “n8n” module, and this document explains why, then how to connect each of the two.

Why no module was written

The plan provided for an AutomationPlatformsAddOn specialising the outbound webhook with a preset per platform. Set against what the two services actually expect, there was nothing left to write.

Make and n8n are not destination services like Mailchimp or Xero: they are generic receivers. A Make custom webhook and an n8n Webhook node accept any JSON posted to an address, and that is exactly what SolisForms sends. The two presets would have had the same contents: the same address to paste, the same body, the same signature.

The four contributions the plan announced already existed:

AnnouncedWhere it already is
Versioned JSON body, field selection“Outbound webhook” panel, with its format and its headers
HMAC signatureX-SolisForms-Signature, timestamped
Log, retry, manual resendDelivery log, RetryPolicy, resend from the submission
Per-form conditionThe panel’s conditional logic

One more module would have given two paths for the same send, two logs to consult when nothing arrives, and twice the same code to maintain — for a different label in a dropdown. The plan said so itself: “The feature must not duplicate the old webhook when the preset brings no value.” That is the case, for both.

What was missing was not code: it was this document.

Zapier, on the other hand, does have a module. The difference is that Zapier queries the site — it needs a REST endpoint, a list of subscriptions, a sample to show in its editor and deduplication on a delivery id. Make and n8n ask for none of that: they wait.

The body transmitted

Always the same, whatever the destination:

{
  "entry_id": 42,
  "form_id": 7,
  "form_title": "Quote request",
  "created_at": "2026-10-05 09:00:00",
  "fields": [
    { "id": "company", "label": "Company", "value": "Café Rousseau" },
    { "id": "need", "label": "Need", "value": "A detailed quote" }
  ]
}

Reformatted here for reading: on the network it is a single line, and the non-ASCII characters in it are escaped — which matters as soon as you verify the signature.

fields is a list, not an object indexed by id. That is deliberate: the order is the form’s, and two fields can carry the same label. In Make as in n8n, you find a value again by filtering the list on id.

Each field carries its id and its label because a third-party recipient does not know our internal ids, and a body reduced to field_17 would be unusable without opening the form.

The values are the ones the admin shows: the label of a chosen option, not its technical value. An upload field carries the file’s name, not the file.

What does not go out: passwords, set aside from every output of the plugin, and layout blocks, which have no value.

created_at is in universal time, in the Y-m-d H:i:s format.

The signature

If a secret is filled in on the panel, every send carries:

X-SolisForms-Signature: t=1767225600,v1=fcab2406…

v1 is the HMAC-SHA256, in hexadecimal, of the string t + . + the body as it was sent, with the secret as the key.

The scheme is the payment gateways’: a recipient already equipped for Stripe has nothing new to write, and the timestamp bounds the replay window — a body captured and sent again later is worth nothing.

The trap, and it is the same on both sides

The signature bears on the bytes received. A platform that parses the JSON and then re-encodes it to present it to you produces a different body: SolisForms escapes non-ASCII characters: Café goes out as Caf\u00e9. A platform that re-encodes gives it back as Café — different bytes, therefore a different fingerprint, and the signature no longer matches, with the right secret all the same.

That is symptom number one. You therefore have to ask the platform for the raw body.

A vector to test your code against

Secret shared-secret, timestamp 1767225600, and for the body exactly this line — the \u00e9 is literally what travels, and there is no trailing newline:

{"entry_id":42,"form_id":7,"form_title":"Quote request","created_at":"2026-10-05 09:00:00","fields":[{"id":"company","label":"Company","value":"Caf\u00e9 Rousseau"},{"id":"need","label":"Need","value":"A detailed quote"}]}

The expected signature is:

fcab24065f8857307c2b67338c76f0b9b05194e91e0a06689669704eefd0a057

If your implementation returns that value, it is right. Otherwise it is almost always because you are hashing a re-encoded body rather than the one that arrived.

This vector is held by a test in the repository: were it to stop being exact, the suite would fail before anybody went looking for the error in their own code.

The shortcut that is enough for most people

Verifying an HMAC in a visual scenario is tedious on both sides. For most uses there is something simpler and just as safe:

  1. The receiving address is already a secret: whoever does not have it cannot post anything. Make generates one at random. n8n, for its part, lets you choose the path of the Webhook node — it offers a random id, and replacing it with /webhook/form makes the address guessable. Keep the one it offers.
  2. If that is not enough, the panel’s Additional headers field accepts a line X-Shared-Token: <long random value>. The platform compares that header against a constant and rejects everything else. It is a one-line check, where the signature asks for about ten.

The signature keeps a real advantage: it proves that the body was not modified on the way, and its timestamp closes off replay. A shared token does neither. Take it when what the scenario triggers is irreversible — a payment, a dispatch, an accounting entry.

Connecting Make

  1. In Make, create a scenario and add the Webhooks → Custom webhook module. Copy the address it displays, on hook.<region>.make.com.
  2. In SolisForms, open the form, Outbound webhook panel: switch it on, paste the address, leave the method as POST and the format as JSON.
  3. Make has to learn the shape of the data before it can map it. Leave its module listening (“Determine data structure”), then submit the form once. The panel has no test button: the submission is the test.
  4. The fields then become mappable in the following modules.

To verify the signature, switch on JSON pass-through on the webhook: Make then hands you the raw body instead of the parsed structure, and that is what the computation bears on. Without that option, verification will fail whatever you write.

Make exposes keyed hashing functions, which produce the expected HMAC. Rather than copying a call signature that changes with versions, test yours against the vector above: that is what it is published for.

Connecting n8n

  1. Add a Webhook node, method POST. Copy the production URL; while editing, n8n listens on the test URL, which is a different address.
  2. The same settings on the SolisForms side as for Make.
  3. To verify the signature, switch on the Webhook node’s Raw Body option, otherwise n8n hands you a parsed object and the computation will bear on something else.

A Code node is then enough:

const crypto = require( 'crypto' );

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

const parts = Object.fromEntries(
	header.split( ',' ).map( ( p ) => p.split( '=' ) )
);

const expected = crypto
	.createHmac( 'sha256', secret )
	.update( parts.t + '.' + body )
	.digest( 'hex' );

if (
	! parts.v1 ||
	! crypto.timingSafeEqual(
		Buffer.from( expected ),
		Buffer.from( parts.v1 )
	)
) {
	throw new Error( 'Invalid signature' );
}

// The timestamp closes off replay: beyond five minutes, we refuse.
if ( Math.abs( Date.now() / 1000 - Number( parts.t ) ) > 300 ) {
	throw new Error( 'Signature expired' );
}

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

timingSafeEqual requires two buffers of the same length: a truncated signature makes it throw, which is the intended behaviour.

On n8n hosted by its publisher, external Node.js modules are restricted; the Crypto node does the same computation without require.

What SolisForms does when things go wrong

The send is scheduled, never executed during the submission: a slow or broken scenario does not delay the visitor, and does not fail their submission.

A network failure, a 408, a 429 or a 5xx are replayed: three attempts in all, at one then five then thirty minutes. A 4xx other than those two is not replayed — a refused body will be no better accepted on the third attempt, and insisting would only flood the platform with requests already judged bad.

Mind the quota: both platforms count executions, and a retry consumes one. A scenario failing for a reason of its own will be replayed up to three times.

Every attempt is recorded in the delivery log, visible in the submission’s detail with its HTTP code and the beginning of the response. A manual resend is there too.

The address is checked before every call: it must be public and in http(s). An address pointing at an internal service is refused, and the refusal is written to the log — a compromised account or the import of a form from elsewhere could otherwise have the server reach what nobody else can.

Sending only some submissions

The panel’s conditional logic decides when the submission arrives. Filtering in SolisForms rather than in the scenario has two advantages: the submissions set aside consume no execution, and they do not leave the site.

What does not exist, and will not come by this route

No receiving of commands from Make or n8n: the site sends, it does not listen. No two-way scenario, no trigger other than a submission’s arrival.

A confirmed payment does not trigger a separate send — a form that takes money refuses the submission if the payment does not suit, so a recorded submission is already a paid submission.

To write into an accounting system, a calendar, a CRM or a spreadsheet, the dedicated modules do better than these platforms: they know the constraints of the service targeted — idempotence, matching, select options, timezones — which a generic scenario has to rediscover. See accounting, calendars and the others.