Webhooks API
A webhook is a URL UseNadia POSTs to when something happens: a post publishes, a comment or a message arrives, an automation fires or collects an email. The Webhooks tab of Settings is a person creating one by hand; these routes are for a machine that registers and unregisters its own URL — the n8n trigger node does exactly that. What the deliveries look like, and how to build the receiver, is on Webhooks in the guide. These routes do not exist in the original project.
GET /webhooks/events the event catalogueGET /webhooks your endpointsPOST /webhooks register one (the secret is returned here, once)GET /webhooks/{id}DELETE /webhooks/{id}POST /webhooks/{id}/test one signed sample delivery, to check the endpointThe events
Section titled “The events”GET /webhooks/eventsResponse 200, { "events": [ { "name", "description" } ] }:
| Event | When |
|---|---|
post.published |
A post was published on a channel |
comment.received |
A comment arrived on a post of a connected channel |
dm.received |
A direct message arrived on a connected channel |
automation.triggered |
A comment matched an automation and the reply was sent |
automation.email_captured |
An automation recorded the email address of the person it is talking to |
Register an endpoint
Section titled “Register an endpoint”POST /webhooks| Field | Type | Required | Description |
|---|---|---|---|
name |
string |
Yes | For the Settings list |
url |
string |
Yes | Public HTTPS URL. Private network addresses are refused |
events |
string[] |
No | Names from the catalogue, no duplicates. Omitted: every event |
integrations |
[{ id }] |
No | Only these channels. Omitted: every channel |
id |
string |
No | Your own id for the endpoint |
{ "name": "n8n — leads", "url": "https://n8n.example.com/webhook/abc123", "events": ["automation.email_captured"], "integrations": [{ "id": "cm4ean69r0003w8w1cdomox9n" }]}Response 201:
{ "id": "cm4wh0001", "name": "n8n — leads", "url": "https://n8n.example.com/webhook/abc123", "events": "automation.email_captured", "hasSecret": true, "secret": "whsec_…", "createdAt": "2026-10-20T09:30:00.000Z", "updatedAt": "2026-10-20T09:30:00.000Z"}events comes back as a comma-separated string, null or empty meaning every
event.
List, read, delete
Section titled “List, read, delete”GET /webhooks → [ { id, name, url, events, hasSecret, createdAt, updatedAt } ]GET /webhooks/{id} → the same objectDELETE /webhooks/{id} → { "id": "cm4wh0001" }404 when the endpoint is not yours.
Send a test delivery
Section titled “Send a test delivery”POST /webhooks/{id}/testSends one delivery to this endpoint only, without waiting for a real
event: the same envelope, headers and signature as a real one, with
"test": true added to the envelope and data a realistic sample of the event
(fake test_… ids — nothing was published). The body is optional:
| Field | Type | Required | Description |
|---|---|---|---|
event |
string |
No | The event to simulate. Omitted: the endpoint’s first event, or post.published when it receives every event |
The call waits for your endpoint — one attempt, no retry, 10-second timeout — and returns what it answered:
{ "delivered": true, "status": 200, "event": "post.published", "deliveryId": "0f5b2c1e-…" }status is null when nothing answered (timeout, DNS, connection refused).
404 when the endpoint is not yours; 400 when the event is not in the
catalogue or the endpoint is not subscribed to it. A receiver that must not
act on a test checks test in the envelope: real deliveries never carry it.
Verifying a delivery
Section titled “Verifying a delivery”Every delivery is a JSON envelope { id, event, occurredAt, organizationId, data }
with four headers:
| Header | Content |
|---|---|
X-Nadia-Event |
The event name |
X-Nadia-Delivery |
The delivery id, to deduplicate |
X-Nadia-Timestamp |
Unix seconds at sending time |
X-Nadia-Signature |
sha256= followed by the hex HMAC-SHA256, keyed with the secret, of <timestamp>.<raw body> |
Signing the timestamp together with the body is what stops a captured delivery from being replayed later with its signature still valid: check the signature, then refuse a timestamp too far from your clock.
const expected = 'sha256=' + crypto .createHmac('sha256', secret) .update(`${req.headers['x-nadia-timestamp']}.${rawBody}`) .digest('hex');A delivery is retried 3 times with a 10-second timeout each, except on a
4xx other than 429: that means the receiver rejected the payload itself,
and sending it again would change nothing. Answer 2xx quickly and do the
work afterwards.