Tutorial: Using the Messenger Webview to create richer bot-to-user interactions

Chris Chinchilla
8 min readMar 16, 2018

Developers are building many different types of experiences in Messenger, and while messaging is at the core of a bot’s architecture, the Messenger webview provides devs with the flexibility to incorporate elements of their webpage experience into their app. Whether you’re looking to add a checkout flow, share a new product page, or simply distribute content, the webview opens up a world of possibilities to create richer interactions.

Here are the specific ways you can open a webview from an action: on the persistent menu, a URL button inside a message, or a chat extension. The example we’re going to provide in this tutorial uses a URL button within the webview.

Scope

In this tutorial, we will:

  • Whitelist the domains that serve your webview.
  • Handle incoming messages.
  • Create a URL button that calls a webview.
  • Create a webview.
  • Use the Messenger Extensions SDK to access platform features in your webview.
  • Create an alternative webview that doesn’t use the Messenger Extensions SDK.
  • Process data from the webview.

What you need

Messenger Features

  • Webhook: The Messenger Platform sends events to your webhook to notify your bot when interactions or events happen.
  • Webview: Allows you to open a standard webview, where you can load web pages inside Messenger and offer an experience that is harder with standard Messenger features.
  • A URL Button: Allows you to open a web page in Messenger.
  • The Messenger Extensions SDK: Brings Messenger-specific features to any website or web app opened in the Messenger webview.

Prerequisites

  • Complete the general set up:
  • Facebook Page created: Used as the identity of your bot.
  • Facebook for Developers account: Required to create new apps, which are the core of any Facebook integration.
  • Webhook setup: The Messenger Platform sends events to your webhook to notify your bot when a variety of interactions or events happen, including when a person sends a message.
  • Facebook app setup: contains the settings for your Messenger bot, including access tokens.
  • Build a bot for Messenger. You can do so quickly using the Quick Start guide.
  • Have a server with Node.js installed.

Repository with working code

You can find the final code for the tutorial here.

What does it look like?

The bot overview

How does it work? Step-by-step

Now we’re ready to start building our bot, we’ll do this step by step, and we recommend you complete each one before moving to the next.

STEP 1: Download the starter project

We’ve created a starter project that has the structure set up for you. If you want to skip ahead, you can find the completed project here.

STEP 2: Install dependencies

To begin, you need to install some Node module dependencies. This tutorial uses:

  • Express to set up a web server for the webhook.
  • body-parser to parse the JSON body of the incoming POST requests.
  • request to send HTTP requests to the Messenger Platform.
  • dotenv to make setting environment variables easier.

Issue this command in your terminal:

npm install

Also, add your authentication information to the .env file. Make sure you don’t push this file to version control as it contains sensitive information.

STEP 3: Whitelist domains

As this bot uses the Messenger Extensions SDK, for security reasons, you need to whitelist all the domains that serve your webview, including development and production URLs. You can do this by issuing a POST request:

curl -X POST -H "Content-Type: application/json" -d '{
"whitelisted_domains":[
"https://<DEVELOPMENT_URL>",
"https://<PRODUCTION_URL>"
]
}' "https://graph.facebook.com/v2.6/me/messenger_profile?access_token=<PAGE_ACCESS_TOKEN>"

You can also add the domains from the page connected to your bot under Settings > Messenger platform > Whitelisted Domains.

STEP 4: Handle incoming messages

This example bot only responds to one command, but it’s necessary to display the button that opens the webview. Inside the stub handleMessage function, add the following:

let response;if (received_message.text) {
// Code to follow
} else {
response = {
"text": `Sorry, I don't understand what you mean.`
}
}
callSendAPI(sender_psid, response);

This code checks to see if the message is text, and if not, sends a default message. In a full bot, you could also handle other message types and respond appropriately. Whatever message the bot sends, the code uses the callSendAPI function to send it.

Next, inside the if clause, add the following switch statement:

if (received_message.text) {
switch (received_message.text.replace(/[^\w\s]/gi, '').trim().toLowerCase()) {
case "room preferences":
response = setRoomPreferences(sender_psid);
break;
default:
response = {
"text": `You sent the message: "${received_message.text}".`
};
break;
}
} else {
response = {
"text": `Sorry, I don't understand what you mean.`
}
}

This code first sanitizes any text entered and converts it to lowercase, making it easier to know what format to expect. If the message is ‘room preferences,’ it calls the setRoomPreferences function (covered in step 5), otherwise, it repeats the message.

STEP 5: Display the URL button

This example uses a URL button to open the webview. Add the following code to the stub setRoomPreferences function:

let response = {
attachment: {
type: "template",
payload: {
template_type: "button",
text: "OK, let's set your room preferences so I won't need to ask for them in the future.",
buttons: [{
type: "web_url",
url: SERVER_URL + "/options",
title: "Set preferences",
webview_height_ratio: "compact",
messenger_extensions: false
}]
}
}
};
return response;

This returns a response to the handleMessage function that consists of a button template and a URL button. Let's go into detail on two of the button properties that are important for a webview.

webview_height_ratio sets how large the webview appears in Messenger. The possible values are compact, tall and full. The image below shows how they look respectively.

Webview size presets

Enabling messenger_extensions allows your webview to access a selection of features from the Messenger platform, offering a higher level of integration, and creating a better user experience. Also, without this parameter set to true, the webview opens in a separate tab or window if a user is visiting from the 'messenger.com' domain.

STEP 6: Create the webview page

A webview page is traditional HTML, CSS, and JavaScript, and as you serve it from your web server, in theory, you can do whatever you usually do with a web page within it. Bear in mind that a Messenger window displays this webview, so you have limited space, and should build applications that use this space wisely and suit the device they are shown on. Keep a webview concise and straightforward, segmenting interactions with them in the same way you segment other interactions with a bot.

The url: SERVER_URL + "/options" parameter from step 5 expects a /options path served from the same URL your bot is running on. Near the top of the code we configure the Express framework to serve static HTML files from a particular folder, app.use(express.static('public'));.

At the top level of the project is a public folder, and in it an options.html file. Add the following to that file inside the body tag, below the script tag:

<form action="/optionspostback" method="get">
<input type="hidden" name="psid" id="psid">
<h3>Pillows</h3>
<input type="radio" name="pillows" value="soft" checked>Soft<br>
<input type="radio" name="pillows" value="hard">Hard<br>
<h3>Bed</h3>
<input type="radio" name="bed" value="single" checked>Single<br>
<input type="radio" name="bed" value="double">Double<br>
<input type="radio" name="bed" value="twin">Twin<br>
<h3>View</h3>
<input type="radio" name="view" value="sea" checked>Sea<br>
<input type="radio" name="view" value="street">Street<br>
<input type="submit" value="Submit" id="submitButton">
</form>

This code creates the HTML for the webview and is all standard HTML. The psid element is a hidden input that we set with the users PSID in step 8, and we submit the form data to the /optionspostback endpoint of our Express application for processing.

STEP 7: Initialize the SDK

Now add the JavaScript inside the script tag, first initializing the Extensions SDK:

(function (d, s, id) {
var js, fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) {
return;
}
js = d.createElement(s);
js.id = id;
js.src = "//connect.facebook.com/en_US/messenger.Extensions.js";
fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'Messenger'));

We don’t want any other JavaScript code to trigger until the SDK has fully loaded, so add a stub function below the function above to hold the rest of our code:

window.extAsyncInit = function () {
// SDK loaded, code to follow
};

STEP 8: Access SDK features

When the page loads, the first code snippet in step 7 loads the SDK, and the second snippet called when it is fully loaded. With the SDK loaded, you can start accessing its features. Your users may not have certain Messenger, or Facebook features enabled. This example bot uses the getContext method to access information about the person and the thread that opened the webview. Add the following code to the window.extAsyncInit function:

MessengerExtensions.getSupportedFeatures(function success(result) {
let features = result.supported_features;
if (features.indexOf("context") != -1) {
MessengerExtensions.getContext('<YOUR_APP_ID>',
function success(thread_context) {
// success
document.getElementById("psid").value = thread_context.psid;
// More code to follow
},
function error(err) {
console.log(err);
}
);
}
}, function error(err) {
console.log(err);
});

The code above checks to see if the platform supports the getContext feature, and if so, uses the information provided by it to set the value of the hidden psid input field. In this tutorial, we use this purely to show how the bot and the webview can exchange information.

After the document.getElementById method add the following code:

document.getElementById('submitButton').addEventListener('click', function () {
MessengerExtensions.requestCloseBrowser(function success() {
console.log("Webview closing");
}, function error(err) {
console.log(err);
});
});

This method uses the requestCloseBrowser method to close the webview and returns the user to the Messenger thread once they have submitted the form.

STEP 9: Allow Messenger to serve the webview

To prevent techniques such as clickjacking and other security risks for people using Messenger in a browser, browsers block webviews served from other domains by default. We need to allow Facebook domains to serve our webview by using the X-Frame-Options ALLOW-FROM HTTP header. This step is more complex, as each web page can only have one value set for the header. Because Messenger runs on both the 'messenger.com' and 'facebook.com' domains, we need a workaround. Note that while it's important to add this for web users, it's not needed for mobile users.

First create a new Express route stub to serve the webview:

app.get('/options', (req, res) => {});

Inside that stub add the following:

let referer = req.get('Referer');
if (referer) {
if (referer.indexOf('www.messenger.com') >= 0) {
res.setHeader('X-Frame-Options', 'ALLOW-FROM https://www.messenger.com/');
} else if (referer.indexOf('www.facebook.com') >= 0) {
res.setHeader('X-Frame-Options', 'ALLOW-FROM https://www.facebook.com/');
}
res.sendFile('public/options.html', {root: __dirname});
}

The code above checks the domain that served the page in the webview, and if it is one of the domains that Messenger uses, sets the ALLOWED_BY header to the appropriate domain. It then serves the static page created in steps 7 and 8.

STEP 10: Process the webview data

After using the webview to gather data from a user, our Express application needs to process and do something with it. Create another stub Express route for this:

app.get('/optionspostback', (req, res) => {});

And inside this stub function add the following:

let body = req.query;
let response = {
"text": `Great, I will book you a ${body.bed} bed, with ${body.pillows} pillows and a ${body.view} view.`
};
res.status(200).send('Please close this window to return to the conversation thread.');
callSendAPI(body.psid, response);

This code extracts the data, adds it to a response, sends an ‘OK’ status and passes the response to the callSendAPI method along with the PSID value extracted from the form data.

Conclusion

In this tutorial, we looked at how to add a webview to your bot to create richer interactions within your application and how to interact with external applications. We covered adding Messenger platform features to your webview with the Extensions SDK, and the options for how to open a webview within a bot.

A webview can help drive sign-ups with a custom form, render pages from an external CRM or e-commerce system, and more. The webview feature adds a new visual element to your users’ experience that connects them to your brand — ultimately leading to higher engagement. Join the Dev group to let us know about your experiences using the webview.

--

--

Chris Chinchilla

I explain cool tech to the World. I am a Technical Writer and blogger. I have crazy projects in progress and will speak to anyone who listens.