Chat API Webhooks

This article describes why we use webhooks, what the body of the webhook requests look like and what responses Ultimate expects to these webhooks.

Why we use Webhooks

Ultimate Chat API leverages webhooks to enhance performance and makes the conversations more responsive. The Chat API responds with an empty 200 response to indicate that the request has been acknowledged and will be handled. The actual bot response will be sent as a separate webhook request.

The main reason to use this approach is that it allows the bot to be proactive in sending messages without needing a visitor message as an input. An example of this is sending a message after a timeout asking if the conversation can be closed. Another reason is that we can send consecutive bot messages as they happen without needing to group them together.

Chat API Webhook events

The Chat API webhook will receive events related to bot conversations. The events that will be received are:

  1. sendMessage - A message from the bot to the visitor. It could be a simple text message, a carousel of cards or a list of buttons.
  2. escalate - An escalation request from the bot to the CRM.
  3. isTeamOnline - When the bot needs to make decisions that are dependent on the availability of human agents.

In our template project, we have defined this webhook in the url /converse-webhook, but the implementation in a CRM can be different. Here is an example implementation using Typescript:

/**
* This method processes the Converse Webhook. Receives a DTO of type ConverseWebhookDto with the event details
*
* It will process the request differently depending on the eventType field of the payload.
*
* @param req
* @param res
* @param next
*/
public processEvent = async (req: Request, res: Response, next: NextFunction): Promise<void> => {
const requestBody: ConverseWebhookDto = req.body;
try {
this.converseWebhookService.processConverseWebhookEvent(requestBody);
res.status(200).send();
} catch (error: any) {
next(error);
}
};
}

sendMessage event

This will be the most common event that will be sent to the CRM. It represents a message from the bot to the visitor. It could be a simple text message, a carousel of cards or a list of buttons. The event should be acknowledged and processed ASYNCHRONOUSLY.

In our template project, in the ConverseWebhookService is where we can process this type of event. From here, it’s up to the CRM to execute logic on the message that it has received from the bot. It can directly forward it to the customer or process it before if needed.

/**
* This method processes the Converse Webhook event.
* @param converseWebhookDto
*/
public processConverseWebhookEvent(converseWebhookDto: ConverseWebhookDto): void {
switch (converseWebhookDto.data.eventType) {
case ConverseWebhookEventTypes.SEND_MESSAGE:
/**
* This event represents a message sent from the bot.
* It can represent a text message, a list of buttons or a carousel.
*
* The payload of this event is represented in the BotMessageEvent class.
*
* The request needs to be acknowledged and processed ASYNCHRONOUSLY.
*/
logger.info(
`Received ${ConverseWebhookEventTypes.SEND_MESSAGE} event: \n ${JSON.stringify(converseWebhookDto)}`
);
break;
default:
break;
}
}
}

Text message

The text message structure:

FieldTypeRequiredDescription
botIdstringtrueThe ID of the bot that is sending the event.
dataObjecttrueObject containing the payload of the event. Depending on the event type, it will contain different values

The data object will contain the following fields:

FieldTypeDescription
eventTypestring
Set to sendMessage
Event of type sendMessage.
platformConversationIdstringThe ID of the conversation
conversationIdstringULTIMATE internal ID (Can be ignored)
typestring
Set to text
Simple text message type.
replyIdstringThe ID of the reply in Ultimate system that triggered this text message.
textstringThe text of the message.
predictedIntentsPredictedIntent[]Predicted intents for visitor message. Each object of type PredictedIntent in the array needs to contain the following fields:
value string - the ID of the intent in Ultimate system
name string - name of the intent
confidence number - confidence in the intent

Example in JSON format:

{
"botId": "BOT_ID",
"data": {
"eventType": "sendMessage",
"platformConversationId": "13427e90-f76a-47c2-a26d-ea0b4f1836c5",
"conversationId": "CONVERSATION_ID",
"type": "text",
"replyId": "REPLY_ID",
"buttons": [],
"text": "Hello, this is a bot text message",
"carouselCards": [],
"predictedIntents": [{"value": "1234", "name": "intent name", "confidence": 1}]
}
}

List of buttons message

The list of buttons message structure:

FieldTypeRequiredDescription
botIdstringtrueThe ID of the bot that is sending the event.
dataObjecttrueObject containing the payload of the event. Depending on the event type, it will contain different values

The data object will contain the following fields:

FieldTypeDescription
eventTypestring
Set to sendMessage
Event of type sendMessage.
platformConversationIdstringThe ID of the conversation
conversationIdstringULTIMATE internal ID (can be ignored)
typestring
Set to text
Simple text message type.
replyIdstringThe ID of the reply in Ultimate system that triggered this text message.
buttonsButton[]The buttons array will be empty for a simple text message. Each object of type Button in the array needs to contain the following fields:
type string - set to the value button
text string - text of the button
textstringThe text of the message with buttons. Not mandatory to exist.
predictedIntentsPredictedIntent[]Predicted intents for visitor message. Each object of type PredictedIntent in the array needs to contain the following fields:
value string - the ID of the intent in Ultimate system
name string - name of the intent
confidence number - confidence in the intent

Example in JSON format:

{
"botId": "BOT_ID",
"data": {
"eventType": "sendMessage",
"platformConversationId": "13427e90-f76a-47c2-a26d-ea0b4f1836c5",
"conversationId": "CONVERSATION_ID",
"type": "text",
"replyId": "REPLY_ID",
"buttons": [
{
"type": "button",
"text": "YES"
},
{
"type": "button",
"text": "NO"
}
],
"text": "its a bot text message with 2 buttons",
"carouselCards": [],
"predictedIntents": [{"value": "1234", "name": "intent name", "confidence": 1}]
}
}

The carousel message structure:

FieldTypeRequiredDescription
botIdstringtrueThe ID of the bot that is sending the event.
dataObjecttrueObject containing the payload of the event. Depending on the event type, it will contain different values

The data object will contain the following fields:

FieldTypeDescription
eventTypestring
Set to sendMessage
Event of type sendMessage.
platformConversationIdstringThe ID of the conversation
conversationIdstringULTIMATE internal ID (can be ignored)
typestring
Set to carousel
Carousel message type.
replyIdstringThe ID of the reply in Ultimate system that triggered this text message.
textstringThe text of the message with buttons. Not mandatory to exist.
carouselCardsCarouselCard[]The carousel cards array will contain a list of cards. See table below with the fields of CarouselCards
predictedIntentsPredictedIntent[]Predicted intents for visitor message. Each object of type PredictedIntent in the array needs to contain the following fields:
value string - the intent id
name string - name of the intent
confidence number - confidence in the intent


Each object of type CarouselCard in the array needs to contain the following fields:

FieldTypeDescription
titlestringTitle of the carousel.
descriptionstringEvent of type sendMessage
imageUrlstringUrl of the card image.
buttonsButton[]Each object of type Button in the array needs to contain the following fields:
type string - set to the value button
text string - text of the button

Example in JSON format:

{
"botId": "BOT_ID",
"data": {
"eventType": "sendMessage",
"platformConversationId": "13427e90-f76a-47c2-a26d-ea0b4f1836c5",
"conversationId": "CONVERSATION_ID",
"type": "carousel",
"replyId": "REPLY_ID",
"buttons": [],
"text": "",
"carouselCards": [
{
"title": "card 1",
"description": "card 1 description",
"imageUrl": "URL",
"buttons": [
{
"text": "button 1",
"type": "button"
},
{
"text": "button 2",
"type": "button"
}
]
},
{
"title": "card 2",
"description": "card 2 description",
"imageUrl": "URL",
"buttons": [
{
"text": "button 1",
"type": "button"
},
{
"text": "button 2",
"type": "button"
}
]
}
],
"predictedIntents": [{"value": "1234", "name": "intent name", "confidence": 1}]
}
}

The response structure:

A successful sendMessage returns a 200 response to the webhook request and no response body.

A failed sendMessage returns a 4XX - 5XX range response to the webhook request.

escalate event

This event will happen when the bot triggers an escalation to an agent. When this event is received, the bot will stop sending messages to the visitor, and it will be the CRM’s responsibility to manage the escalated conversation between the agent and the visitor.

The ConverseWebhookService in our template project is where we process this type of event. From here, it’s up to the CRM to implement its logic internally, like checking agent availability, adding the customer to a queue or any other logic that needs to be implemented.

/**
* This method processes the Converse Webhook event.
* @param converseWebhookDto
*/
public processConverseWebhookEvent(converseWebhookDto: ConverseWebhookDto): void {
switch (converseWebhookDto.data.eventType) {
case ConverseWebhookEventTypes.ESCALATE:
/**
* This event represents an escalation request from the bot.
*
* The payload of this event is represented in the BotEscalationEvent class.
*
* The request will be processed SYNCHRONOUSLY and respond if the escalation was successful (200) or not (4xx-5xx).
*/
logger.info(
`Received ${ConverseWebhookEventTypes.ESCALATE} event: \n ${JSON.stringify(converseWebhookDto)}`
);
break;
default:
break;
}
}
}

The escalate message structure:

FieldTypeRequiredDescription
botIdstringtrueThe ID of the bot that is sending the event.
dataObjecttrueObject containing the payload of the event. Depending on the event type, it will contain different values

The data object will contain the following fields:

FieldTypeDescription
eventTypestring
Set to escalate
Event of type escalate.
platformConversationIdstringThe ID of the conversation
conversationIdstringULTIMATE internal ID (can be ignored)
escalateTo?stringThe ID of the escalation team within the CRM system (OPTIONAL)

Example in JSON format:

{
"botId": "BOT_ID",
"data": {
"eventType": "escalate",
"platformConversationId": "13427e90-f76a-47c2-a26d-ea0b4f1836c5",
"escalateTo": "string"
}
}

The response structure:

A successful escalation returns a 200 response to the webhook request and no response body.

A failed escalation returns a 4XX - 5XX range response to the webhook request.

isTeamOnline event

This event will happen when the bot needs to know if there is an agent team available. When this event is received, the request needs to be handled SYNCHRONOUSLY and respond if the team is available with boolean field isOnline set to true or false.

The ConverseWebhookService in our template project is where this event is processed. From here, it’s up to the CRM to implement its logic internally, like checking specific agent or group of agents availability

/**
* This method processes the Converse Webhook event.
* @param converseWebhookDto
*/
public processConverseWebhookEvent(converseWebhookDto: ConverseWebhookDto): void {
switch (converseWebhookDto.data.eventType) {
case ConverseWebhookEventTypes.IS_TEAM_ONLINE:
/**
* This event happens when the bot needs to make decisions that are dependent on the availability of human agents
*
* The payload of this event is represented in the BotIsTeamOnlineEvent class.
*
* The user will process the request and respond if there is availability or not using the class BotIsTeamOnlineEventResponse.
*/
logger.info(
`Received ${ConverseWebhookEventTypes.IS_TEAM_ONLINE} event: \n ${JSON.stringify(
converseWebhookDto
)}`
);
break;
default:
break;
}
}
}

The escalate message structure:

FieldTypeRequiredDescription
botIdstringtrueThe ID of the bot that is sending the event.
dataObjecttrueObject containing the payload of the event. Depending on the event type, it will contain different values

The data object will contain the following fields:

FieldTypeDescription
eventTypestring
Set to isTeamOnline
Event of type isTeamOnline.
conversationIdstringULTIMATE internal ID (can be ignored)
teamId?stringThe ID of the team to check (OPTIONAL)

Example in JSON format:

{
"botId": "BOT_ID",
"data": {
"eventType": "isTeamOnline",
"teamId": "string"
}
}

The response structure:

FieldTypeRequiredDescription
isOnlinebooleantrueReturns true if the team is online and false in other case
{
"isOnline": true
}

Action Webhook

For action webhook, refer to LINK-ARTICLE-HERE MOVE TO A DIFFERENT ARTICLE

Executing an action

TODO

As a result of a call to an action

The action execution could optionally return a response body of this format:

{
"results": [
{
"key": "newBalance",
"value": "1000"
},
{
"key": "status",
"value": "active"
},
{
"key": "userEmail",
"value": "user@mail.com",
"sanitize": true
}
]
}

These parameters will be stored in the conversation session and can be used in the dialog. For example, as other action parameters or to drive the dialog flow.