Skip to content

Webhooks

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 (= bot.id)
  • 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.

Supported Events

For each webhook, it is possible to filter which events are received. The following table shows the available event types and their descriptions:

Event Internal Name Description
All "" (empty string) Receive all different payloads
All actions action Receive all action messages
User actions user_action Receive only action messages from the user-side of the conversation
Operator actions operator_action Receive only action messages from the operator-side (e.g. bot messages)
Message delivery action_status Receive all action status update messages
Note events note Receive all note messages
Conversation tags conversation_tags Receive all conversation tags messages
Conversation is closed conversation_close Receive all conversation close messages
Conversation is deleted conversation_delete Receive all conversation delete messages
User changes user_changes Receive all user changes messages
Calendar event changes calendar_event Receive all calendar event messages
All task events task Receive all task-related messages
Specific task task.TASK_NAME Receive events for a specific task
Specific raw task data task.raw.TASK_NAME Receive minimal data for a specific task

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": [
      "online"
    ]
  },
}

Conversation close

{
  // ... shared fields, see above.
  "type": "conversation_close",
  "conversation_close": {
    "conversation_data": {},
    "user_data": {}
  },
}

Conversation delete

{
  // ... shared fields, see above.
  "type": "conversation_delete",
  "conversation_delete": {
    "conversation_data": {},
    "user_data": {}
  },
}

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.

Calendar event changes

Whenever a event is created, updated or deleted, the calendar_event event is emitted.

{
  // ... shared fields, see above.
  "type": "calendar_event_create" | "calendar_event_update" | "calendar_event_delete",
  // ISO8601 time at which the event was created.
  "event_created": "2024-01-23T15:09:50.126973Z",
  "calendar_event": {
    // ISO8601 time at which the event starts.
    "from": "2024-01-23T15:09:50.126973Z",
    // ISO8601 time at which the event ends.
    "until": "2024-01-23T15:09:50.126973Z",
    // Is the event an all-day event?
    "all_day": true | false,
    // The title of the event.
    "title": "string",
    // The description of the event.
    "description": "string",
    "metadata": {
      // Additional custom metadata for the event with key: value format.
    },
    // The location of the event.
    "location": "string",
    // The calendar the event is part of.
    "calendar": {
      // The bot ID of the calendar.
      "bot_id": "string",
      // The name of the calendar.
      "name": "string"
    }
  },
}

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 following timings.

  1. after 1 second.
  2. after 15 seconds.
  3. after 1 minute.
  4. after 5 minutes.
  5. after 15 minutes.
  6. after 30 minutes.
  7. after 2 hours.
  8. after 6 hours.
  9. after 1 day.
  10. after 2 days.

Notification of failures

When a webhook fails, the environment owner will receive an email notification with some data about the failed webhook. The notification occurs after 6 failed attempts.

Authorization

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:

34.91.172.214

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:

<?php

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 {
  http_response_code(401);
}
?>

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