Asynchronous webhooks with IBM Watson Assistant

Sarah Packowski
8 min readSep 3, 2022

--

Webhooks must return before a configured timeout or else they fail. See how your chatbot can use web services that require asynchronous handling.

Download the complete example here: https://github.com/spackows/watson-assistant-async

Solution overview

Webhook timeout

Webhook calls by a Watson Assistant chatbot are synchronous: When your chatbot calls a webhook, an answer must be returned within the configured timeout. Otherwise, the webhook call fails.

Webhook timeout configuration
Webhook timeout configuration

Web chat API

There are multiple ways to implement a solution for using asynchronous web services with chatbot webhooks. This post shows how you can use the Watson Assistant web chat API to accomplish this:

  • Step 1: Build a simple chatbot dialog
  • Step 2: Publish a web chat integration
  • Step 3: Deploy the sample Node.js app
  • Step 4: Update the chat dialog to call a webhook
  • Step 5: Message the chat using the web chat API
  • Step 6: Handle the async result message in the chatbot
  • Step 7: Update the webhook to use /asyncwrapper

Step 1: Build a simple chatbot dialog

In the Watson Assistant web interface (classic experience shown) create a dialog skill with two nodes:

  • A “Welcome” node, triggered by the welcome condition, that prompts users to enter data to be processed
  • An “Anything else” node, triggered by the anything_else condition, that prints a generic input-not-recognized message
Initial dialog tree
Initial chatbot dialog tree

Step 2: Publish a web chat integration

In the Watson Assistant web interface, perform these steps:

2.1 On the Assistants page, create an assistant

2.2 Add the dialog skill created in the previous step to the assistant

2.3 Add a web chat integration to the assistant

2.4 From the Embed tab of the Web chat integration page, collect the integrationID, region, and serviceInstanceID (you’ll need them in the next step)

Web chat integration
Web chat integration page

Step 3: Deploy the sample Node.js app

The sample solution for this blog post is a Node.js web app. To use the sample app with a Watson Assistant chatbot, deploy the app — on IBM Cloud using IBM Code Engine, for example.

Source: https://github.com/spackows/watson-assistant-async

Highlights

In the file server.js, you can see:

  • GET /chatbot endpoint (views/pages/chatbot.ejs) for viewing the web chat integration in a browser
g_app.get( "/chatbot", function( request, response )
{
var parms = { "integration_id" : integration_id,
"region" : region,
"service_instance_id" : service_instance_id,
"base_url" : g_base_url };
response.render( "pages/chatbot", parms );

} );
  • POST /asyncendpoint a simple endpoint that takes long enough to respond that using it directly in Watson Assistant as a webhook fails
g_app.post( "/asyncendpoint", function( request, response )
{
var str = request.body.str ? request.body.str : "";
const myTimeout = setTimeout( function()
{
// Wait 10 seconds before responding
// Note: Just a random thing to show results.
// *Only works for ASCII text.
var result = str.split("").reverse().join("");

response.status( 200 ).json( { "error_str" : "",
"result" : result } );

}, 10 * 1000 );

} );
  • POST /asyncwrapper and endpoint for your chatbot to call as a webhook
g_app.post( "/asyncwrapper", function( request, response )
{
var str = request.body.str ? request.body.str : "";

// 1. Respond right away so the chatbot can move on
// 2. Call the async web service
// 3. After the web service responds, send a message
// to the chatbot using the chatbot web api

response.status( 200 ).json( { "error_str" : "",
"result" : "Success" } );

callAsyncWebService( str, function( error_str, result_data )
{
if( error_str )
{
sendMessage( { "error_str" : error_str } );
return;
}

sendMessage( result_data );

} );

} );

The app requires several environment variables to be set:

  • WA_INTEGRATION_ID : The integrationID collected in the previous step
  • WA_REGION : The region collected in the previous step
  • WA_SERVICE_INSTANCE_ID : The serviceInstanceID collected in the previous step
  • BASE_URL : Should be set to http://localhost:8080 when running the app on your local computer and set to the base url of your deployed app when deployed in the Cloud

Step 4: Update the chat dialog to call a webhook

In the Watson Assistant web interface, add a webhook:

4.1 In the webhooks section of the skill-editing page, enter the url of your deployed app, with the /asyncendpoint endpoint specified

Webhook
Enter your deployed app, specifying the /asyncendpoint endpoint

4.2 Add a child node to the “Welcome” node, called “Calling webhook”. Set the condition of the new node to be anything_else. The new node has the sole job of printing the status message ‘Calling the async webhook …’. Set the “Welcome” node to jump to the child node, wait for user input, and then evaluate the condition.

4.3 Add a child node to the “Calling webhook” node, called “Call async wrapper webhook”, with a condition of anything_else. Customize the new node to enable Callout to webhooks. Specify one parameter:

  • Key : str
  • Value : "<? input.text ?>"

In the Assistant responds section, simply print the webhook result.

Add a webhook to your chatbot
Calling a webhook

4.4 Use the Try it button to test the chatbot. The webook will time out.

Calling the webhook times out
Calling the webhook times out

Step 5: Message the chat using the web chat API

The easiest way to test the next step is by running the sample node.js app on your local computer.

5.1 In a command window, set the environment variables listed in step 3.

5.2 Run the sample app using the command node server.js

5.3 Open http://localhost:8080/chatbot in a browser. The Watson Assistant chatbot icon appears in the lower-right corner. Click the icon to start the chat. Manually type the same input as with the Try it window befor. Notice the webhook call to /asyncendpoint times out just like before.

5.4 In a separate command window, use cURL to call the /asyncwrapper endpoint with some sample text:

> curl -X POST http://localhost:8080/asyncwrapper -d str="bla bla bla"

You will see results in the command window:

{ "error_str" : "", "result" : "Success" }

And you will see a message sent to the chat in your browser:

Testing /asyncwrapper locally
Testing /asyncwrapper locally

Explanation

The /asyncwrapper endpoint called the callAsyncService routine:

[server.js]

function callAsyncWebService( str, callback )
{
var url = g_some_async_webservice;
var data = { "str" : str };

g_axios.post( url, data ).then( function( result )
{
if( 200 !== result.status )
{
callback( "async call failed", {} );
return;
}

callback( "", result.data );

} ).catch( function( error )
{
callback( error.message, {} );

} );

}

(You would implement your asynchronous call and associated logic in the callAsyncService routine — such as implementing polling, for example.)

The /asyncwrapper endpoint then calls sendMessage to send the results from callAsyncService to the chatbot integration web page using socket.io:

[server.js]

var g_socket_p = null;
const g_io = g_socketio( g_server );
g_io.on( "connection", function( socket )
{
g_socket_p = socket;
} );
function sendMessage( data )
{
g_socket_p.emit( "asyncresult", data );
}

The Javascript in the integration web page uses the Watson Assistant web chat API send method to pass the results to the chat in a message:

[chatbot.ejs]

socket.on( "asyncresult", function( data )
{
messageChatbot( data.result );

} );
...
function messageChatbot( txt )
{
var send_obj = { "input": { "message_type" : "text",
"text" : "ASYNCRESPONSE " + txt } };

g_wa_instance.send( send_obj, {} );
}

Step 6: Handle the async result message in the chatbot

In the Watson Assistant web interface, update the chat dialog to handle the message coming from the web chat API:

6.1 Add a pattern entity, called “ASYNCRESPONSE”, with the pattern: ASYNCRESPONSE

6.2 Add a dialog node just above the “Anything else” node, called “ASYNCRESPONSE”. Set the condition to recognize the ASYNCRESPONS Eentity just created.

6.3 In the “ASYNCRESPONSE” dialog node, open the context menu, and then set a user-defined variable, called $WEBHOOK_RESULT to the value:

"<? input.text.extract( '\\s+(.*)$', 1 ) ?>"

Read more about using a regular expression to extract part of an input string here: Expression language

6.4 In the Assistant responds section of the “ASYNCRESPONSE” dialog node, return a multiline response:

Webhook response received:

<? $WEBHOOK_RESULT ?>

Processing...

Dialog node to handle the web chat API message
Dialog node to handle the web chat API message

(Your chatbot would go on to process the result from the asynchronous web service.)

Explanation

In Step 5, the Javascript method sendMessage (in [chatbot.ejs]) used the web chat API to send a message to the chat. And sendMessage prepended the text with "ASYNCRESPONSE ". Because that string matches the pattern entity just added to the dialog, prepending the message with that string causes the chatbot dialog to recognize that the message contains the response to a call to an asynchronous web service.

If you rerun the test with the curl command from Step 5, the final reply from the chatbot won’t be from the “Anything else node”, it will be from your new “ASYNCRESPONSE” node:

Testing ASYNCRESPONSE node
Testing ASYNCRESPONSE node

Step 7: Update the webhook to use /asyncwrapper

In the Watson Assistant web interface, update the webhook to point to the /asyncwrapper endpoint.

When you run through steps 5.1 to 5.3 on the deployed chatbot web page, you’ll see that the asynchronous results appear in the chat:

Complete solution
Complete solution

Notes:

  • In this example, the text that the web page Javascript sends to the chat (“ASYNCRESPONSE alb alb alb”) is visible in the chat just to make it easier to follow what’s going on. In your production solution, you can set the silent option to “true” to hide the message from the chat.
  • Designing the dialog to handle on-going chat with the user while waiting for asynchronous results is not included in this blog post (to keep this post focused on the API aspects of the solution.)

Conclusion

Using features such as the web chat API, you can create feature-rich chatbot solutions with Watson Assistant that integrate countless web services — including ones that require asynchronous handling.

--

--

Sarah Packowski
Sarah Packowski

Written by Sarah Packowski

Design, build AI solutions by day. Experiment with input devices, drones, IoT, smart farming by night.

No responses yet