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 alwaysmain
)conversation_uuid
- A unique UUID of the conversationchannel
- An object containing{type: string}
, where the string is something likewhatsapp
,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. - Conversation tags - receive all
conversation_tags
messages. - Conversation is closed - receive all
conversation_close
messages. - User changes - receive all
user_changes
messages. - Calendar event changes - receive all
calendar_event
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 eachaction
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": {},
}
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
orchannel
property. To identify the user between events, auser_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.
- after 1 second.
- after 15 seconds.
- after 1 minute.
- after 5 minutes.
- after 15 minutes.
- after 30 minutes.
- after 2 hours.
- after 6 hours.
- after 1 day.
- 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.