Webhook events reference

⚡ New — Available in PR #733

This page is the reference for all five webhook event types. For the how, see Connect Zapier or Connect Make.

Common envelope

Every event has the same envelope structure:

{
  "event_id": "evt_8f3a...",
  "event_type": "lead.captured",
  "occurred_at": "2026-05-01T15:23:00Z",
  "organization_id": "org_abc",
  "chatbot_id": "bot_xyz",
  "data": {
    /* event-specific payload */
  }
}

Headers on every delivery:

  • Content-Type: application/json
  • User-Agent: Hilal-Chatbot-Webhooks/1.0
  • X-Webhook-Signature: t=<unix>,v1=<hex> — HMAC-SHA256 of t.body.
  • X-Webhook-Event-Id: evt_8f3a... — the same event_id as in the body.

The signature header lets you verify the payload came from Hilal Chatbot. See Verifying signatures below.

lead.captured (fully wired)

Fires when a user provides identifying information during a chat (email, phone, name).

data payload:

{
  "lead_id": "lead_123",
  "conversation_id": "conv_456",
  "email": "user@example.com",
  "name": "Jane Doe",
  "phone": "+15551234567",
  "captured_via": "user-info-form"
}

Captured fields vary — only the ones you actually collected appear.

conversation.started (coming soon)

Fires when a new conversation begins, on any channel.

data payload:

{
  "conversation_id": "conv_456",
  "channel": "web",
  "user_identity": {
    "identitySource": "verified",
    "sub": "user_789",
    "email": "user@example.com"
  }
}

user_identity reflects JWT verification status if a token was passed; anonymous otherwise.

conversation.ended (coming soon)

Fires when a conversation closes — auto-closed by inactivity, manually closed by an agent, or resolved by the bot.

data payload:

{
  "conversation_id": "conv_456",
  "duration_seconds": 312,
  "message_count": 8,
  "status": "resolved",
  "csat_score": 5
}

escalation.triggered (coming soon)

Fires when the bot hands off to a live agent — either because the bot’s confidence dropped below the escalation threshold, the user explicitly asked, or an agent took over manually.

data payload:

{
  "conversation_id": "conv_456",
  "reason": "low_confidence",
  "assigned_agent_id": null
}

assigned_agent_id is filled when an agent picks up the conversation; for the initial escalation, it’s null.

message.received (coming soon — high volume)

Fires for every user message. Use carefully — a busy chatbot can produce thousands of these per hour.

data payload:

{
  "conversation_id": "conv_456",
  "message_id": "msg_789",
  "role": "user",
  "content": "How do I reset my password?",
  "channel": "web"
}

If you only need first messages, subscribe to conversation.started instead.

Verifying signatures

Every delivery includes X-Webhook-Signature: t=<unix-timestamp>,v1=<hex-signature>.

To verify in Node.js:

const crypto = require('crypto')

function verifyWebhookSignature(rawBody, signatureHeader, secret) {
  const [tPart, v1Part] = signatureHeader.split(',')
  const t = tPart.split('=')[1]
  const v1 = v1Part.split('=')[1]

  // Reject stale signatures (5 min skew window)
  const now = Math.floor(Date.now() / 1000)
  if (Math.abs(now - parseInt(t, 10)) > 300) return false

  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${t}.${rawBody}`)
    .digest('hex')

  return crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected))
}

The secret is shown once when you create the webhook — copy it and store in your env.

Retry behavior

If your endpoint returns non-2xx (or doesn’t respond within 15 seconds), Hilal Chatbot retries with exponential backoff:

  • 1st retry: 30 seconds later
  • 2nd retry: 2 minutes later
  • 3rd retry: 10 minutes later
  • After 3 failed retries: marked as failed; visible in the delivery log

For permanent failures (e.g., your endpoint is gone), pause the webhook in Integrations → Webhooks to stop retries.

Idempotency

Use the X-Webhook-Event-Id header to dedupe. Hilal Chatbot may deliver the same event twice (e.g., if a 2xx response was lost in transit). Treat each event_id as processed-once.

HTTPS only

The webhook URL must be HTTPS. Hilal Chatbot rejects HTTP URLs at registration.

What’s next