Back to News for Developers

Adding WhatsApp Flows to Your Chatbot Experience

March 20, 2024ByGafi G & Iryna Wagner

More businesses than ever currently use chatbots via WhatsApp to engage with customers, discern their needs, and gather essential data. However, this process of collecting and organizing data can pose challenges in terms of efficiency. Enter WhatsApp Flows.

Integrating chatbots with WhatsApp Flows lets businesses interpret incoming customer information more effectively. Chatbots can then initiate specific Flows tailored for data collection, depending on the context of the conversation.

In this tutorial, you’ll build a chatbot using Llama 2 and Python, connecting it to WhatsApp Flows to enhance its data collection capabilities. In doing so, you’ll experience how WhatsApp Flows enhances the user-friendliness of the chatbot experience and improves the accuracy and efficiency of customer data collection.

Enhancing the Chatbot Experience with WhatsApp Flows

Your chatbot will respond to user requests and prompts with WhatsApp Flows for data collection. More specifically, it’ll allow users to converse with the chatbot, search for information about services offered by a fictitious hotel in Spain, and contact the company for support.

The chatbot will use simple if statements in conjunction with the standard Llama 2 model to provide access to a sufficient general knowledge base.

Steps You’ll Follow

Part 1:

Part 2:

  • Convert the Llama 2 model from GGML to GGUF.

  • Write the Python code for integrating the Flows and the chatbot.
  • Create a webhook for listening to the messages.
  • Run the application.

Part 1: 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:

Finally, ensure you complete the required steps for using Flows. You can also preview the complete project code.

Getting Started

To begin, navigate to the Flows page in your WhatsApp Business Account. If this is your first interaction with Flows, you should see a button titled Start building Flows. Otherwise, there will be a Create Flow button on the top-right of the page.

Click the displayed button to open a dialog box where you can input some details about your Flow:

Getting started WhatsApp Chatbox

The Services Inquiry Flow

First, you’ll create a Flow that allows the user to seek information on services offered by the hotel company.

Enter a name in the Name field. Then, under the Categories drop-down, choose Sign Up, and leave the Template drop-down set to None. Click Submit.

The next page displays an editor on the left and a preview on the right.

Replace the contents of the editor with the JSON markup for your Flow. (You can learn more about Flow JSON in the developer documentation.)

Save the Flow, and your preview should resemble the image below:

Chatbox flow

The Flow contains one screen that allows the user to enter their details, select the services of interest, and an optional additional message. When the user clicks Submit, the Flow closes and sends the captured data to your business for processing. One method of transmitting this data involves the use of endpoints. However, this project doesn’t need an endpoint. The data will be passed to the same webhook that powers the chatbot.

You can achieve this transfer using actions. You define the data to be passed to the next screen using the payload object:

...

"on-click-action": {
    "name": "complete",
    "payload": {
        "firstname": "${form.first_name}",
        "secondname": "${form.second_name}",
        "services_interested": "${form.services_interested}",
        "additional_info": "${form.additional_info}",
        "flow_key": "agentconnect"
    }
}
...
        

In the snippet, the user’s button-click action triggers the on-click-action, capturing the data in the payload. It then sends the payload to your webhook server and closes the Flow via the complete action.

You can assign the payload keys as you would any variable. The corresponding values can represent data objects or the names of Flow components (similar to an HTML form’s name attribute).

Now, you can see the Flow in action and simulate a real user experience using the Interactive preview toggle:

Chatbox Interactive preview

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:

Chatbox contact us flow

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.

Part 2: 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

The chatbot will operate through a predefined script designed to assist users based on their input. Upon initial interaction, it offers a personalized greeting text and a text-based menu depending on the user’s message. These options cater to specific needs: querying services offered by the hotel, contacting one of its agents, or engaging with a Llama-powered chatbot.

Responding with an alphanumeric character will connect the user to the corresponding service or action. However, any other response triggers the default chatbot functionality, which assists with general inquiries or guides the user through the available services based on further conversation.

To begin, create a virtual environment by running the command below in your terminal:

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).

Chatbox sample greeting

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.

Chatbox sample offers

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:

Chatbox configuration

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:

WhatsApp Chatbox webhook

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:

WhatsApp Chatbox running app

Below is another screenshot showing the chatbot’s response. The user asks for a quick translation, which the bot can provide.

WhatsApp Chatbox running app

Conclusion

WhatsApp Flows serve as a powerful tool for businesses to collect structured information, enhancing customer interactions and streamlining communication between businesses and consumers. One of the ways to build Flows is with WhatsApp Manager Flow Builder, which offers a user-friendly interface for designing Flows.

This demo application offers a glimpse of 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.

Leverage WhatsApp Flows to elevate customer interactions and streamline data collection in your applications today. Try it now.