- Table of Contents
- Pre-requisites
- Setting up your WhatsApp Chatbot
- Identifying Users
- Limitations
- Pricing
To setup a WhatsApp Chatbot, you must ensure that you have:
- a WhatsApp Business account
- a WhatsApp Developer account
You can register for these at https://business.facebook.com and https://developers.facebook.com respectively.
On https://developers.facebook.com, click on 'My Apps' at the top right hand corner of the screen. You will be redirected to the Apps page
If you do not have an existing chat bot, click on 'Create App'
Select the app type as 'business'
Fill in the appropriate details
Click Create App and your application has been created!
Now, you must add WhatsApp to the App. Do this by scrolling through the products and find WhatsApp, and click 'Set up'
You will be redirected to this page. Click on the Business you wish to link to, and press 'Continue'
You can now try sending in a test message by adding in your phone number, and clicking the 'Send Message' button
After clicking send message, you should receive such a message to your WhatsApp
Now we need to set-up webhooks to make use of the API.
In order to set up webhooks, on the left hand side, under 'WhatsApp', click onto Configuration
Click on 'edit'
Now fill in the following form
The callback URL will be to your backend Webhook controller, typically in the form of https://your-domain-here.com/webhook
Verify token refers to the unique key to authorize connection to the webhook. We now go through how Meta verifies and authorizes the webhook.
During verification, Meta will use the Callback URL as well as the Verify token you have passed in to do the following GET request
GET https://www.your-clever-domain-name.com/webhooks?
hub.mode=subscribe&
hub.challenge=1158201444&
hub.verify_token={{YOUR_VERIFY_TOKEN}}
During this process, Meta expects the backend to:
- Ensure that the provided
YOUR_VERIFY_TOKEN
is the same as the one in the backend - Return the same value provided in challenge
So an example response to the following request should be:
1158201444
If the response is not the same as the challenge number, the verification will fail and the webhook setup will fail.
An example method can be found in webhook.controller
/**
* Facebook will call this API to verify the webhook
*
* Learn more at https://developers.facebook.com/docs/graph-api/webhooks/getting-started#verification-requests
* @param mode
* @param challenge
* @param token
* @returns
*/
@Get('/webhook')
async verify(
@Query('hub.mode') mode: String,
@Query('hub.challenge') challenge: String,
@Query('hub.verify_token') token: String,
) {
if (mode && token) {
if (mode === 'subscribe' && token === process.env.VERIFY_TOKEN) {
console.log('WEBHOOK VERIFIED');
return challenge;
} else {
throw new BadRequestException('VERIFICATION FAILED');
}
}
}
After successfully setting up the webhook, we now need to handle the events sent by WhatsApp.
Setup an endpoint to 'POST /webhook'
, the following request is an example event sent by WhatsApp
{
"object": "whatsapp_business_account",
"entry": [{
"id": "WHATSAPP_BUSINESS_ACCOUNT_ID",
"changes": [{
"value": {
"messaging_product": "whatsapp",
"metadata": {
"display_phone_number": PHONE_NUMBER,
"phone_number_id": PHONE_NUMBER_ID
},
"contacts": [{
"profile": {
"name": "NAME"
},
"wa_id": PHONE_NUMBER
}],
"messages": [{
"from": PHONE_NUMBER,
"id": "wamid.ID",
"timestamp": TIMESTAMP,
"text": {
"body": "MESSAGE_BODY"
},
"type": "text"
}]
},
"field": "messages"
}]
}]
}
More example payloads can be found at https://developers.facebook.com/docs/whatsapp/cloud-api/webhooks/payload-examples
Some important information we can pull from this event are:
- Senders WhatsApp ID
- Text message from the Sender
- Time of message sent
We try responding to the message with an echo of the message sent to us as an example
From the example request, we can pull out the following information:
const sender_phone_number_id =
req.body.entry[0].changes[0].value.metadata.phone_number_id;
const sender_phone_number =
req.body.entry[0].changes[0].values.messages[0].from;
const sender_message =
req.body.entry[0].changes[0].values.messages[0].text.body;
We send a response back to the sender. To do this, we use axios to make a POST request.
await axios.post(
`https://graph.facebook.com/v12.0/${phone_number_id}/messages?access_token=${process.env.TOKEN}`,
{
messaging_product: 'whatsapp',
to: sender_phone_number,
text: { body: `echo: ${sender_message}` },
},
);
NOTE that access token here refers to the one shown in Step 2 image 3.
We now try sending a message to our text bot.
Our chat bot has successfully echoed!
Message templates can be useful if we want to incorporate Quick Replies or Call to Action. They can also be useful to have a fixed template to sending replies, and not needing to hard code everything in the backend. We go through some use cases for Message Templates
Quick replies are buttons that when pressed, will reply with the same text in the button.
The buttons with the text 'Start', 'Quit' and 'Lol' are the quick replies. By tapping on one, it will immediately reply with the button clicked
As seen, after clicking 'Lol', 'Lol' is immediately replied.
This can be useful if you want to give a list of available commands for users to choose from, and immediately invoke the command
Call to Action is also a button, used to either:
- Call a specific phone number
- Redirect to a URL.
Businesses can use this to redirect to their company websites, or to call their hotline
Message templates can have dynamic variables within them. So you can use them dynamically to fit the user. We will show more of this later.
Creating a message template can be quite confusing in the beginning (because it is so hard to navigate to)
We navigate through the UI to create message templates together
-
Sign in to https://business.facebook.com, click the three line menu icon at the top left
-
Scroll down the menu till you see 'WhatsApp Manager' and click on it
-
Click on the three dots found at the top right of the card Click on 'Manage Message Templates'
-
Click on 'Create Message Template' to start creating a message template
You will be given options to create a Transactional, Marketing or a OTP. For now, we choose Transactional, and name it anything we like.
Note that we must also select a language. We will select English (UK)
-
We fill in the Header with a Text and body. For the body, we use the following to demonstrate dynamic variables
-
We add buttons at the bottom. We use quick reply to check if the information is correct
On the right hand side, it shows what our text message will look like
We click submit now. Lets edit our code to send this template and handle the responses.
We make use of the webhook handler to check if the message is 'start', if the message is start, we create the template and send it back
const message = req.body.entry[0].changes[0].values.messages[0].text.body;
const sender_phone_number_id =
req.body.entry[0].changes[0].value.metadata.phone_number_id;
const sender_phone_number =
req.body.entry[0].changes[0].values.messages[0].from;
const sender_name = body.entry[0].changes[0].value.contacts[0].profile.name;
if (message === 'start') {
await axios.post(
`https://graph.facebook.com/v12.0/${phone_number_id}/messages?access_token=${process.env.TOKEN}`,
{
messaging_product: 'whatsapp',
to: sender_phone_number,
type: 'template',
template: {
name: 'testing_templates',
language: {
code: 'en_GB',
},
components: [
{
type: 'body',
parameters: [
{
type: 'text',
text: sender_name,
},
{
type: 'text',
text: sender_phone_number,
},
],
},
],
},
},
);
}
We see that after we send start, the message has filled up with the correct information
Differentiating between button reply vs normal text reply
A reply to a button will have a different payload compared to a normal text
// This is nested within entry[0].value.messages[0]
// Normal text
{
"from": '...',
"id": 'wamid.HBgKNjU4NjY2NDM3NRUCABIYFDNFQjBDNUE2RTlBMDhBMzI4ODM4AA==',
"timestamp": '1658108791',
"text": [Object],
"type": 'text'
}
// Button reply
{
"context": [Object],
"from": '6586664375',
"id": 'wamid.HBgKNjU4NjY2NDM3NRUCABIYFDNFQjAzOUJDQ0Q3NEU5OUI1NDMxAA==',
"timestamp": '1658108632',
"type": 'button',
"button": [Object]
}
// Button object
{ "payload": 'Yes', "text": 'Yes' }
We can see that button will have type 'button' instead of text, the button object will have a payload and text, which are likely the same. We can make use of this to differentiate between button replies and normal texts
Another way to send messages with buttons are interactive messages. We use the Message API to send these. The two types of messages are list messages and reply buttons
These messages can be used to list out options to choose from.
To create a list message, we need an interactive object, below is an example interactive object:
const interactive = {
type: 'list',
header: {
type: 'text',
text: 'Welcome',
},
body: {
text: 'Welcome to my Chatbot. Select an option you would like',
},
action: {
button: 'View Options',
sections: [
{
title: 'Account',
rows: [
{
id: 'account-login',
title: 'Login',
description: 'Log into your account',
},
{
id: 'account-forgot',
title: 'Forgot your password',
},
],
},
{
title: 'Cart',
rows: [
{
id: 'cart-view',
title: 'View my cart',
},
{
id: 'cart-checkout',
title: 'Checkout',
},
],
},
],
},
};
await axios.post(
`https://graph.facebook.com/v12.0/${phone_number_id}/messages?access_token=${process.env.TOKEN}`,
{
messaging_product: 'whatsapp',
to: from,
type: 'interactive',
interactive: interactive,
},
);
This list message will send the following:
We can tell that the following response is from clicking an interactive message as the following webhook will be sent:
// Example message from webhook
{
"context": [Object],
"from": '...',
"id": 'wamid.HBgKNjU4NjY2NDM3NRUCABIYFDNFQjBDQTJBQzhDRkZCMjZGNkFCAA==',
"timestamp": '1658112692',
"type": 'interactive',
"interactive": [Object]
}
// interactive object
{
"type": 'list_reply',
"list_reply": { id: 'account-forgot', title: 'Forgot your password' }
}
We can see that the list_reply id is the same the one we set in the code above. This is how we can handle the list replies
These are similar to message templates, except that the response will be an interactive type response. This could be potentially better to use if we want to tie an ID to each button click.
const interactive = {
type: 'button',
header: {
type: 'text',
text: 'Welcome',
},
body: {
text: 'Welcome to my Chatbot. Select an option you would like',
},
action: {
buttons: [
{
type: 'reply',
reply: {
id: 'button-login',
title: 'Login',
},
},
{
type: 'reply',
reply: {
id: 'button-forgot-password',
title: 'Forgot Password',
},
},
{
type: 'reply',
reply: {
id: 'button-help',
title: 'Help',
},
},
],
},
};
await axios.post(
`https://graph.facebook.com/v12.0/${phone_number_id}/messages?access_token=${process.env.TOKEN}`,
{
messaging_product: 'whatsapp',
to: from,
type: 'interactive',
interactive: interactive,
},
);
This will send the following message:
The following is a sample response, similar to the list response
// Message object
{
context: [Object],
from: '6586664375',
id: 'wamid.HBgKNjU4NjY2NDM3NRUCABIYFDNFQjBBNTEwNzhFQjJEMTE0REFGAA==',
timestamp: '1658113969',
type: 'interactive',
interactive: [Object]
}
// interactive object
{
type: 'button_reply',
button_reply: { id: 'button-login', title: 'Login' }
}
You can make use of the interactive buttons to create an entire message flow for your end users.
You can read more about interactive messages at https://developers.facebook.com/docs/whatsapp/guides/interactive-messages/
You can identify users in the payload sent
The following is a sample payload:
{
"object": "whatsapp_business_account",
"entry": [{
"id": "WHATSAPP_BUSINESS_ACCOUNT_ID",
"changes": [{
"value": {
"messaging_product": "whatsapp",
"metadata": {
"display_phone_number": PHONE_NUMBER,
"phone_number_id": PHONE_NUMBER_ID
},
"contacts": [{
"profile": {
"name": "NAME"
},
"wa_id": PHONE_NUMBER
}],
"messages": [{
"from": PHONE_NUMBER,
"id": "wamid.ID",
"timestamp": TIMESTAMP,
"text": {
"body": "MESSAGE_BODY"
},
"type": "text"
}]
},
"field": "messages"
}]
}]
}
We can extract the WhatsApp ID of the end user through the metadata field, and retrieve their phone_number_id. We can then store this ID in our database to keep track of users
The WhatsApp chatbot, while being able to have interactions with users via Interactive Message Templates and Interactive Messages, is not as robust as Telegram bot capabilities
- No datepicker
- Cannot send location
- UI/UX not as nice as Telegram
To understand WhatsApp pricing, we must understand the two different kinds of conversations.
- A user-initiated conversation is a conversation which begins with a customer sending a message to a business.
- A business-initiated conversation is a conversation which begins with a business sending a message to a customer.
Upon initiating a conversation (from either business or user), a conversation will last for 24 hours.
For example, User A wishes to find out more about Business B, so he sends a message to Business B's ChatBot at 12am on 18 July. This means that a user-initiated conversation begins, and will end on 12am of 19 July.
- Note: When a message is sent (but not delivered), the conversation session will be in a pre-opened state. Upon successful delivery of the message, the 24 hour period will refresh. This means that if a user sends a message at 12 am, but the message is only delivered at 12:10 am, the 24 hours will refresh to start at 12:10 am.
- If a message is sent but not delivered within 30 days, the message will be dropped and there will be no conversation charge.
Conversations are chargeable. Within the 24 hour window, any number of messages can be sent and the charge will be the same.
- This means that a conversation with 1 message and a conversation with 1000 messages will cost the same amount
A business is given 1000 free conversations a month. Even if a business has multiple WhatsApp bots, all the bots will only be given 1000 free conversations in total.
Conversations that are initiated through Ads that Click to WhatsApp or through a Facebook Page Call-to-Action are not charged, but conversations subsequently are charged normally.
In Singapore, the cost is as follows:
- For user-initiated conversations, USD$0.0224 per conversation
- For business-initiated conversations, USD$0.0745 per conversation
To read more, visit https://developers.facebook.com/docs/whatsapp/pricing.