Skip to content

Webhooks

A webhook is an HTTP callback configured at the account level. When something happens in ChannelX — a message is created, a conversation status changes — ChannelX sends a POST request to the URLs you've registered. You can set up several webhooks per account and choose which events each one listens for.

Adding a webhook

  1. Go to Settings → Integrations → Webhooks and click Configure.
  2. Click Add new webhook. In the dialog, enter the URL that should receive the POST request and select the events you want to subscribe to. Subscribing only to relevant events keeps your endpoint from being flooded with noise.

ChannelX then sends a POST request to that URL whenever a subscribed event fires.

A sample payload

{
  "event": "message_created",
  "id": "1",
  "content": "Hi",
  "created_at": "2020-03-03 13:05:57 UTC",
  "message_type": "incoming",
  "content_type": "text",
  "content_attributes": {},
  "source_id": "",
  "sender": {
    "id": "1",
    "name": "Agent",
    "email": "agent@example.com"
  },
  "contact": {
    "id": "1",
    "name": "contact-name"
  },
  "conversation": {
    "display_id": "1",
    "additional_attributes": {
      "browser": {
        "device_name": "Macbook",
        "browser_name": "Chrome",
        "platform_name": "Macintosh"
      }
    }
  },
  "account": {
    "id": "1",
    "name": "ChannelX"
  }
}

message_type is incoming (from the visitor), outgoing (from an agent), or template. content_type can be text, input_select, cards, or form.

Objects

An event payload is built from a small set of objects. The core ones are:

  • Accountid, name.
  • Inboxid, name.
  • Contactid, name, avatar, type, and the parent account.
  • User (agent/admin) — id, name, email, type.
  • Conversationid, inbox_id, status, can_reply, channel, unread_count, the contact_inbox link, an array of messages, and a meta block with the sender and assignee. It also carries additional_attributes (browser, referer, initiated_at) and seen-timestamps.
  • Messageid, content, message_type, content_type, content_attributes, private, source_id, plus the sender, account, conversation, and inbox.

Supported events

Event Fires when
conversation_created A new conversation is created.
conversation_updated Any conversation attribute changes. The payload includes changed_attributes with the current and previous values.
conversation_status_changed A conversation's status changes. Not supported when using agent bot APIs instead of webhooks.
message_created A message is created in a conversation.
message_updated A message is updated.
webwidget_triggered The visitor opens the live-chat widget.
conversation_typing_on An agent starts typing. Use the is_private flag to tell a note apart from a customer reply.
conversation_typing_off An agent stops typing or leaves the conversation window.

Verifying webhooks

ChannelX signs every outgoing request so your server can confirm the payload genuinely came from ChannelX and wasn't altered in transit. The signing secret is shown once when the webhook is created and can be viewed again on the webhook's edit form.

Each request carries these headers:

  • X-ChannelX-Signature — the HMAC-SHA256 signature, prefixed with sha256=.
  • X-ChannelX-Timestamp — the Unix timestamp (seconds) when the request was signed.
  • X-ChannelX-Delivery — a unique delivery ID for the event, when available.

The signature is computed as:

sha256=HMAC-SHA256(webhook_secret, "{timestamp}.{raw_body}")

Where webhook_secret is the webhook's secret, timestamp is the value of the timestamp header, and raw_body is the raw JSON body exactly as received.

Verification steps

  1. Read X-ChannelX-Signature and X-ChannelX-Timestamp from the headers.
  2. Read the raw request body as bytes — do not parse and re-serialize it.
  3. Compute sha256=HMAC-SHA256(secret, "{timestamp}.{raw_body}").
  4. Compare your result against the received signature using a constant-time comparison.
  5. Optionally reject requests whose timestamp is too old, to block replay attacks.

Example (Python)

import hmac
import hashlib

def verify_signature(raw_body: bytes, timestamp: str, received_signature: str, secret: str) -> bool:
    message = f"{timestamp}.".encode() + raw_body
    expected = "sha256=" + hmac.new(secret.encode(), message, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, received_signature)

Equivalent implementations work the same way in Ruby (ActiveSupport::SecurityUtils.secure_compare), Node.js (crypto.timingSafeEqual), and Go (hmac.Equal).

Do the verification right

  • Always verify against the raw body. Parsing and re-serializing the JSON can reorder keys or change whitespace and will break the signature.
  • Always use a constant-time comparison. A naive == check leaks timing information that an attacker can exploit.
  • Reject requests older than about five minutes to mitigate replay attacks.
  • Store the webhook secret securely and never expose it in client-side code or logs.