Messaging protocol & Bot side API's
Synchronous and asynchronous channels
Depending on channel specifics, some of them requires to gather all responses synchronously. The good example is Google assistant.
- synchronous channels - requires replies in a webhook's response body and has limited or none support for asynchronous replies
- asynchronous channels - allows replies in a webhook's response body and supports asynchronous replies
Bot Applications webhook
Application's webhook API
POST
/bot- bot application webhookOrchestrator uses this API to notify bot about new messaging events
Webhook data
The request can contain more messaging events because of optimization of execution - it lowers a number of sent requests.
entry: (object[]) - list of messaging event groupsentry.id: (string) - ID of messaging group[entry.requires_response]: (boolean) - if true, there's no way to respond the conversation asynchronously using the /send APIentry.app_id: (string) - application to which events are sent toentry.messaging: (object[]) - list of messaging events direcly passed to applicationentry.standby: (object[]) - list of messaging events passed to application in standby mode
There are either messaging or standby events. Never both in same time.
{"entry":[{"id":"<CHANNEL_ID>","requires_response": true, // optional"app_id": "1234","messaging": [ // or "standby"...list of messaging events at specific channel]}]}
Event sent to a bot application
sender.id: (string) - identification of the senderrecipient.id: (string) - identifies a channeltimestamp: (number) - when the event was emitted (taken from the original event)mid: (string) - unique identification of the event (given by the orchestrator)features: (string[]) - supported channel features (text,ssml,voice,phrases,tracking)
{"sender": {"id": "<USER_ID>"},"recipient": {"id": "<CHANNEL_ID>"},"timestamp": 1458692752478,"mid": "<unique event identification>","features": ["text"]...event contents}
Responding to webhook's event
If the channel requires to respond synchronously (the requires_response is set to true), the bot's webhook
has to send responses back to the orchestrator immediatelly.
- for each
entryobject from webhook there should be a corresponding object in response - each messaging event could have corresponding response object between responses
- messaging events in a single response are processed sequentionally - so this makes able to insert delays between each response
{"entry": [{"id": "<CHANNEL_ID>","responses": [{"response_to_mid": "<original message id>","messaging": [...list of responses]}]}]}
Sending responses to Orchestrator's API
At channels, that supports asynchronous communication, there's possibility to send responses also after dispatching the webhook's event.
- POST
/webhook/api- when acting as an application - POST
/webhook/api/<CHANNEL_ID>- when acting as a channel
Event sent by bot as a response
response_to_mid: (string) - identifies the source messaging event for aggregation purposesexpected: (object) - optional channel guidace for expected user responseexpected.input: (object) - input type guidanceexpected.input.type: (string) - type of the inpupt (password,none,upload)expected.phrases: (string[]) - list of expected phrases (when supported by voice channel)expected.entities: (string[]) - list of expected entities (when supported by voice channel)
{"recipient":{"id":"<USER_ID>"},"sender":{"id":"<CHANNEL_ID>"},"response_to_mid": "source event identification","expected": { // optional"input": {"type": "password"}}...response contents}
Messaging event types
Text message
message.text: (string) - required text message
incomming event
{"sender":{"id":"<USER_ID>"},"recipient":{"id":"<CHANNEL_ID>"},"timestamp":1458692752478,"mid": "<unique event identification>","message":{"text":"hello, world!"}}
responding to user
{"recipient":{"id":"<USER_ID>"},"sender":{"id":"<CHANNEL_ID>"},"response_to_mid": "source event identification","message":{"text":"hello, world!"}}
Responding with an alternative content for voice
incomming event: not available
responding to user
message.voice: (object) - optinal alternative for voice interface[message.voice.voice]: (string) - voice name[message.voice.language]: (string) - voice language[message.voice.ssml]: (string) - optional SSML for providing better voice experience
Speech object can be attached to any message response type.
{"recipient":{"id":"<USER_ID>"},"sender":{"id":"<CHANNEL_ID>"},"response_to_mid": "source event identification","message":{"text":"hello, world!","voice": {"ssml": "SSML alternative", // optional"voice": "cs-CZ-AntoninNeural"}}}
Quick reply suggestions
message.text: (string) - required text message
incomming event
text(string) - the text of selected quick reply suggestionquick_reply.payload(string) - the content of quick reply
{"sender":{"id":"<USER_ID>"},"recipient":{"id":"<CHANNEL_ID>"},"timestamp":1458692752478,"mid": "<unique event identification>","message":{"text":"hello, world!","quick_reply": {"payload": "<DEVELOPER DEFINED PAYLOAD>"}}}
responding to user
text(string) - text message the suggestions are bound toquick_replies(object[]) - list of quick repliesquick_replies[].content_type(enum) - place for alternative suggestion typestext- textual quick reply types
quick_replies[].title(string) - suggestion titlequick_replies[].payload(string) - bot payload helps to identify the chosen suggestion
{"sender":{"id":"<CHANNEL_ID>"},"responder":{"id":"<USER_ID>"},"response_to_mid": "source event identification","message":{"text":"hello, world!","quick_replies": [{"content_type": "text","title": "<SUGGESTION CAPTION>","payload": "<DEVELOPER_DEFINED_PAYLOAD>"}]}}
Incomming intent
message.intent: (object) - requiredmessage.intent.intent: (string|string[]) - the intent[message.intent.score]: (number) - detected intent score (0.0 - 1.0)[message.intent.entities]: (object[]) - optional array of entitiesmessage.intent.entities.entity: (string) - detected entity namemessage.intent.entities.value: (string) - detected entity value[message.intent.entities.score]: (number) - detected entity score (0.0 - 1.0)[message.text]: (string) - optional text uterance
incomming event
{"sender": {"id":"<USER_ID>"},"recipient": {"id":"<CHANNEL_ID>"},"timestamp":1458692752478,"mid": "<unique event identification>","message": {"intent": {"intent": "detected-intent","entities": [ // optional{ "entity": "entity-name", "value": "entity-value" }]},"text":"hello, world!" // optional}}
responding to user: not available
Postback event
postback.payload(string) - requiredpostback.title(string) - optional button title, or call to actionpostback.target_app_id(string,enum) - event is targetted for a special appPRIMARY- the primary application of a channelOWNER- thread owner (default)
incomming event
The postback is a background event. It could be visible, if there is a title attribute.
{"sender": {"id": "<USER_ID>"},"recipient":{"id": "<CHANNEL_ID>"},"timestamp": 1458692752478,"mid": "<unique event identification>","postback":{"payload": "<USER_DEFINED_PAYLOAD>","title": "<OPTIONAL_CTA_TITLE>","target_app_id": "<OPTIONAL_TARGET_APP>"}}
responding to user
{"sender":{"id":"<USER_ID>"},"recipient":{"id":"<CHANNEL_ID>"},"response_to_mid": "source event identification","postback":{"payload": "<USER_DEFINED_PAYLOAD>",}}
Attachments
Attachment: media (audio, video, image, file)
incomming event
message.attachments(object[]) - attachment typemessage.attachments[].type(enum) - attachment typeaudiovideoimagefile
message.attachments[].payload.url(string) - url of media content
{"sender":{"id":"<USER_ID>"},"recipient":{"id":"<CHANNEL_ID>"},"timestamp":1458692752478,"mid": "<unique event identification>","message":{"attachments": [{"type":"image","payload":{"url": "<MEDIA URL>"}}]}}
responding to user
message.attachment.type(enum) - attachment typeaudiovideoimagefiletemplate- has different format of properties
message.attachment.payload.url(string) - url to media contentmessage.attachment.payload.is_reusable(string) - optionally tells the channel adapter to cache the content
{"sender":{"id":"<USER_ID>"},"recipient":{"id":"<CHANNEL_ID>"},"response_to_mid": "source event identification","message":{"attachment":{"type":"image","payload":{"url":"<MEDIA URL>","is_reusable":true // optional}}}}
Button (element description)
type(enum) - button typepostbackweb_url
url(string) - link forweb_urlbuttonpayload(string) - developer defined payload forpostbackbutton
URL button
Opens a web page.
{"type": "web_url","url": "<TARGET URL>","title": "<VISIBLE CAPTION>"}
Postback button
Sends a postback to the bot, that created the button regardless who's the current thread owner.
{"type": "postback","payload": "<DEVELOPER DEFINED PAYLOAD>","title": "<VISIBLE CAPTION>"}
Attachment: template
message.attachment.type(enum) - attachment typetemplate
message.attachment.payload.template_type(enum)button- for the button templategeneric- for the carousel of cards
incomming event: not available
sending a button template
message.attachment.payload.text(string) - text above buttonsmessage.attachment.payload.buttons(Button[]) - list of buttons (1-3)
{"recipient":{"id":"<ID>"},"message":{"attachment":{"type":"template","payload":{"template_type":"button","text":"<TEXT ABOVE BUTTONS>","buttons":[{"type": "postback","payload": "<DEVELOPER DEFINED PAYLOAD>","title": "<VISIBLE CAPTION>"}]}}}}
sending a carousel of cards (generic template)
message.attachment.payload.elements(object[]) - list of cardsmessage.attachment.payload.elements[].title(string) - card titlemessage.attachment.payload.elements[].subtitle(string) - optional card subtitlemessage.attachment.payload.elements[].buttons(Button[]) - optional buttons (0-3)
{"recipient":{"id":"<ID>"},"message":{"attachment":{"type":"template","payload":{"template_type":"generic","elements":[{"title":"<REQUIRED CARD TITLE>","image_url":"<IMAGE URL>", // optional"subtitle":"<SUBTITLE TEXT>", // optional"buttons":[ // optional{"type": "postback","payload": "<DEVELOPER DEFINED PAYLOAD>","title": "<VISIBLE CAPTION>"}]}]}}}}
Handover protocol
The protocol allows do orchestrate the conversation and makes bot able to decide, which skill should handle the conversation.
Pass thread - transfering the context to another application
passing a thread
target_app_id(string,enum) - application to pass the thread toPRIMARY- the primary application of a channel
metadata(string) - optional metadata to pass within the pass thread event
{"sender":{"id":"<USER_ID>"},"recipient":{"id":"<CHANNEL_ID>"},"response_to_mid": "source event identification","target_app_id": "<TARGET_APP>","metadata": "<OPTIONAL_STRING_PAYLOAD>"}
incomming event
The postback is a background event. It could be visible, if there is a title attribute.
pass_thread_control.new_owner_app_id(string) - identifier of the target apppass_thread_control.previous_owner_app_id(string) - id of the last thread owner apppass_thread_control.metadata(string) - optional metadata from the original eventcontext(object) - shared context if the app is subscribed to it's updatescontext.timestamp(number) - unix timestamp of the last modification of shared context
{"sender":{"id":"<USER_ID>"},"recipient":{"id":"<CHANNEL_ID>"},"timestamp":1458692752478,"mid": "<unique event identification>","pass_thread_control":{"new_owner_app_id": "<TARGET_APP_ID>","previous_owner_app_id": "<PREVIOUS_APP_ID>","metadata": "<OPTIONAL_STRING_PAYLOAD>"},"context": {"timestamp": 129392434398// if the app is subscribed to shared context updates}}
Passing thread with text
To make the target application able to answer the incomming event it's possible to include a previous text event from the user.
message.text: (string) - text uterance
passing a thread
{..."target_app_id": "<TARGET_APP>","message":{"text": "<TEXT_TO_BE_PROCESSED_WITH_TARGET_APP>"}}
incomming event
{..."pass_thread_control":{"new_owner_app_id": "<TARGET_APP_ID>","previous_owner_app_id": "<PREVIOUS_APP_ID>"},"message":{"text": "<TEXT_TO_BE_PROCESSED_WITH_TARGET_APP>"}}
Passing thead with an intent
It's also possible to to pass the thread with an intent
message.intent: (string) - required[message.entities]: (object[]) - optional array of entitiesmessage.entities.entity: (string) - detected entity namemessage.entities.value: (string) - detected entity value[message.entities.score]: (number) - detected entity confidence (0.0 - 1.0)[message.text]: (string) - optional text uterance
passing a thread
{..."target_app_id": "<TARGET_APP>","message":{"intent":"received-intent","entities": [ // optional{ "entity": "entity-name", "value": "entity-value" }],"text":"hello, world!" // optional}}
incomming event
{..."pass_thread_control":{"new_owner_app_id": "<TARGET_APP_ID>","previous_owner_app_id": "<PREVIOUS_APP_ID>"},"message":{"intent":"received-intent","entities": [{ "entity": "entity-name", "value": "entity-value" }],"text":"hello, world!"}}
Passing the thread to trigger the specific action or dialogue
To be able to pass the thread to a specific dialogue of a specific bot, a postback can be bundled in pass thread event. For example, it's useful when passing a thread to human agent.
postback.payload(string) - required - will be passed to target application
incomming event
The postback is a background event. It could be visible, if there is a title attribute.
{..."target_app_id": "<TARGET_APP>","postback":{"payload": "<USER_DEFINED_PAYLOAD>"}}
incomming event
{..."pass_thread_control":{"new_owner_app_id": "<TARGET_APP_ID>","previous_owner_app_id": "<PREVIOUS_APP_ID>"},"postback":{"payload": "<USER_DEFINED_PAYLOAD>"}}
Sharing the conversation context between skills
Setting the context and listening to updates
Each conversation has an own key-value store of shared data.
setting a shared context
The shared context is changed just when the following request is received by orchestrator.
set_context(object) - keys and values to be changedset_context.<KEY>(any) - the changed value
{"sender":{"id":"<USER_ID>"},"recipient":{"id":"<CHANNEL_ID>"},"set_context": {"<KEY>": "<VALUE>"}}
getting notified
The application will get notified, if
- a prop, the app is subrcibed to, has been changed
- the app is not an emmiter of the change
There's only one object to be received
set_context(object) - new context of the conversation
{"sender":{"id":"<USER_ID>"},"recipient":{"id":"<CHANNEL_ID>"},"set_context": {"<KEY>": "<VALUE>"}}
When handover event occurs, the target application will receive the whole context wihin a
contextobject
Tracking protocol
The protocol allows to attach usefull informations from dialogue platform to source conversation.
Attaching tracking events to the bot response
In ideal case, a tracking event should be sent after all conversation response events.
tracking(object) - tracking object containertracking.events[].type(enum:log,report,conversation,audit,user) - category of the tracking eventtracking.events[].category(string) - category of the tracking eventtracking.events[].label(string) - optional label of the tracking eventtracking.events[].value(number) - optional numeric value of the tracking event - 0 by defaulttracking.events[].payload(object) - optional unstructured data related to tracking eventtracking.meta(object) - optional metadata related to response eventstracking.meta.actions(string[]) - list of dispatched actionstracking.meta.intent(string) - detected intenttracking.meta.confidence(number) - used confidence thresholdtracking.meta.intents({ intent: string, score number }) - detailed intent detection breakdowntracking.meta.entities({ entity: string, value: any, score number }) - detected entities
{..."response_to_mid": "abc123","tracking": {"events": [ // optional{"type": "","category": "<TRACKING_CATEGORY>","label": "<TRACKING_LABEL>", // optional"value": 1, // optional"payload": {} // optional}],"meta": {"actions": ["start"],"intent": null,"confidence": 0.86}}}
Other events
Sender action event (typing)
This event enhances a text channel conversation.
sender_action: (enum) - optinal alternative for voice interfacetyping_on- show typing indicatortyping_off- hide a typing indicator
incomming event
{"recipient": {"id": "<CHANNEL_ID>"},"sender": {"id": "<USER_ID>"},"response_to_mid": "source event identification","sender_action": "typing_off"}
responding to user
{"recipient": {"id": "<USER_ID>"},"sender": {"id": "<CHANNEL_ID>"},"response_to_mid": "source event identification","sender_action": "typing_on"}
Wait event - for synchronous responses
This event helps to make a pause between visual messaging events at synchronous response mode.
incomming event: not available
responding to user
the event is ignored in asynchronous communication
wait: (number) - number of milisecods to wait
{"recipient": {"id": "<USER_ID>"},"sender": {"id": "<CHANNEL_ID>"},"response_to_mid": "source event identification","wait": 1000}
Application subscribtion
Each app can be subscribed to following events
messages(boolean) - text messages and quick replieshandovers(boolean) - handover eventspostbacks(boolean) - postbackssenderActions(boolean) - client side sender actionscontextUpdates(boolean) - subscribe to context updatesstandbyIncoming(boolean) - the app will receive all standby messages from pagestandbyOutgoing(boolean) - the app will receive all standby messages from applicationstracking(boolean) - the app will receive all tracking events sent to orchestrator even if it's not a thread owner