Skip to content

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 catalogue
GET /webhooks your endpoints
POST /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 endpoint
GET /webhooks/events

Response 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
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.

GET /webhooks → [ { id, name, url, events, hasSecret, createdAt, updatedAt } ]
GET /webhooks/{id} → the same object
DELETE /webhooks/{id} → { "id": "cm4wh0001" }

404 when the endpoint is not yours.

POST /webhooks/{id}/test

Sends 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.

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.