Set one up
- 1Open Settings > Webhooks and add a name and a public https address.
- 2Pick the channels it is for, or none for every channel.
- 3Pick its events: published posts, leads a flow collects, finished flows.
- 4Click Send test event to post a sample of an event to your address.
In Zapier, Make and n8n
The PostNimbo apps for Zapier, Make and n8n subscribe for you: turning a trigger on calls POST /public/v1/webhooks with the address the tool listens on, and turning it off calls DELETE /public/v1/webhooks/:id. Your own code can do the same with an API key.
How it is delivered
- A POST with a JSON body, and the event name in the header
X-PostNimbo-Event. - Your server has 10 seconds to answer a published post and 5 seconds to answer a flow event. A flow event is tried twice more after a timeout, a 429 or a 5xx answer; a published post is not retried. Answer quickly and do the work afterwards.
- Only public https addresses are accepted, and a redirect must also lead to a public address.
- Every delivery is signed with the secret shown in the webhook settings, in the headers
X-PostNimbo-Timestamp,X-PostNimbo-Signature.
Check a delivery came from us
Compute an HMAC-SHA256 of the timestamp, a full stop and the raw body with your secret, compare it with the signature header in constant time, and refuse a timestamp more than 5 minutes old.
const crypto = require('crypto');
// rawBody: the request body exactly as received, before JSON parsing
function fromPostNimbo(rawBody, headers, secret) {
const timestamp = headers['x-postnimbo-timestamp'];
const signature = headers['x-postnimbo-signature'] || '';
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
const expected =
'sha256=' +
crypto
.createHmac('sha256', secret)
.update(timestamp + '.' + rawBody)
.digest('hex');
return (
signature.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))
);
}Events
The same list is served at GET /public/v1/webhooks/events, so a tool can show the triggers and sample data.
post.publishedA post went live on a channel. Sent once per channel, after the network accepted it, with the post in the array.
Webhooks with no channels get it for every channel; the others only for the channels they list.
[
{
"id": "5b9c3f0e-6d0a-4a59-9b8e-2f1c7a4d2e11",
"content": "<p>Our autumn menu is here.</p>",
"publishDate": "2026-10-26T09:30:00.000Z",
"releaseURL": "https://www.linkedin.com/feed/update/urn:li:share:7000000000000000000",
"state": "PUBLISHED",
"integration": {
"id": "c1d2e3f4-0000-4a00-8000-000000000001",
"name": "Example Bakery",
"providerIdentifier": "linkedin-page",
"picture": "https://example.com/avatar.png",
"type": "social"
}
}
]post.scheduledA post was queued to publish: scheduled (or published now), rescheduled, approved, or edited while scheduled. Sent once per channel, with the same fields as post.published. The next posts of a repeating post do not send it.
Webhooks with no channels get it for every channel; the others only for the channels they list.
[
{
"id": "5b9c3f0e-6d0a-4a59-9b8e-2f1c7a4d2e11",
"content": "<p>Our autumn menu is here.</p>",
"publishDate": "2026-10-26T09:30:00.000Z",
"releaseURL": null,
"state": "QUEUE",
"integration": {
"id": "c1d2e3f4-0000-4a00-8000-000000000001",
"name": "Example Bakery",
"providerIdentifier": "linkedin-page",
"picture": "https://example.com/avatar.png",
"type": "social"
}
}
]post.failedA post could not be published, or its publishing could not be confirmed, and is marked as failed. Sent once per channel, with the same fields as post.published (the reason is shown in PostNimbo).
Webhooks with no channels get it for every channel; the others only for the channels they list.
[
{
"id": "5b9c3f0e-6d0a-4a59-9b8e-2f1c7a4d2e11",
"content": "<p>Our autumn menu is here.</p>",
"publishDate": "2026-10-26T09:30:00.000Z",
"releaseURL": null,
"state": "ERROR",
"integration": {
"id": "c1d2e3f4-0000-4a00-8000-000000000001",
"name": "Example Bakery",
"providerIdentifier": "linkedin-page",
"picture": "https://example.com/avatar.png",
"type": "social"
}
}
]lead.capturedAn automation flow saved an email or phone number someone typed in answer to a question. Sent once per answer.
Webhooks with no channels get it for every channel's flows; the others only for the flows on the channels they list.
{
"event": "lead.captured",
"timestamp": "2026-10-26T09:30:00.000Z",
"runId": "7d3f2a10-5c4e-4f8a-9a61-0e2b3c4d5e6f",
"flow": {
"id": "2a9e7c1b-3d4f-4e5a-8b6c-7d8e9f0a1b2c",
"name": "Autumn menu giveaway"
},
"channel": {
"id": "c1d2e3f4-0000-4a00-8000-000000000001",
"network": "instagram",
"name": "Example Bakery"
},
"contact": {
"id": "4f5e6d7c-8b9a-4c0d-9e1f-2a3b4c5d6e7f",
"name": "Sam Taylor",
"username": "samtaylor",
"email": "sam@example.com",
"phone": "+447700900123",
"tags": [
"giveaway"
],
"fields": {
"postcode": "SW1A 1AA"
}
}
}flow.completedSomeone reached the end of an automation flow. Sent once per run, with the same fields as lead.captured (email and phone are null when not collected).
Webhooks with no channels get it for every channel's flows; the others only for the flows on the channels they list.
{
"event": "flow.completed",
"timestamp": "2026-10-26T09:31:00.000Z",
"runId": "7d3f2a10-5c4e-4f8a-9a61-0e2b3c4d5e6f",
"flow": {
"id": "2a9e7c1b-3d4f-4e5a-8b6c-7d8e9f0a1b2c",
"name": "Autumn menu giveaway"
},
"channel": {
"id": "c1d2e3f4-0000-4a00-8000-000000000001",
"network": "instagram",
"name": "Example Bakery"
},
"contact": {
"id": "4f5e6d7c-8b9a-4c0d-9e1f-2a3b4c5d6e7f",
"name": "Sam Taylor",
"username": "samtaylor",
"email": "sam@example.com",
"phone": null,
"tags": [
"giveaway"
],
"fields": {}
}
}