Consume Embers webhooks in n8n
A community recipe: point a signed Embers endpoint at an n8n Webhook node, verify the HMAC in a function node, then run your own workflow.
n8n can receive Embers leads, but nothing about it is an Embers feature. This is a community recipe: a generic n8n Webhook node listening to the same signed HTTPS webhook that Zapier, Make, Clay, and hand-written receivers use. Embers does not build it, test it, or support it.
Before you start
- An n8n instance, cloud or self-hosted, reachable over public HTTPS on port 443.
- The production webhook URL from your n8n Webhook node, not the test URL, if you want deliveries to keep working after the editor closes.
- The
whsec_signing secret from the Embers endpoint.
Set up the workflow
-
Add a Webhook node
Create a workflow with a Webhook node set to accept
POST. Take the HTTPS URL it gives you.Configure the node so your function can read the raw request body. The signature is computed over the exact bytes Embers sent, so a body that has been parsed and re-serialised will not match.
-
Register the URL in Embers
In the Embers app, open Developer, then Webhooks (
/developer/webhooks) and add the n8n URL as an endpoint. Public HTTPS on port 443 only, and a URL with a#fragment is rejected because browsers never send fragments in an HTTP request. Redirects are not followed.Copy the signing secret shown when the endpoint is created and store it as an n8n credential or environment variable.
-
Subscribe to the events you want
Subscribe to
lead.createdfor one event per newly qualified lead.lead.engagement.addedis the highest-volume event and is off by default on new endpoints.lead.status_changedandreport.generatedare there when you need them. -
Verify the signature in a function node
Put a function node immediately after the Webhook node and reject anything that fails. Embers sends Standard Webhooks headers:
webhook-id,webhook-timestamp(Unix seconds), andwebhook-signaturein the formv1,<base64 HMAC-SHA256>. The signature covers{webhook-id}.{webhook-timestamp}.{raw body}.const crypto = require('crypto'); const headers = $input.first().json.headers; const rawBody = $input.first().json.body; // the exact string Embers sent const secret = Buffer.from($env.EMBERS_WEBHOOK_SECRET.replace('whsec_', ''), 'base64'); const id = headers['webhook-id']; const timestamp = headers['webhook-timestamp']; const age = Math.abs(Date.now() / 1000 - Number(timestamp)); if (!id || !timestamp || age > 300) { throw new Error('Missing headers or stale timestamp'); } const expected = crypto .createHmac('sha256', secret) .update(`${id}.${timestamp}.${rawBody}`) .digest('base64'); const ok = String(headers['webhook-signature'] || '') .split(' ') .some((part) => { const value = part.split(',')[1]; if (!value || value.length !== expected.length) return false; return crypto.timingSafeEqual(Buffer.from(value), Buffer.from(expected)); }); if (!ok) { throw new Error('Bad signature'); } return [{ json: JSON.parse(rawBody) }];Compare in constant time, and treat the
webhook-signatureheader as a space separated list so a secret rotation with two valid signatures does not lock you out. -
Deduplicate, then do the work
Delivery is at least once and retries can arrive out of order. Store
webhook-idand drop anything you have already processed. Join people ondata.lead.linkedin_urn, falling back todata.lead.idwhen the URN is blank on an older record.Answer with a
2xxquickly. Any2xxis accepted, so acknowledge first and run the slow branches after.
What Embers sends
The payload is identical to every other receiver. A lead is delivered when its score is above zero, it matched your ICP or you approved it manually, and its company is not blocklisted. data.lead.contact is present on Solo and above and carries only stored email and phone, never a fresh lookup. The full field list is in the webhook payload reference.
Troubleshooting
Every request fails the signature check. Almost always the body. n8n must hand your function the raw string, not a re-encoded object. Compare the length of your computed value against the header before you look anywhere else.
Deliveries stopped after a few days. Embers retries network errors, 408, 425, 429, and 5xx seven times, with the last attempt about 6.3 days after the first. Other 4xx responses are terminal. Either way the endpoint pauses. See Webhook endpoint is paused.
Test URL, not production URL. An n8n test URL only listens while the editor is open. Register the production URL.
Related
Was this article helpful?
What went wrong?
Thank you. We read every answer.
Still stuck?
Send us the article you were following and what happened. We answer from the same inbox that writes these pages.