How to make a Webhook for Dialogflow Fulfillment

Pallav Trivedi
8 min readFeb 19, 2018

--

Photo by Jason Leung on Unsplash

This is the last part of a three part tutorial series “Building a Chat Bot having AI is Easy”. In the previous parts, we trained & configured our Dialogflow agent and integrated it in an iOS app having a chat window. Till now, our chat bot replies us either using default fallback intent, or using our custom intent i.e. bot.quit. But in both the cases, we do not get the NBA team/game details (as mentioned while discussing the use case in part one). Here we are going to fix it.

Prerequisite: Beginner level knowledge of Node Js

Remember that in first part, we created only one intent viz. bot.quit and left the other intents for this part. Lets start by creating them. Go to your Dialogflow account, and click on create intent. This time, name the intent as “schedule”. We are going to use this intent for asking the schedule of games.

Go for creating an entity name game_occurence, and add two entries viz. previous and next to it.

Go back to your schedule intent and enter some sentences in “User says” section and “schedule” as the name of action in Action section.

For now, save and leave this intent here as we need a webhook to proceed from here. Also, we would require a Database in our webhoook, so let us create our DB first.

We would use Mongo as DB, and for creating the same, go to mLab and sign up for free. After logging in, click on create new and choose any cloud provider with free plan (Sandbox).

Next, select the region, enter the name of the DB and click on submit order.

Once DB is created, create two collections viz. gameschedules and teaminfos. Insert some documents in both.
Sample document for teaminfo:

{
"_id": {
"$oid": "5a81cee221c47608e597b281"
},
"description": "The Sacramento Kings are an American professional basketball team based in Sacramento, California. The Kings compete in the National Basketball Association as a member of the Western Conference Pacific Division.",
"name": "Sacramento Kings",
"__v": 0
}

Sample document for gameschedule:

{
"_id": {
"$oid": "5a8343874c17e8115bbf2ec1"
},
"score": "111-106",
"isWinner": false,
"hasBeenPlayed": true,
"opponent": "Minnesota",
"date": {
"$date": "2018-02-10T18:30:00.000Z"
},
"__v": 0
}

I created the above document with reference to team “Sacramento Kings”. Feel free to chose yours.
You can get the this data from official website of NBA or from thousands of resources over internet like this.
The last thing, click on create user and create one. Now you can connect your DB using a driver via standard MongoDB URI mentioned in your mLab account.

Creating Webhook
Create a NodeJS project (using npm init), and install Express and Mongoose as dependencies. Open your project directory using Atom/Sublime or any of your favourite editors and create a directory name API with three subdirectories viz. Controllers, Models and Routes. In the controllers directory, make a file GameDataController.js. Similarly in models directory, create two files for our models viz. GameSchedule.js and TeamInfo.js. We must also have a router, so lets create a file viz. Routes.js in routes directory.
It is a good practice to keep a separate file Config.js, which would contain our DB url and other configuration stuffs. So create a Config.js at the root (same level where you API directory exists). This is how your hierarchy should look like.

In Config.js, put your DB url i.e.

module.exports =
{
dbUrl:"mongodb://YourUsername:YourPassword@Your_DB_URI/nba"
}

Next, in index.js, create object of application object using Express, route the calls to our router.js, and start the server on some port.

'use strict';var express  = require('express'),
bodyParser = require('body-parser'),
http = require('http'),
config = require('./config'),
server = express(),
mongoose = require('mongoose'),
TeamInfo = require('./API/Models/TeamInfo'), //created model loading here
GameSchedule = require('./API/Models/GameSchedule');
// mongoose instance connection url connection
mongoose.Promise = global.Promise;
mongoose.connect(config.dbUrl);
server.use(bodyParser.urlencoded({ extended: true }));
server.use(bodyParser.json());
var routes = require('./API/Routes/Routes'); //importing route
routes(server); //register the route
server.listen((process.env.PORT || 8000), function () {
console.log("Server is up and listening on port" + process.env.PORT);
});

We have routed our service calls to router.js, so its time to handle them over there. This is the code that your router.js should have.

'use strict';
var express = require('express');
module.exports = function(app) {
var gameDataController = require('../Controllers/GameDataController');
var apiRoutes = express.Router();app.get('/',function(req,res){
res.send('We are happy to see you using Chat Bot Webhook');
});
// registerUser Route
app.route('/')
.post(gameDataController.processRequest);
};

Befor writing our Controller, lets write our models first. Open your TeamInfo.js and write the below code.

var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var TeamInfo = new Schema({name:{
type:String,
required:false
},
description:{
type:String,
required:false
}
});
module.exports = mongoose.model('TeamInfo', TeamInfo);

Similarly, for GameSchedule.js,

var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var GameSchedule = new Schema({date:{
type:Date,
required:false
},
opponent:{
type:String,
required:false
},
hasBeenPlayed:{
type:Boolean,
required:false
},
isWinner:{
type:Boolean,
required:false
},
score:{
type:String,
required:false
}
});
module.exports = mongoose.model('GameSchedule', GameSchedule);

We are done with index.js, router and models. Lets begin the last piece of code for this series as GameDataController.js. Here we will be fetching the data from our mongo DB, and will be returning the response according to the query parameters.

To start with, let’s get the reference of our models and mongoose.

'use strict';var mongoose = require('mongoose');
var TeamInfo = mongoose.model('TeamInfo');
var GameSchedule = mongoose.model('GameSchedule');

Remember the method processRequest used in Router.js. Its time to define and export it.

exports.processRequest = function(req, res) {if (req.body.result.action == "schedule") {
getTeamSchedule(req,res)
}
else if (req.body.result.action == "tell.about")
{
getTeamInfo(req,res)
}
};

The two function used here i.e. getTeamSchedule and getTeamStats would look like.
getTeamInfo:

function getTeamInfo(req,res)
{
let teamToSearch = req.body.result && req.body.result.parameters && req.body.result.parameters.team ? req.body.result.parameters.team : 'Unknown';TeamInfo.findOne({name:teamToSearch},function(err,teamExists)
{
if (err)
{
return res.json({
speech: 'Something went wrong!',
displayText: 'Something went wrong!',
source: 'team info'
});
}
if (teamExists)
{
return res.json({
speech: teamExists.description,
displayText: teamExists.description,
source: 'team info'
});
}
else {
return res.json({
speech: 'Currently I am not having information about this team',
displayText: 'Currently I am not having information about this team',
source: 'team info'
});
}
});
}

getTeamSchedule:

function getTeamSchedule(req,res)
{
let parameters = req.body.result.parameters;
if (parameters.team1 == "")
{
let game_occurence = parameters.game_occurence;
let team = parameters.team;
if (game_occurence == "previous")
{
//previous game
GameSchedule.find({opponent:team},function(err,games)
{
if (err)
{
return res.json({
speech: 'Something went wrong!',
displayText: 'Something went wrong!',
source: 'game schedule'
});
}
if (games)
{
var requiredGame;
for (var i=0; i < games.length; i++)
{
var game = games[i];
var convertedCurrentDate = new Date();
var convertedGameDate = new Date(game.date);
if (convertedGameDate > convertedCurrentDate)
{
if(games.length > 1)
{
requiredGame = games[i-1];
var winningStatement = "";
if (requiredGame.isWinner)
{
winningStatement = "Kings won this match by "+requiredGame.score;
}
else {
winningStatement = "Kings lost this match by "+requiredGame.score;
}
return res.json({
speech: 'Last game between Kings and '+parameters.team+' was played on '+requiredGame.date+' .'+winningStatement,
displayText: 'Last game between Kings and '+parameters.team+' was played on '+requiredGame.date+' .'+winningStatement,
source: 'game schedule'
});
break;
}
else {
return res.json({
speech: 'Cant find any previous game played between Kings and '+parameters.team,
displayText: 'Cant find any previous game played between Kings and '+parameters.team,
source: 'game schedule'
});
}
}
}
}
});
}
else {
return res.json({
speech: 'Next game schedules will be available soon',
displayText: 'Next game schedules will be available soon',
source: 'game schedule'
});
}
}
else {
return res.json({
speech: 'Cant handle the queries with two teams now. I will update myself',
displayText: 'Cant handle the queries with two teams now. I will update myself',
source: 'game schedule'
});
}
}

In the above function, three conditions have been handled.
- Request for previous game schedule (eg. “When was the last game of Kings”).
- Request for next game schedule (eg. “When is the next game of Kings”).
- Request having two teams as parameters (eg. “When is the next game between Kings and Heat”).

To keep it simple for the tutorial purpose, I have written the logic for the first case only. A hard coded response has been set for the remaining two.
Feel free to write your logic for them.

Further, notice that in both the functions, the json response is having the same keys i.e speech, displayText and source. This is the standard format of response for Dialogflow, i.e. Dialogflow will only be able to parse the response if it has these three keys.

That’s all geeks. Save the files, hit “node index.js” on the terminal and test our webhook on postman.

We tested on our local server that the webhook is working fine. Now to get it work with Dialogflow, we need to host it somewhere. Where…? Yes, you got it right. Heroku. Make a repository on Github, push your webhook code on it (git repo), and host it (using the git repo you just) on heroku.

Once your heroku app is created, go to your heroku dashboard, find your app and click on it. This is what it would look like.

On the top right corner, click on the “Open App” button. A new tab would open saying “We are happy to see you using Chat Bot Webhook”. That is the response we sent for GET request (“/”) from Routes.js. Copy the URL from the address bar.

We are done with hosting our webhook, now lets connect it as fulfillment for our Dialogflow intent.

Open Dialogflow, click on the Fulfillments from left menu and enable the webhook. Paste the URL copied from heroku app in URL field and save.

By doing this, we have told our agent to use this webhook as fulfillment whenever asked for. As it is not necessary to use fulflillment for all the intents, we need to specify wherever required. To do so, open your schedule intent and scroll down. Below responses section, you would find fulfillments. Click on it, check for Use Webhook checkbox and save.

That’s it. To test it, use the “Try it now” section at top right. Enter “tell me about the last game with Portland” and you would get the same response that you got on Postman while running on localhost.

You would get the same response from your chat bot as well.

Our chat bot is ready

Happy coding !!!

--

--

Pallav Trivedi

Engineering Manager @BookMyShow ; Previously @ Hungama Digital Media, Built.io, Reliance Jio