Browse sections

Webhooks

Get an HTTP request on your server the moment a conversation, message or contact changes, and verify it came from us.

On this page

A webhook sends a POST request to a URL on your server whenever something you chose happens in your workspace, such as a new message. You do not need to keep asking the API for changes.

Add a webhook

You need the Administrator role.

  1. Open Settings, then Integrations.
  2. On the Webhooks card (1), select Configure (2).
  1. Select Add new webhook.
  2. Enter the Webhook URL (1). It must be a public https address on your server.
  3. Optionally, enter a Webhook Name (2) so you can tell your webhooks apart.
  4. Under Events (3), tick the events you want.
  5. Select Create webhook (4).
  6. The webhook's Secret appears. Copy it to your server's settings, then select Done. You can see it again later in the webhook's edit form.

Events

The name in the payload's event field, and its label in the form:

  • conversation_created: Conversation Created
  • conversation_status_changed: Conversation Status Changed (open, resolved, pending, snoozed)
  • conversation_updated: Conversation Updated (for example labels, assignee or attributes)
  • message_created: Message created (incoming and outgoing messages, and private notes with "private": true; activity lines are not sent)
  • message_updated: Message updated (for example a delivery status change)
  • webwidget_triggered: Live chat widget opened by the user
  • contact_created: Contact created
  • contact_updated: Contact updated
  • conversation_typing_on and conversation_typing_off: Conversation Typing On / Off

What arrives

The body is JSON, sent with Content-Type: application/json. It always has event and an account object; the rest depends on the event. A shortened message_created:

{
  "event": "message_created",
  "id": 9876,
  "content": "Where is my order?",
  "message_type": "incoming",
  "private": false,
  "created_at": "2026-10-29T08:15:02Z",
  "account": { "id": 12, "name": "My Store" },
  "inbox": { "id": 29, "name": "Store WhatsApp" },
  "sender": { "id": 42, "name": "Sara", "phone_number": "+966500000000" },
  "conversation": { "id": 123, "status": "open", "labels": [], "meta": { "assignee": null } }
}
  • conversation.id is the number the inbox shows, the same one the API uses.
  • account.id here is the workspace's numeric ID, not the acc_ ID in your URL.
  • The …_updated and conversation_status_changed events add changed_attributes, with each changed field's previous and current value.

Verify the signature

Every request carries three headers:

  • X-Hadir-Delivery: a unique ID for this delivery.
  • X-Hadir-Timestamp: when it was sent, in Unix seconds.
  • X-Hadir-Signature: sha256= followed by the HMAC SHA256, in hex, of the timestamp, a dot, and the raw body, keyed with the webhook's secret.

Compute the same value on your server from the raw body, before parsing it, and compare. In Node.js:

const crypto = require('crypto');

function isFromHadir(rawBody, headers, secret) {
  const ts = headers['x-hadir-timestamp'];
  const expected = 'sha256=' + crypto.createHmac('sha256', secret)
    .update(`${ts}.${rawBody}`).digest('hex');
  const given = String(headers['x-hadir-signature'] || '');
  const fresh = Math.abs(Date.now() / 1000 - Number(ts)) < 300;
  return fresh && given.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(given), Buffer.from(expected));
}

In PHP: hash_equals('sha256=' . hash_hmac('sha256', $ts . '.' . $rawBody, $secret), $signature). Rejecting old timestamps, as above, stops someone from replaying a captured request.

Timeouts and retries

  • Your server has 5 seconds to answer. Reply with any 2xx quickly, and do the slow work afterwards.
  • A delivery that times out or gets a non-2xx answer is not retried, and the dashboard does not show failed deliveries. If you must not miss anything, also check the API from time to time.
  • Deliveries can arrive out of order. Use created_at and the IDs, not arrival order.
Note: webhooks keep working even if your plan later stops including the API; only adding or editing them is refused. Delete a webhook you no longer need from the same page.

Was this helpful?

Thanks for your feedback ✓

Please don't include personal details

We got your note and will review the article with it in mind.

To see how this site uses cookies, read our Privacy Policy