Skip to main content

Send submissions to n8n

Twenty minutes, once per form. n8n's Webhook node is a plain HTTPS endpoint that hands you the body, the headers and the raw bytes - which makes it the one automation tool where every part of a LocalForm webhook is usable, signature included. Self-hosted, it is also the only one where the submission never leaves infrastructure you control: WordPress on your server, n8n on your server, nobody else in the path.

Start with Send submissions to another system if you have not: the payload, the field names, the delivery log and the retry behaviour are the same wherever you send them, and this page does not repeat them.

Build the workflow first - it produces the URL that LocalForm needs.

1. Add the Webhook node

  1. Create a workflow and add the Webhook node as its trigger.
  2. Set HTTP Method to POST. The default is GET, and a GET-only webhook answers LocalForm's POST with a 404.
  3. Leave Authentication at None. Basic, Header and JWT auth all expect the caller to send credentials, and LocalForm sends only its signature header - step 7 is how you secure this instead.
  4. Leave the generated Path (a UUID) alone. It is long and random, which is most of what keeps the endpoint private.

The node shows two URLs, and picking the wrong one is the mistake that costs the most time here:

Listens whenUse it for
Test URLOnly after you click Listen for test event, and only for the next requestBuilding and debugging, with the data visible in the editor
Production URLWhenever the workflow is ActiveThe address you actually save on the form

A test that works and then goes silent the next day is almost always the Test URL saved into the form, with the editor long since closed.

2. Send it one real submission

Click Listen for test event, copy the Test URL, paste it into the form's Webhook URL (General panel), and submit the published form yourself as a visitor would. Fill in every field, including the optional ones - the sample you capture now is what you will drag from for the rest of the build.

The Webhook node's output is the whole request, not just the body:

{
"headers": {
"content-type": "application/json",
"x-localform-signature": "9f1c…"
},
"params": {},
"query": {},
"body": {
"form_id": 12,
"form_title": "Autumn Workshop 2026",
"submission_id": 345,
"event_date": "2026-09-12",
"fields": { "name": "Ada Lovelace", "email": "ada@example.com" },
"custom_fields": { "source": "website" }
}
}

So an answer is {{ $json.body.fields.email }}, the form title is {{ $json.body.form_title }}, and the signature is {{ $json.headers['x-localform-signature'] }} - lowercased, because Node lowercases incoming header names.

Nothing arriving at all? Check LocalForm → Settings → Webhooks → Logs before you touch the workflow: it distinguishes "never sent" from "sent and rejected", and it is where a certificate or reachability problem shows up as a WP_Error message rather than a status code.

3. Build the rest, then activate

Add whatever the workflow is for - a database insert, a Google Sheet row, a Matrix or Slack message, an HTTP Request to your own API - and drag the fields in from the sample.

Then Activate the workflow, go back to the Webhook node, copy the Production URL, and replace the test URL on the form with it. Submit once more to confirm, and check the Executions tab: production runs are logged there rather than shown in the editor.

4. Choose how it answers

The Webhook node's Respond option decides what LocalForm gets back, and LocalForm waits 15 seconds for it.

  • Immediately (default) answers 200 before the workflow runs. The safest choice: a slow workflow can never be counted as a failed delivery.
  • When Last Node Finishes holds the request open until everything has run. Convenient, and a trap on any workflow with a slow API in it - past 15 seconds LocalForm gives up and retries, so the workflow runs again while the first run is still going.
  • Using 'Respond to Webhook' Node lets you answer early and keep working afterwards, and is the option that can redirect the visitor.

5. Redirect the visitor onward

For a payment page or a personalised thank-you, set Respond to Using 'Respond to Webhook' Node, then add a Respond to Webhook node early in the workflow - before any slow step - responding with JSON:

{ "redirect_url": "https://example.com/thank-you/{{ $json.body.submission_id }}" }

LocalForm sends the visitor there after submitting. A Redirect URL set on the form itself always wins over one the workflow returns, which is the first thing to check when a redirect goes somewhere unexpected.

6. Lock the endpoint down to your server

The Webhook node has IP Allowlist under Options - neither hosted alternative offers it. Put the public IP of the machine running WordPress in there and everything else gets a 403 before your workflow ever runs, which removes the whole class of "someone found the URL" problems.

Check the IP the requests actually arrive from (the log on the n8n side, or your host's control panel) rather than assuming: a site behind a proxy or CDN sends its origin's address, not the one that serves your visitors.

7. Verify the signature

Set a Webhook Secret Key under LocalForm → Settings → Webhooks and every request carries X-LocalForm-Signature, an HMAC-SHA256 of the body. n8n can check it properly - but only against the raw bytes.

This is the part people get wrong: the signature covers exactly what LocalForm sent, and WordPress's JSON encoder escapes forward slashes and non-ASCII characters (https:\/\/example.com, é). Re-serialising $json.body in JavaScript produces different bytes and therefore a different digest, no matter how correct the code looks. So:

  1. In the Webhook node's Options, turn on Raw Body. The parsed body disappears and the payload arrives as binary data (property data) instead.
  2. Verify, then parse.

With the Crypto node (no code, works on n8n Cloud):

  • Action: Hmac, Type: SHA256, Encoding: HEX.
  • Turn on Binary File and set Binary Property Name to data, so the digest is taken over the raw bytes.
  • Put the secret in the node's credential, and send the result into an IF node comparing it to {{ $json.headers['x-localform-signature'] }}.

With a Code node (fewer moving parts, and it parses the body for you):

const crypto = require('crypto');

const raw = await this.helpers.getBinaryDataBuffer(0, 'data');
const signature = $input.first().json.headers['x-localform-signature'] || '';
const expected = crypto.createHmac('sha256', $env.LOCALFORM_WEBHOOK_SECRET)
.update(raw)
.digest('hex');

const a = Buffer.from(signature);
const b = Buffer.from(expected);
if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
throw new Error('Bad LocalForm signature');
}

return [{ json: JSON.parse(raw.toString('utf8')) }];

Self-hosted, require is blocked until you allow it: set NODE_FUNCTION_ALLOW_BUILTIN=crypto on the n8n container and restart. If that is not something you can change, use the Crypto node above.

Either way the check runs before anything else in the workflow, and a missing signature is a failure rather than an unsigned request - no header means no secret is configured on the WordPress side.

When delivery fails

Read LocalForm → Settings → Webhooks → Logs first; the response body from n8n is recorded there.

What the log showsWhat it means
Nothing at allWP-Cron has not run yet on a quiet site. Load any page and look again.
404The workflow is not active, or the node is not set to POST, or the URL is the Test one and nothing is listening.
403The IP allowlist does not include the address your server actually sends from.
500 with an error messageThe workflow ran and threw - the signature check in step 7 answers this way by design.
A WP_Error about SSLn8n's certificate is self-signed or expired. Certificate verification cannot be turned off from the settings.
cURL error 7 / connection refusedThe WordPress server cannot reach n8n at all - the usual cause is an n8n bound to localhost, or a firewall between the two machines.

The full payload reference - including the Pro event_dates and payment_prices arrays - is in webhooks. The other walkthroughs: Power Automate, Zapier.

LocalForm is not affiliated with, or endorsed by, n8n.