# n8n

Start an n8n workflow with each new response, then send it anywhere n8n reaches. Formerie sends a [webhook](/guides/webhooks/), and n8n's **Webhook** node catches it.

We ran these steps on self-hosted n8n 2.45.7, with real signed deliveries from a local copy of Formerie. We haven't run them on n8n Cloud.

## Connect your form

1. In n8n, create a workflow and add a **Webhook** node.
2. Set **HTTP Method** to **POST**, and choose a **Path**, such as `formerie-responses`.
3. Copy the **Production URL**. The **Test URL** works only while n8n is listening in the editor, so it won't catch your form's later responses.
4. In Formerie, open your form, then **Delivery**, and add a webhook with that address. Keep the signing secret.
5. In n8n, publish the workflow, which turns the production address on.
6. In Formerie, choose **Send test**. Find the run under the workflow's **Executions**.

While you build, you can catch a test in the editor: select **Listen for Test Event**, and use the **Test URL** in a second webhook for a moment.

## Use the answers

The body is under `body`, and headers are under `headers`:

```text
{{ $json.body.data.fields.email }}
{{ $json.body.data.submission.language }}
{{ $json.headers['webhook-id'] }}
```

The first is the field called `email` on your form. A nested field follows its groups, such as `fields.contact.email`.

## Check the signature

A workflow's address is secret, but anyone who has it could send fake responses. To refuse them, check the signature. It takes three more nodes and a setting:

1. In the **Webhook** node, set **Respond** to **Using 'Respond to Webhook' Node**, and turn on the **Raw Body** option under **Add Option**.
2. If you host n8n, allow the crypto module by setting `NODE_FUNCTION_ALLOW_BUILTIN=crypto` on your instance.
3. Add a **Code** node after it, in JavaScript, with this, and your own secret:

```javascript
const crypto = require('crypto');

const secret = 'whsec_your_secret';
const item = $input.first();
const headers = item.json.headers;
const rawBody = Buffer.from(item.binary.data.data, 'base64').toString('utf8');

const id = headers['webhook-id'];
const timestamp = headers['webhook-timestamp'];
const fresh = Math.abs(Date.now() / 1000 - Number(timestamp)) <= 300;
const key = Buffer.from(secret.replace(/^whsec_/, ''), 'base64');
const expected = crypto.createHmac('sha256', key).update(`${id}.${timestamp}.${rawBody}`).digest('base64');
const valid = fresh && (headers['webhook-signature'] ?? '').split(' ').some((part) => part === `v1,${expected}`);

return [{ json: { valid, event: valid ? JSON.parse(rawBody) : null } }];
```

4. Add an **If** node with the condition `{{ $json.valid }}` is true.
5. On its true branch, carry on with your workflow and end with a **Respond to Webhook** node answering `200`. On its false branch, add one answering `401`.

After this, your answers are at `$json.event.data.fields`. A forged request got a `401` in our run. A real one got `200`, and kept getting it through a [change of secret](/guides/webhooks/#change-your-secret) until the old one ended, when it got `401` until the node had the new secret.

Keep the secret in an n8n credential if your setup allows it, rather than in the node.

## If it doesn't work

- **Nothing arrives after publishing:** the webhook in Formerie may still have the **Test URL**. Replace it with the **Production URL**.
- **n8n can't be reached:** Formerie needs a public `https` address on the standard port. A self-hosted n8n needs a reverse proxy or tunnel in front of it.
- **The Code node stops on `require`:** the crypto module isn't allowed yet. See step 2.
- **The Code node can't read `binary`:** the **Raw Body** option is off.

Next: [send responses to Google Sheets](/guides/recipe-google-sheets/) or [HubSpot](/guides/recipe-hubspot/).
