# Consume Embers webhooks in n8n

URL: https://useembers.com/help/integrations/n8n/
Category: Integrations
Plan: Solo and above
Updated: 2026-10-28
Last verified: 2026-10-28

> A community recipe: point a signed Embers endpoint at an n8n Webhook node, verify the HMAC in a Code node, then run your own workflow.

n8n can receive Embers leads through the same [signed HTTPS webhook](/help/integrations/webhooks/) that Zapier, Make, Clay, and hand-written receivers use. This is a community recipe: Embers does not build, test, or support an n8n node.

> **Warning: Community recipe on Solo and above**
>
> Webhook endpoints require Solo or above, and only a workspace admin can create them. n8n is not listed on the Integrations screen in the Embers app, and nothing in the product behaves differently for it. When a workflow breaks, the endpoint's **Activity** on the Webhooks page (or **Recent deliveries** under Settings, then API and webhooks) shows what Embers sent and what n8n answered; everything after that is yours to debug.

## Before you start

- An n8n instance, cloud or self-hosted, reachable over public HTTPS on port 443.
- On a self-hosted instance, the Code node allowed to load the built-in `crypto` module (`NODE_FUNCTION_ALLOW_BUILTIN=crypto`).
- Somewhere to keep the `whsec_` signing secret. The example reads it from the `EMBERS_WEBHOOK_SECRET` environment variable.

## Set up the workflow

### Add a Webhook node

Create a workflow that starts with a **Webhook** node. Set **HTTP Method** to `POST` and leave **Respond** on **Immediately**, so n8n answers `200` before your workflow runs. Under **Options**, turn on **Raw Body**: the node then keeps the exact bytes Embers sent as binary data named `data`, which the signature check needs.

### Register the URL in Embers

Copy the node's **Production URL**, not the Test URL, and activate the workflow. In Embers, open **Integrations** in the sidebar, then the **Webhooks** row (`/integrations/webhooks`), choose **Add endpoint**, and paste it. **Settings**, then **API and webhooks**, then **Webhooks** works too. Store the `whsec_` secret from **Save the signing secret** where the Code node can read it.

### Subscribe to the events you want

Keep **New ICP lead** (`lead.created`) for one event per newly qualified lead. **New engagement** (`lead.engagement.added`) is the highest-volume event and is not selected on a new endpoint. **Lead status changed**, **Lead changed** (`lead.changed`, a [company change](/help/leads/company-changes/) at a lead, also not selected on a new endpoint), and **Report ready** are there when you need them.

### Verify the signature in a Code node

Add a **Code** node straight after the Webhook node, in **Run Once for All Items** mode, with this JavaScript. It throws on anything that fails, which stops the execution, and passes on the parsed event when the check succeeds.

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

const TOLERANCE_SECONDS = 300;
const secret = $env.EMBERS_WEBHOOK_SECRET;

const headers = $input.first().json.headers;
const rawBody = await this.helpers.getBinaryDataBuffer(0, 'data');

const id = headers['webhook-id'];
const timestamp = headers['webhook-timestamp'];
const age = Math.abs(Date.now() / 1000 - Number(timestamp));
if (!id || !timestamp || !(age <= TOLERANCE_SECONDS)) {
  throw new Error('Missing Embers headers or stale timestamp');
}

const key = Buffer.from(secret.replace(/^whsec_/, ''), 'base64');
const expected = crypto
  .createHmac('sha256', key)
  .update(`${id}.${timestamp}.`)
  .update(rawBody)
  .digest();

const verified = String(headers['webhook-signature'] || '')
  .split(' ')
  .some((entry) => {
    const received = Buffer.from(entry.split(',')[1] || '', 'base64');
    return received.length === expected.length && crypto.timingSafeEqual(received, expected);
  });

if (!verified) {
  throw new Error('Embers signature did not match');
}

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

If your instance blocks `$env` in the Code node, read the secret from wherever it keeps secrets and change that line. [Verify webhook signatures](/help/integrations/verify-webhook-signatures/) explains each part of the check.

### Deduplicate, then do the work

Delivery is at least once and retries can arrive out of order. Store each `id` (the same value as the `webhook-id` header) and skip events you have already processed. Join people on `data.lead.linkedin_urn`, falling back to `data.lead.id` when the URN is blank on an older record. Skip `webhook.test` events, which carry `data.is_test: true`, once you are live.

To build the rest of the workflow, send a test from Embers (**Send test** in the endpoint's **...** menu) and open that execution in n8n's executions list: it holds a sample shaped like the real event.

## What Embers sends

The payload is the same one every receiver gets. A lead event fires when the person qualifies: a score above zero, an ICP match or a manual approval, and a company that is not blocklisted. `data.lead.contact` appears on a paid Solo plan or above and carries only a stored email and phone, never a fresh lookup. The [webhook payload reference](/help/integrations/webhook-payload-reference/) lists every field.

Because the Webhook node answers before the Code node runs, a failed check or a workflow error never pauses the endpoint, and Embers does not retry it either. Watch n8n's own executions list for those.

## Troubleshooting

**The endpoint paused with HTTP 404.** You registered the Test URL, or the workflow is not active. n8n answers `404` for a webhook nothing is listening on, and a `404` pauses the endpoint at once. Activate the workflow, save the Production URL on the endpoint to clear the pause, and replay. See [a webhook endpoint is paused](/help/troubleshooting/webhook-endpoint-paused/).

**Every execution fails the signature check.** **Raw Body** is off, so the Code node has no `data` binary to hash, or the secret was rotated. Turn the option on, or copy the new secret.

**`require('crypto')` fails.** A self-hosted instance has not allowed the built-in module. Set `NODE_FUNCTION_ALLOW_BUILTIN=crypto` and restart n8n.

**Deliveries stopped after a few days.** n8n was unreachable or answering `5xx` for the whole [retry window](/help/integrations/webhook-retries-and-pauses/) of about 6.3 days, so the endpoint paused. Fix it, resume, and replay.

## Related

- [Verify webhook signatures](/help/integrations/verify-webhook-signatures/): The signature scheme in full.

- [Webhook payload reference](/help/integrations/webhook-payload-reference/): Every field in every event.

- [A webhook endpoint is paused](/help/troubleshooting/webhook-endpoint-paused/): Resume after a wrong URL or an inactive workflow.

## Frequently asked questions

**Is there an Embers node for n8n?**

No. n8n receives the standard signed webhook through its own Webhook node. Embers does not publish or support an n8n node.

**Why did my endpoint pause as soon as I saved it?**

The first delivery reached an n8n URL nothing was listening on, usually the Test URL or an inactive workflow, and n8n answered 404. A 404 pauses the endpoint after one attempt.
