Skip to content
Developers

Webhooks

PostNimbo posts to your address the moment a post goes live, so Zapier, Make, n8n or your own server can act on it.

Set one up

  1. 1Open Settings > Webhooks and add a name and a public https address.
  2. 2Pick the channels it is for, or none for every channel.
  3. 3Pick its events: published posts, leads a flow collects, finished flows.
  4. 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.

Set up Zapier, Make or n8n

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.

Node.js
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.published

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

Payload
[
  {
    "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.scheduled

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

Payload
[
  {
    "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.failed

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

Payload
[
  {
    "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.captured

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

Payload
{
  "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.completed

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

Payload
{
  "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": {}
  }
}

Building something with PostNimbo?

Tell us what you are making. We help with the API, review apps for the directory and add the endpoints integrations need.