Adding WhatsApp Flows to Your Chatbot Experience
How to Create Flows
In this portion, you’ll use the Flow Builder to create some Flows. Alternatively, you can use Flows API, which we won’t cover here.
Part 1 Prerequisites
To follow along, ensure you have:
ngrok installed.
A verifieddeveloper account on Meta for Developers and familiarity with Cloud API hosted by Meta.
Finally, ensure you complete the required steps for using Flows. You can also preview the complete project code.
Getting Started
After testing, you can publish the Flow, since it’s currently in the Draft state. To do so, open the menu to the right of Save and click Publish. The Flow is now ready and usable.
The Contact Us Flow
Now, you’ll create a “Contact us” Flow.
Begin by repeating the same initial Flow creation process. For the category, choose Contact Us. Replace the contents of the editor with this JSON markup to render the following:
Publish the Flow and continue to the next section to set up a chatbot. The section features three functions — send_message
, flow_details
, and flow_reply_processor
— which contain vital logic for sending the Flows to users. The section also includes the logic for processing incoming Flow payloads. Therefore, we recommend looking through it even if you’ve already built a chatbot.
How to Set Up the Chatbot
Next, you’ll configure the chatbot and integrate it into your Flows.
Part 2 Prerequisites
Before continuing, ensure you have the following:
Basic knowledge and a recent version of Python
The HuggingFace version of Llama 2 downloaded. The HuggingFace Llama 2 model requires no additional tools or specialized hardware. You can also use the official version, but it necessitates additional setup.
Your account’s access token and phone number ID
A code editor
Using Python to Integrate the Flows and Chatbot
python -m venv venv
Activate it:
source venv/bin/activate
Then, install the required packages:
pip install requests flask llama-cpp-python python-dotenv
You use Flask
to create routes and interact with the API, requests
for sending internet requests, llama-cpp-python
for interacting with the model, and python-dotenv
for loading environment variables.
Next, create an environment file named .env
and the following content, assigning the values appropriately. (You can use any string for the TOKEN
.)
TOKEN = ACCESS_TOKEN = PHONE_NUMBER_ID =
In the same directory, create a file called main.py
and start by adding the packages you’ll use:
import os import re import time import uuid import requests from dotenv import load_dotenv from flask import Flask, request, make_response, json from llama_cpp import Llama
Now, initialize the variables and classes. The snippet also initiates Flask and calls the load_dotenv()
method to help load the variables:
app = Flask(__name__) load_dotenv() PHONE_NUMBER_ID = os.getenv('PHONE_NUMBER_ID') url = f"https://graph.facebook.com/v18.0/{PHONE_NUMBER_ID}/messages" TOKEN = os.getenv('TOKEN') ACCESS_TOKEN = os.getenv('ACCESS_TOKEN') code_prompt_texts = ["Contact us", "Chat with our chatbot", "YES", "NO"] service_list = [ "Accommodation Services", "Spa Services", "Dining Services", "Recreational Facilities", "Business & Conference Services", "Transportation Services", "Accessibility Services", "Pet-Friendly Services" ]
The service_list stores the services offered by the hotel. The code_prompt_texts
list contains the options corresponding to users’ input choices, which are, 1, 2, Y, and N using the function below. The function below will help map the user’s replies to the corresponding option.
def extract_string_from_reply(user_input): match user_input: case "1": user_prompt = code_prompt_texts[0].lower() case "2": user_prompt = code_prompt_texts[1].lower() case "Y": user_prompt = code_prompt_texts[2].lower() case "N": user_prompt = code_prompt_texts[3].lower() case _: user_prompt = str(user_input).lower() return user_prompt
The code converts the strings to lowercase to prevent mismatches when running the conditional logic. Its match…case
structure matches the user-entered prompts to the outputs. For example, a user entering “1” will trigger the "Contact us"
functionality.
Next, the following function contains an if
statement that uses the Python RegEx package, re
, to search for certain terms from a customer message to decide which type of response to send the user:
def user_message_processor(message, phonenumber, name): user_prompt = extract_string_from_reply(message) if user_prompt == "yes": send_message(message, phonenumber, "TALK_TO_AN_AGENT", name) elif user_prompt == "no": print("Chat terminated") else: if re.search("service", user_prompt): send_message(message, phonenumber, "SERVICE_INTRO_TEXT", name) elif re.search( "help|contact|reach|email|problem|issue|more|information", user_prompt ): send_message(message, phonenumber, "CONTACT_US", name) elif re.search("hello|hi|greetings", user_prompt): if re.search("this", user_prompt): send_message(message, phonenumber, "CHATBOT", name) else: send_message(message, phonenumber, "SEND_GREETINGS_AND_PROMPT", name) else: send_message(message, phonenumber, "CHATBOT", name)
Now, a message such as "Hello there"
will fire the send_message
method with SEND_GREETINGS_AND_PROMPT
as the second argument. Below is the send_message
method. Replace the content between <xxx>
appropriately.
def send_message(message, phone_number, message_option, name): greetings_text_body = ( "\nHello " + name + ". Welcome to our hotel. What would you like us to help you with?\nPlease respond with a numeral between 1 and 2.\n\n1. " + code_prompt_texts[0] + "\n2. " + code_prompt_texts[1] + "\n\nAny other reply will connect you with our chatbot." ) # loading the list's entries into a string for display to the user services_list_text = "" for i in range(len(service_list)): item_position = i + 1 services_list_text = ( f"{services_list_text} {item_position}. {service_list[i]} \n" ) service_intro_text = f"We offer a range of services to ensure a comfortable stay, including but not limited to:\n\n{services_list_text}\n\nWould you like to connect with an agent to get more information about the services?\n\nY: Yes\nN: No" contact_flow_payload = flow_details( flow_header="Contact Us", flow_body="You have indicated that you would like to contact us.", flow_footer="Click the button below to proceed", flow_id=str("<FLOW-ID>"), flow_cta="Proceed", recipient_phone_number=phone_number, screen_id="CONTACT_US", ) agent_flow_payload = flow_details( flow_header="Talk to an Agent", flow_body="You have indicated that you would like to talk to an agent to get more information about the services that we offer.", flow_footer="Click the button below to proceed", flow_id=str("<FLOW-ID>"), flow_cta="Proceed", recipient_phone_number=phone_number, screen_id="TALK_TO_AN_AGENT", ) match message_option: case "SEND_GREETINGS_AND_PROMPT": payload = json.dumps( { "messaging_product": "whatsapp", "to": str(phone_number), "type": "text", "text": {"preview_url": False, "body": greetings_text_body}, } ) case "SERVICE_INTRO_TEXT": payload = json.dumps( { "messaging_product": "whatsapp", "to": str(phone_number), "type": "text", "text": {"preview_url": False, "body": service_intro_text}, } ) case "CHATBOT": LLM = Llama( model_path="/home/incognito/Downloads/llama-2-7b-chat.ggmlv3.q8_0.gguf.bin", n_ctx=2048, ) # create a text prompt prompt = message # generate a response (takes several seconds) output = LLM(prompt) payload = json.dumps( { "messaging_product": "whatsapp", "to": str(phone_number), "type": "text", "text": { "preview_url": False, "body": output["choices"][0]["text"], }, } ) case "CONTACT_US": payload = contact_flow_payload case "TALK_TO_AN_AGENT": payload = agent_flow_payload case "FLOW_RESPONSE": payload = json.dumps( { "messaging_product": "whatsapp", "to": str(phone_number), "type": "text", "text": {"preview_url": False, "body": message}, } ) headers = { "Content-Type": "application/json", "Authorization": "Bearer " + ACCESS_TOKEN, } requests.request("POST", url, headers=headers, data=payload) print("MESSAGE SENT")
If the message is a simple greeting (SEND_GREETINGS_AND_PROMPT
), the response contains additional prompts (greetings_text_body
).
In the same fashion, when a user asks a question about the services offered, a text message (service_intro_text
) containing the services is sent to the user. In addition to the services, it also contains a prompt to the user to choose if they want to talk to an agent.
If the entry requires a chatbot response (CHATBOT
), you initialize the model, feed it the message contents, and process the response to send back to the user. FLOW_RESPONSE
displays a Flow’s captured response.
The other options, CONTACT_US
and TALK_TO_AN_AGENT
, send Flow payloads to the user. The Flow payloads originate from the flow_details
function, whose body is shown below. The payload incorporates essential Flow details, including the FLOW_ID
, which you can retrieve from the Flows page of your WhatsApp Business account. It’s also possible to define these IDs within your environment variables.
def flow_details(flow_header, flow_body, flow_footer, flow_id, flow_cta, recipient_phone_number, screen_id ): # Generate a random UUID for the flow token flow_token = str(uuid.uuid4()) flow_payload = json.dumps({ "type": "flow", "header": { "type": "text", "text": flow_header }, "body": { "text": flow_body }, "footer": { "text": flow_footer }, "action": { "name": "flow", "parameters": { "flow_message_version": "3", "flow_token": flow_token, "flow_id": flow_id, "flow_cta": flow_cta, "flow_action": "navigate", "flow_action_payload": { "screen": screen_id } } } }) payload = json.dumps({ "messaging_product": "whatsapp", "recipient_type": "individual", "to": str(recipient_phone_number), "type": "interactive", "interactive": json.loads(flow_payload) }) return payload
This method creates the action.parameters.flow_token
by generating a random UUID. The action.parameters.flow_action_payload.screen
is passed as a parameter (screen_id
). Ideally, it should represent the ID of the initial screen you intend to show the user when the action.parameters.flow_cta
executes.
Finally, add the webhook routes. The webhook GET
request initiates when adding the webhook to your app on Meta for Developers. It returns the request’s hub.challenge
when successful.
@app.route("/webhook", methods=["GET"]) def webhook_get(): if request.method == "GET": if ( request.args.get("hub.mode") == "subscribe" and request.args.get("hub.verify_token") == TOKEN ): return make_response(request.args.get("hub.challenge"), 200) else: return make_response("Success", 403)
The POST
request extracts and processes the message payload using the user_message_processor
method introduced earlier. Because the code only caters to the message payload, it will raise errors upon capturing any other payload. For this reason, you can use an if
statement to check whether there’s a messages
body.
@app.route("/webhook", methods=["POST"]) def webhook_post(): if request.method == "POST": request_data = json.loads(request.get_data()) if ( request_data["entry"][0]["changes"][0]["value"].get("messages") ) is not None: name = request_data["entry"][0]["changes"][0]["value"]["contacts"][0][ "profile" ]["name"] if ( request_data["entry"][0]["changes"][0]["value"]["messages"][0].get( "text" ) ) is not None: message = request_data["entry"][0]["changes"][0]["value"]["messages"][ 0 ]["text"]["body"] user_phone_number = request_data["entry"][0]["changes"][0]["value"][ "contacts" ][0]["wa_id"] user_message_processor(message, user_phone_number, name) else: # checking that there is data in a flow's response object before processing it if ( request_data["entry"][0]["changes"][0]["value"]["messages"][0][ "interactive" ]["nfm_reply"]["response_json"] ) is not None: flow_reply_processor(request) return make_response("PROCESSED", 200)
Additionally, you can use a helper function called flow_reply_processor
to extract the response from the Flow and send it back to the user:
def flow_reply_processor(request): request_data = json.loads(request.get_data()) name = request_data["entry"][0]["changes"][0]["value"]["contacts"][0]["profile"]["name"] message = request_data["entry"][0]["changes"][0]["value"]["messages"][0]["interactive"]["nfm_reply"][ "response_json"] flow_message = json.loads(message) flow_key = flow_message["flow_key"] if flow_key == "agentconnect": firstname = flow_message["firstname"] reply = f"Thank you for reaching out {firstname}. An agent will reach out to you the soonest" else: firstname = flow_message["firstname"] secondname = flow_message["secondname"] issue = flow_message["issue"] reply = f"Your response has been recorded. This is what we received:\n\n*NAME*: {firstname} {secondname}\n*YOUR MESSAGE*: {issue}" user_phone_number = request_data["entry"][0]["changes"][0]["value"]["contacts"][0][ "wa_id"] send_message(reply, user_phone_number, "FLOW_RESPONSE", name)
It uses a key(flow_key
) to differentiate between the two Flows and thereby extracting the responses appropriately while passing in the correct first screen IDs.
Before running the code, compare it with the full version and confirm that everything matches.
How to Set Up the Webhook
Before proceeding, run this command from your terminal:
flask --app main run --port 5000
If successful, you should see a message that reads:
* Running on http://127.0.0.1:5000
Next, run ngrok http 5000
to obtain a URL that maps to your application. Copy the link.
Then, in your developer account on Meta for Developers, click the Configuration menu under WhatsApp in the left navigation pane:
In the Webhook card, click Edit.
Then, in the Callback URL field of the dialog that opens, paste the copied URL and append /webhook
to it.
Add the token from your .env
file’s TOKEN
variable into the Verify token field. Click Verify and save to close the dialog.
Now, from the same card, click Manage and check the messages field. The card should appear as follows:
The webhook is now ready.
Running the Application
You can send a message like “Hello” to your account number. You should receive the appropriate response. Try replying with a prompt shown in the menu to test the Flows:
Below is another screenshot showing the chatbot’s response. The user asks for a quick translation, which the bot can provide.
Conclusion
WhatsApp Flows are a powerful tool for businesses to collect structured information, enhancing customer interactions and streamlining communication between businesses and consumers. One way to build Flows is with WhatsApp Manager Flow Builder, which offers a user-friendly interface for designing Flows.
This demo application demonstrates how you can leverage WhatsApp Flows for improved customer engagement and data-driven analysis. For more specialized uses, you can also configure Llama 2 and WhatsApp Flows to connect your chatbot to a custom model or train it on a proprietary data source, enabling it to answer natural language questions about your product and other functionalities.
Utilize WhatsApp Flows to elevate customer interactions and streamline data collection in your applications today.