Skip to content


As soon as the Rest API is enabled, it is possible to set up webhooks to receive events and message whenever an event in the bot happens.

Each webhook endpoint that is added to the bot will receive POST requests whenever an event in the bot happens, e.g. when the bot sends a message or the user sends a message, or when the message status changes.

Webhook endpoints can be added manually through the Studio UI or programmatically by using the Webhooks REST endpoint.

Message structure

A webhook JSON message always contains the following generic fields:

  • type - The type of the webhook event.
  • event_created - The ISO8601 time at which the event was created.
  • bot_id - The ID of the bot (=
  • user_id - The ID of the user (= user.user_id)
  • conversation_id - The channel-specific ID of the conversation (in case of whatsapp this is always main)
  • conversation_uuid - A unique UUID of the conversation
  • channel - An object containing {type: string}, where the string is something like whatsapp, phone, etc.

Depending on the event, the object will contain an additional field with a payload of that event type. These are listed below.

Filtering events

For each webhook, it is possible to set up what kind of messages are received on the webhook:

  • All - receive all different payloads
  • All actions - receive all action messages
  • User actions - receive only action messages that the user-side of the conversation is sending
  • Operator actions - receive only action messages that the operator-side of the conversation is sending (e.g. bot messages)
  • Message delivery - receive all action_status update messages.
  • Note events - receive all note messages.
  • User changes - receive all user_changes messages.

Action events

When the bot or the user sends an action, next to the generic fields this JSON object will also contain an action field.

The POST payload for the webhook request will look something like this:

  // generic fields
  "bot_id": "17fa89f0-d3c4-4cd4-bd9c-b4da2c0a9099",
  "conversation_id": "main",
  "conversation_uuid": "f5087c2d-6208-4614-a88f-34ea13e5845f",
  "user_id": "d9d9470-0f94-4912-925a-50f5d7b6321a",
  "channel": {
    "type": "whatsapp"

  // the actual action (chat event) that is sent
  "action": {
    "delay": 1000,
    "id": "6ca85481-c5df-4b7f-912d-164f8e1905b4",
    "payload": {
      "message": "Hi there 👋 "
    "time": "2021-04-12T12:38:04.475085Z",
    "type": "text"

The Chat Message Specification contains more information on how a chat action can look.

Message delivery notifications

Some channels provide information on the status change of sent messages, for instance, whether messages have been delivered to the user, whether it has been read, etc.

When the status of an action changes, next to the generic fields this JSON object will also contain an action_status field:

  // shared fields between all webhook messages
  "bot_id": "17fa89f0-d3c4-4cd4-bd9c-b4da2c0a9099",
  "conversation_id": "main",
  "conversation_uuid": "f5087c2d-6208-4614-a88f-34ea13e5845f",
  "user_id": "d9d9470-0f94-4912-925a-50f5d7b6321a",
  "channel": {
    "type": "whatsapp"

  // the status of the given action is changed:
  "action_status": {
    "id": "6ca85481-c5df-4b7f-912d-164f8e1905b4",
    "status": "delivered"

Currently, the action_status event for the following channels (click to see the documentation for the status values):

When the delivery status status is "failed", there is an extra property available called "error_message", which contains details about the reason why the message could not be sent.

When retrieving the Conversation history, the delivery status is available in the delivery_status field on each action object of the result:

  "delivery_status": "read",
  "id": "gBGGMWQTIlmfAgmAj1ghwchVbdY",
  "payload": {
    "message": "Hello"
  "time": "2022-04-04T09:27:02.521097Z",
  "type": "text"

Note events

Whenever a note is created or updated, the note event is emitted.

  // ... shared fields, see above.
  "type": "note_create" | "note_update" | "note_delete",
  "note": {
    "id": "7d3931d6-485a-496f-8baf-25425c551096",
    "tags": ["hello-world", "another-tag"],
    "status": "new" | "done",
    "note": "note text",
    "conversation_data": {
      // snapshot of keys that were set (remembered) in the conversation.
    "user_data": {
      // snapshot of the person we are holding a conversation with.

Conversation tags events

Whenever a tag is added or removed in a conversation, the tag event is emitted.

  // ... shared fields, see above.
  "type": "conversation_tags",
  "conversation_tags": {
    // the list of tags that were added
    "added": ["online"],
    // the list of tags that were removed
    "removed": [],
    // the full list of tags that are currently set on the conversation
    "tags": [

CRM User changes events

Whenever a CRM user is created, updated or deleted a user changes event is emitted.

  "bot_id": "571a8374-9a25-45c1-90a2-5dfb4ee46ad8",
  "event_created": "2023-01-23T15:09:50.126973Z",
  "user_uuid": "27be5cc1-923c-497e-bec7-1fdffe5fdbb3",
  "type": "user_create" | "user_update" | "user_delete",
  "user_changes": {
    "changes": {
      // The old values before they were updated. Example:
      "timezone": "Europe/Amsterdam"
      // This object is empty for "user_create" and "user_delete" events.
    "user": {
      // The full user object containing the current state of the user.
      "first_name": "John",
      "last_name": "Doe",
      "alias": "string",
      "profile_picture": "url",
      "timezone": "Europe/Berlin",
      "locale": "nl" | "en_US" | ...,
      "tags": ["list of strings"],
      "user_data": {
        // ... additional custom fields, based on the user data set in Bubble.

User changes are a bit different from other webhooks as they are not directly related to a single conversation. Therefore, they do not have the same shared fields as the other webhook events; specifically they do not have the user_id, conversation_id or channel property. To identify the user between events, a user_uuid property is sent along.

Retry policy

Outbound webhook calls are retried by the DialoX platform whenever the webhook fails in one of the following ways:

  • The webhook URL returns a HTTP status code of 408, 409 or 429
  • The webhook URL returns a HTTP status code >= 500
  • The request fails with a network error (DNS issue or connection timeout). The request timeout is 5 seconds.

Each webhook is retried for a maximum of 10 times, with exponential backoff.


It is possible to add a bearer token to each webhook in order have it call an otherwise secured endpoint.

IP Address

All outgoing HTTP requests made by the DialoX platform, including webhook requests and requests done from Bubblescript, are always done from the following IP address:

Please add this IP address to your firewalls if this is required.

Signature verification

For the receiving party to be able to verify that the webhook payload is received from the DialoX platform, and is not being spoofed in some way, a X-Body-Signature HTTP header is sent along with the request.

It contains the HMAC/SHA256 signature of the request body, with the secret key used for calculating the signature being the bot's REST API token.

Example code

The following PHP code verifies that the incoming request body is signed with the API token:


define('API_SECRET_KEY', 'bot_rest_api_token');

function verify_webhook($data, $hmac_header)
    # Calculate HMAC
  $calculated_hmac = base64_encode(hash_hmac('sha256', $data, API_SECRET_KEY, true));
  return hash_equals($hmac_header, $calculated_hmac);

# Extract the signature header
$hmac_header = $_SERVER['X-Body-Signature];

# Get the raw body
$data = file_get_contents('php://input');

# Compare HMACs
$verified = verify_webhook($data, $hmac_header);

error_log('DialoX webhook verified: '.var_export($verified, true));
if ($verified) {
  # Do something with the webhook

} else {

See the Hookdeck website for more background information and examples in other programming languages.