Facebook Chatbots with OpenWhisk

This post is more than 2 years old.

This weekend I decided to take a quick look at running Facebook Chatbot, aka Facebook Messenger Platform, on the OpenWhisk platform. In general, it worked just fine and 90% of the issues I had were me not reading the docs correctly and making code screw ups. But I can say that I've got it working successfully now and it isn't difficult at all.

Look - it works!

In this blog post I'm going to explain what you would need to do to get a Facebook Chatbot running on OpenWhisk. I will not cover the entire process of creating a bot. For that, you want to read Facebook's Getting Started guide which covers the things you need to setup and discusses the Node code. The code I'm going to share is a modified version of their Node code. It isn't very clean, but it should be enough to get you started if you want to use OpenWhisk for your bot.

The first thing you'll need to do is create a new OpenWhisk action. You are welcome to use my code to start off with, just remember what I said - it is a bit messy. When you create your action, you must use the web action flag so that Facebook can hit it. Given an action name of fbbot, you would enable web action support like so:

wsk action update fbbot --web true

Now you need the URL. You can get that like so:

wsk action get fbbot --url

When you follow Facebook's guide, this is the URL you use for the webhook. Do not add JSON to the end! I always do that as I'm used to building JSON-responding services, but Facebook requires a non-JSON response for verification. Just use the URL as is.

Now for the code. I'm going to share the whole thing at the end, but first I want to share a few bits and pieces. Facebook will either send you a GET or POST request. The GET is always used for verification. You can handle this like so:

	if(args["__ow_method"] === "get") {
		let challenge = args["hub.challenge"];
		let token = args["hub.verify_token"];

		if(token === 'test_token') {
			return { body:challenge };
		} else {
			//todo: return an error condition
	} else if(args.__ow_method === "post") {

Notice I sniff for the HTTP method first, get the values out of the args, and then check to ensure the token is correct. "test_token" was set on the Facebook side and should probably be a bit more secure. As the comment clearly says, I should add an error result there.

The next part of the code will handle responding back to the message. For the most part I followed Facebook's Node sample, but I modified things a bit. Facebook doesn't really how you respond to its HTTP call to your server. Your response means nothing essentially. Instead your code makes its own HTTP request to Facebook's API to respond.

However - if you follow Facebook's lead and "respond early" while the response HTTP call is about to go out, that will not work on OpenWhisk. When OpenWhisk things you're done, it's going to shut down your serverless code. Therefore, the code I used basically waits till everything is done before creating a response.

Since Facebook can, and will, batch responses, that meant using Promise.all as a way of listening for all the possible calls to finish. So here is the entire code snippet. Once again, please remember this leaves a lot of polish out.

const rp = require('request-promise');

function main(args) {
	console.log('fbbot ow activated');

	if(args["__ow_method"] === "get") {
		let challenge = args["hub.challenge"];
		let token = args["hub.verify_token"];

		if(token === 'test_token') {
			return { body:challenge };
		} else {
			//todo: return an error condition
	} else if(args.__ow_method === "post") {
		console.log('post call');

		//ensure it's a page, todo: return an error?
		if(!args.object === "page") return;

		return new Promise((resolve, reject) => {

			let promises = [];
			args.entry.forEach((entry) => {
				//process each entry
				entry.messaging.forEach((event) => {
					if(event.message) promises.push(process(event));


			Promise.all(promises).then(() => {
			.catch((e) => {
				console.log('error in all call');




function process(msg) {
	let senderID = msg.sender.id;
	let recipientID = msg.recipient.id;
	let timeOfMessage = msg.timestamp;
	let message = msg.message;

	console.log(`Received message for user ${senderID} and page ${recipientID} at ${timeOfMessage} with message:`);

	let messageID = message.mid;
	let messageText = message.text;
	//todo: attachments

	var messageData = {
		recipient: {
			id: senderID
		message: {
			text: 'You sent: '+messageText

	return callSendApi(messageData);

function callSendApi(messageData) {
	let options = {
			uri: 'https://graph.facebook.com/v2.6/me/messages',
			qs: { access_token: PAGE_ACCESS_TOKEN },
			method: 'POST',
			json: messageData
	return rp(options);

The process function is where I did the logic of "You sent:" in response to your message. A real bot would do a bit more work obviously. callSendApi is basically just the API call to Facebook. I've modified it to use request-promise since I have to know when it's done.

In theory you can test this on the page I set up for it, BotTest1, but I'm not promising to keep that site up for long. Now that I've got this done, I'm going to look into integrating it with Watson for more advanced demos.

Let me know if you have any questions!

Raymond Camden's Picture

About Raymond Camden

Raymond is a senior developer evangelist for Adobe. He focuses on document services, JavaScript, and enterprise cat demos. If you like this article, please consider visiting my Amazon Wishlist or donating via PayPal to show your support. You can even buy me a coffee!

Lafayette, LA https://www.raymondcamden.com

Archived Comments

Comment 1 by dchamps posted on 10/10/2017 at 10:40 PM

You could give an example of how to verify that the requests came from Facebook with x-hub-signature. I am having difficulties on this issue.

Comment 2 (In reply to #1) by Raymond Camden posted on 10/10/2017 at 11:38 PM

I thought I did. That's the first code block. Do you see it?

Comment 3 (In reply to #2) by dchamps posted on 10/11/2017 at 12:04 AM

Nice and thank you very good post, but I meant verification with x-hub-signature with sha1, as recommended by facebook: https://developers.facebook...

I've done something, but I'm having trouble getting the request body to do the match with the facebook secret app. So I have doubts if it is possible to get the body payload with the current openwhisk architecture.

Comment 4 (In reply to #3) by Raymond Camden posted on 10/12/2017 at 1:20 PM

It was my impression that it was required only for the initial call, the GET. I wasn't even aware of this - it must be optional.

As for OpenWhisk, remember that to get the *raw* HTTP request, you have to enable a flag. This is documented here: https://console.bluemix.net...

I had to do this for Alexa dev.

Comment 5 by Eliseu Silva posted on 10/20/2017 at 2:15 AM

Hi, response for token verify is not working. Is returned 'body: ' with when is expected only challenge value.

Comment 6 (In reply to #5) by Eliseu Silva posted on 10/20/2017 at 2:28 AM

error: Response does not match challenge, expected value="937759710", received="{\n \"body\": \"93775..."

Comment 7 (In reply to #6) by Raymond Camden posted on 10/20/2017 at 2:35 AM

What URL did you use for your action? If it was something.json, remove the .json.

Comment 8 (In reply to #7) by Raymond Camden posted on 10/20/2017 at 2:36 AM

I just realized I had a typo in my blog. It says: Do strong add JSON to the end!

But it should be: Do NOT add JSON. Fixing now.

Comment 9 (In reply to #7) by Eliseu Silva posted on 10/20/2017 at 2:38 AM

Yes. I was using ".json" I removed it and it worked. Thanks!

Comment 10 (In reply to #8) by Eliseu Silva posted on 10/20/2017 at 2:41 AM


Comment 11 by Eliseu Silva posted on 10/20/2017 at 2:46 AM

In my code I made a change in the POST session to read only the last message. Because whenever there was a problem in the bot it would return the message history. I do not know if this could occur in production and so it looks like this:
let msg_events = args.entry [0] .messaging;
let event = msg_events [msg_events.length - 1];
if (event.message && event.message.text) promises.push (process(event));

Comment 12 (In reply to #11) by Raymond Camden posted on 10/20/2017 at 12:42 PM

That's a great tip, thanks Eliseu. I still haven't gone past this basic little demo, but it's still on my queue to build a nicer demo.