Назад к новостям для разработчиков

Создание опросов с помощью WhatsApp Flows

Платформа WhatsApp Flows позволяет компаниям оптимизировать и упростить сбор данных о клиентах. Вы сможете легко собирать структурированную информацию о клиентах, а они будут получать положительные впечатления от взаимодействия с вашей организацией в WhatsApp. Используйте платформу WhatsApp Flows, чтобы собирать данные для генерации лидов, проводить опросы, помогать клиентам оформлять бронирования, реагировать на их вопросы и жалобы и т. д.

Чтобы предложить все эти услуги клиентам, не нужно создавать отдельное приложение и заниматься бэкенд-разработкой. Просто используйте WhatsApp в качестве фронтенд-платформы и задействуйте Webhook для сбора ответов в качестве JSON-сообщений, обработки информации и извлечения необходимых данных.

Давайте рассмотрим на примере выдуманной компании, как создать опрос для клиентов в WhatsApp с помощью Webhook. В ходе опроса, кроме прочего, можно выяснить, как клиенты узнали о компании и какие типы туров они предпочитают. Так компания сможет оптимизировать обслуживание текущих и будущих клиентов.

Проведение опроса с помощью WhatsApp Flows

Предварительные требования

Чтобы приступить к работе, убедитесь, что:

Процесс

  1. Создайте приложение Flask.
  2. Напишите код Python для создания и публикации сценариев с помощью WhatsApp Flows API. Код Python также отправит опубликованный сценарий с помощью Cloud API.
  3. Создайте Webhook для прослушивания сообщений в чате.
  4. Запустите приложение.

Вы также можете просмотреть полный код проекта.

Создание опроса с помощью WhatsApp Flows API

Есть два способа создать сценарий: с помощью Flow Builder UI или Flows API. В этой статье описано, как создавать опрос с помощью программирования и Flows API.

Если вы хотите, чтобы сценарий использовал динамические данные с вашего сервера, создайте конечную точку, которая свяжет опрос с вашим сервером. С помощью конечной точки вы сможете контролировать логику навигации между экранами сценария, извлекать данные сценария со своего сервера, а также показывать и скрывать компоненты на экранах в зависимости от действий пользователя.

В рассматриваемом примере не используется конечная точка, поскольку сценария для опроса и сервер не обмениваются динамическими данными. Для сбора информации из опроса вы будете использовать Webhook чата. Вы также можете прикрепить сценарий к шаблону сообщения в WhatsApp Manager.

Создание приложения Flask

Сначала создайте приложение Flask для взаимодействия с Flows API. Выполните в терминале указанную ниже команду, чтобы создать виртуальную среду.

python -m venv venv
        

Выполните команду ниже, чтобы активировать среду.

source venv/bin/activate
        

Выполните команду ниже, чтобы установить необходимые пакеты.

pip install requests flask python-dotenv
        

Вы будете использовать Flask для создания маршрутов и взаимодействия с Flows API, запросы для отправки HTTP-запросов и пакет python-dotenv для загрузки переменных среды.

Теперь создайте файл среды с названием .env и вставьте указанную ниже информацию.

VERIFY_TOKEN =
ACCESS_TOKEN =
WHATSAPP_BUSINESS_ACCOUNT_ID =
PHONE_NUMBER_ID =
        

Укажите значения на основе информации своего аккаунта разработчика. Для VERIFY_TOKEN можно использовать любую строку. Переменные WHATSAPP_BUSINESS_ACCOUNT_ID и PHONE_NUMBER_ID — это уникальные идентификаторы вашего аккаунта, автоматически сгенерированные Meta. The ACCESS_TOKEN служит для аутентификации и авторизации запросов API.

Чтобы получить доступ к информации с панели вашего приложения Meta, нажмите WhatsApp > Настройка API на панели навигации в левой части экрана, как показано на скриншоте ниже.

Предпросмотр настройки API

Наконец, в том же каталоге создайте файл main.py. Он будет содержать логику Python для создания сценариев и Webhook.

Создание сценария

Чтобы создать сценарий, сначала добавьте в файл main.py указанные ниже пакеты.

import os
import uuid
import requests
from dotenv import load_dotenv
from flask import Flask, request, make_response, json
        

Далее добавьте указанный ниже фрагмент кода в файл main.py, чтобы инициализировать переменные. Этот фрагмент также инициализирует Flask и вызывает пакет load_dotenv(), чтобы загрузить переменные.

app = Flask(__name__)

load_dotenv()
PHONE_NUMBER_ID = os.getenv('PHONE_NUMBER_ID')
VERIFY_TOKEN = os.getenv('VERIFY_TOKEN')
ACCESS_TOKEN = os.getenv('ACCESS_TOKEN')
WHATSAPP_BUSINESS_ACCOUNT_ID = os.getenv('WHATSAPP_BUSINESS_ACCOUNT_ID')
created_flow_id = ""
messaging_url = f"https://graph.facebook.com/v18.0/{PHONE_NUMBER_ID}/messages"

auth_header = {"Authorization": f"Bearer {ACCESS_TOKEN}"}

messaging_headers = {
    "Content-Type": "application/json",
    "Authorization": f"Bearer {ACCESS_TOKEN}",
}
        

Добавьте указанный ниже маршрут в обработчик, который создаст сценарий.

@app.route("/create-flow", methods=["POST"])
def create_flow():
    flow_base_url = (
        f"https://graph.facebook.com/v18.0/{WHATSAPP_BUSINESS_ACCOUNT_ID}/flows"
    )
    flow_creation_payload = {"name": "<FLOW-NAME>", "categories": '["SURVEY"]'}
    flow_create_response = requests.request(
        "POST", flow_base_url, headers=auth_header, data=flow_creation_payload
    )

    try:
        global created_flow_id
        created_flow_id = flow_create_response.json()["id"]
        graph_assets_url = f"https://graph.facebook.com/v18.0/{created_flow_id}/assets"

        upload_flow_json(graph_assets_url)
        publish_flow(created_flow_id)

        print("FLOW CREATED!")
        return make_response("FLOW CREATED", 200)
    except:
        return make_response("ERROR", 500)

Функция вызывает конечную точку сценария (flow_base_url) в то время, как передает полезные данные (flow_creation_payload) с названием и категорией сценария. Возможные значения категории: SIGN_UP, SIGN_IN, APPOINTMENT_BOOKING, LEAD_GENERATION, CONTACT_US, CUSTOMER_SUPPORT, SURVEY или OTHER.

Замените <FLOW-NAME> нужным названием, например survey_flow.

После того как код создаст сценарий, он извлечет идентификатор сценария created_flow_id для загрузки его основного текста JSON.

Загрузка JSON-компонентов сценария

Создайте файл survey.json с этим содержанием. JSON содержит структуру сценария.

Далее вставьте следующий код в файл main.py:

def upload_flow_json(graph_assets_url):
    flow_asset_payload = {"name": "flow.json", "asset_type": "FLOW_JSON"}
    files = [("file", ("survey.json", open("survey.json", "rb"), "application/json"))]

    res = requests.request(
        "POST",
        graph_assets_url,
        headers=auth_header,
        data=flow_asset_payload,
        files=files,
    )
    print(res.json())

Эта функция загрузит данные JSON из файла survey.json в конечные точки объектов сценария.

Во фрагменте кода ниже действие нажатия вызывает функцию on-click-action. Выполняется сбор полезных данных. Поле "name": "complete" указывает на то, что сценарий готов. Он закроется, а полезные данные будут отправлены на сервер вашего Webhook.

...
"on-click-action": {
    "name": "complete",
    "payload": {
        "source": "${form.source}",
        "tour_type": "${form.tour_type}",
        "tour_quality": "${form.tour_quality}",
        "decision_influencer": "${form.decision_influencer}",
        "tour_guides": "${form.tour_guides}",
        "aspects_enjoyed": "${form.aspects_enjoyed}",
        "improvements": "${form.improvements}",
        "recommend": "${form.recommend}",
        "return_booking": "${form.return_booking}"
    }
}

...
        

Значения в объектах полезных данных могут соответствовать компонентам сценария (напоминая названия элементов в HTML-формах) или объектам данных. Ключи, связанные с этими значениями полезных данных, называются именами, как при присвоении переменных в языках программирования.

Элементы data-source также содержат ID, которые выступают в роли ключей к значениям. Код отправляет эти ID в зависимости от выбора пользователя. Например, если пользователь выбирает Likely для элемента data-source ниже, то код отправляет значение 1. Вы можете сопоставить полученную информацию с источниками данных.

...
{
    "type": "RadioButtonsGroup",
    "required": true,
    "name": "return_booking",
    "data-source": [
        {
            "id": "0",
            "title": "Very likely"
        },
        {
            "id": "1",
            "title": "Likely"
        },
        {
            "id": "2",
            "title": "Undecided"
        },
        {
            "id": "3",
            "title": "Unlikely"
        },
        {
            "id": "4",
            "title": "Very likely"
        }
    ]
}
...
        

В сценарии есть один экран с девятью вопросами и несколькими вариантами ответа (как показано ниже).

Сценарий "Опрос"

Вы можете узнать больше об элементах JSON в документации для разработчиков.

Публикация сценария

Далее вставьте функцию ниже в файл main.py, чтобы добавить логику публикации в сценарий. Опубликованный сценарий готов к использованию, поэтому вам не требуется вносить изменения.

def publish_flow(flow_id):
    flow_publish_url = f"https://graph.facebook.com/v18.0/{flow_id}/publish"
    requests.request("POST", flow_publish_url, headers=auth_header)
        

Эта функция вызовет конечную точку публикации, передавая при этом ID сценария.

Отправка сценария

Вставьте указанную ниже функцию в файл main.py, чтобы отправить сценарий пользователю WhatsApp. Эта функция вызовет конечную точку сообщений Cloud API, передавая при этом полезные данные сценария:

def send_flow(flow_id, recipient_phone_number):
    # Generate a random UUID for the flow token
    flow_token = str(uuid.uuid4())

    flow_payload = json.dumps(
        {
            "type": "flow",
            "header": {"type": "text", "text": "Survey"},
            "body": {
                "text": "Your insights are invaluable to us – please take a moment to share your feedback in our survey."
            },
            "footer": {"text": "Click the button below to proceed"},
            "action": {
                "name": "flow",
                "parameters": {
                    "flow_message_version": "3",
                    "flow_token": flow_token,
                    "flow_id": flow_id,
                    "flow_cta": "Proceed",
                    "flow_action": "navigate",
                    "flow_action_payload": {"screen": "SURVEY_SCREEN"},
                },
            },
        }
    )

    payload = json.dumps(
        {
            "messaging_product": "whatsapp",
            "recipient_type": "individual",
            "to": str(recipient_phone_number),
            "type": "interactive",
            "interactive": json.loads(flow_payload),
        }
    )

    requests.request("POST", messaging_url, headers=messaging_headers, data=payload)
    print("MESSAGE SENT")
        

В полезных данных сценария содержатся сведения о нем. Поле action.parameters.flow_token позволяет передать уникальный идентификатор для сообщения сценария, которое отправится от клиента в ваш Webhook, как только клиент пройдет сценарий. В рассматриваемом примере мы будем использовать случайный ID (uuid). Код устанавливает экран action.parameters.flow_action_payload.screen в качестве экрана SURVEY_SCREEN. Это ID экрана, который будет появляться, когда пользователь нажмет action.parameters.flow_cta.

Настройка Webhook

Логика Webhook довольно проста. В ней есть две функции: webhook_get и webhook_post, которые управляют запросами GET и POST соответственно. Код использует запрос GET, когда Webhook добавляется в ваше приложение Meta. Если запрос успешный, он возвращает hub.challenge запроса. Запрос POST записывает полезные данные сообщения в терминал.

@app.route("/webhook", methods=["GET"])
def webhook_get():
    if (
        request.args.get("hub.mode") == "subscribe"
        and request.args.get("hub.verify_token") == VERIFY_TOKEN
    ):
        return make_response(request.args.get("hub.challenge"), 200)
    else:
        return make_response("Success", 403)
        

Запрос POST извлекает и обрабатывает полезные данные сообщения. Поскольку код работает только с полезными данными сообщения, он возвращает ошибку, если получает другие полезные данные. В связи с этим нужно использовать утверждение if, чтобы проверить наличие основного текста сообщения (messages). После проверки наличия основного текста JSON в сообщении (messages) выполняется другая проверка для извлечения номера телефона отправителя. Это происходит, только если в полезных данных сообщения (messages) есть основной текст (text).

@app.route("/webhook", methods=["POST"])
def webhook_post():
    # checking if there is a messages body in the payload
    if (
        json.loads(request.get_data())["entry"][0]["changes"][0]["value"].get(
            "messages"
        )
    ) is not None:
        """
        checking if there is a text body in the messages payload so that the sender's phone number can be extracted from the message
        """
        if (
            json.loads(request.get_data())["entry"][0]["changes"][0]["value"][
                "messages"
            ][0].get("text")
        ) is not None:
            user_phone_number = json.loads(request.get_data())["entry"][0]["changes"][
                0
            ]["value"]["contacts"][0]["wa_id"]
            send_flow(created_flow_id, user_phone_number)
        else:
            flow_reply_processor(request)

    return make_response("PROCESSED", 200)
        

Также следует использовать вспомогательную функцию flow_reply_processor, чтобы извлечь ответ из сценария и отправить его назад пользователю. Поскольку ответ на вопрос сценария содержит ID выбранного варианта при сборе данных из RadioButtonsGroups, функция сопоставляет ID с соответствующими значениями строк.

def flow_reply_processor(request):
    flow_response = json.loads(request.get_data())["entry"][0]["changes"][0]["value"][
        "messages"
    ][0]["interactive"]["nfm_reply"]["response_json"]

    flow_data = json.loads(flow_response)
    source_id = flow_data["source"]
    tour_type_id = flow_data["tour_type"]
    tour_quality_id = flow_data["tour_quality"]
    decision_influencer_id = flow_data["decision_influencer"]
    tour_guides_id = flow_data["tour_guides"]
    aspects_enjoyed_id = flow_data["aspects_enjoyed"]
    improvements_id = flow_data["improvements"]
    recommend_id = flow_data["recommend"]
    return_booking_id = flow_data["return_booking"]

    match source_id:
        case "0":
            source = "Online search"
        case "1":
            source = "Social media"
        case "2":
            source = "Referral from a friend/family"
        case "3":
            source = "Advertisement"
        case "4":
            source = "Others"

    match tour_type_id:
        case "0":
            tour_type = "Cultural tour"
        case "1":
            tour_type = "Adventure tour"
        case "2":
            tour_type = "Historical tour"
        case "3":
            tour_type = "Wildlife tour"

    match tour_quality_id:
        case "0":
            tour_quality = "1 - Poor"
        case "1":
            tour_quality = "2 - Below Average"
        case "2":
            tour_quality = "3 - Average"
        case "3":
            tour_quality = "4 - Good"
        case "4":
            tour_quality = "5 - Excellent"

    match decision_influencer_id:
        case "0":
            decision_influencer = "Positive reviews"
        case "1":
            decision_influencer = "Pricing"
        case "2":
            decision_influencer = "Tour destinations offered"
        case "3":
            decision_influencer = "Reputation"

    match tour_guides_id:
        case "0":
            tour_guides = "Knowledgeable and friendly"
        case "1":
            tour_guides = "Knowledgeable but not friendly"
        case "2":
            tour_guides = "Friendly but not knowledgeable"
        case "3":
            tour_guides = "Neither of the two"
        case "4":
            tour_guides = "I didn’t interact with them"

    match aspects_enjoyed_id:
        case "0":
            aspects_enjoyed = "Tourist attractions visited"
        case "1":
            aspects_enjoyed = "Tour guide's commentary"
        case "2":
            aspects_enjoyed = "Group dynamics/interaction"
        case "3":
            aspects_enjoyed = "Activities offered"

    match improvements_id:
        case "0":
            improvements = "Tour itinerary"
        case "1":
            improvements = "Communication before the tour"
        case "2":
            improvements = "Transportation arrangements"
        case "3":
            improvements = "Advertisement"
        case "4":
            improvements = "Accommodation quality"

    match recommend_id:
        case "0":
            recommend = "Yes, definitely"
        case "1":
            recommend = "Yes, but with reservations"
        case "2":
            recommend = "No, I would not"

    match return_booking_id:
        case "0":
            return_booking = "Very likely"
        case "1":
            return_booking = "Likely"
        case "2":
            return_booking = "Undecided"
        case "3":
            return_booking = "Unlikely"

    reply = (
        f"Thanks for taking the survey! Your response has been recorded. This is what we received:\n\n"
        f"*How did you hear about our tour company?*\n{source}\n\n"
        f"*Which type of tour did you recently experience with us?*\n{tour_type}\n\n"
        f"*On a scale of 1 to 5, how would you rate the overall quality of the tour?*\n{tour_quality}\n\n"
        f"*What influenced your decision to choose our tour company?*\n{decision_influencer}\n\n"
        f"*How knowledgeable and friendly were our tour guides?*\n{tour_guides}\n\n"
        f"*What aspects of the tour did you find most enjoyable?*\n{aspects_enjoyed}\n\n"
        f"*Were there any aspects of the tour that could be improved?*\n{improvements}\n\n"
        f"*Would you recommend our tour company to a friend or family member?*\n{recommend}\n\n"
        f"*How likely are you to book another tour with us in the future?*\n{return_booking}"
    )

    user_phone_number = json.loads(request.get_data())["entry"][0]["changes"][0][
        "value"
    ]["contacts"][0]["wa_id"]
    send_message(reply, user_phone_number)

After the extraction, the following send_message function sends the responses to the sender.

def send_message(message, phone_number):
    payload = json.dumps(
        {
            "messaging_product": "whatsapp",
            "to": str(phone_number),
            "type": "text",
            "text": {"preview_url": False, "body": message},
        }
    )

    requests.request("POST", messaging_url, headers=messaging_headers, data=payload)
    print("MESSAGE SENT")
        

Функция отправляет ответ с помощью конечной точки сообщения Cloud API.

Прежде чем настраивать Webhook на консоли Meta for Developers, запустите приложение. Используйте в терминале команду flask --app main run --port 5000, чтобы запустить код. Если всё настроено правильно, в терминале появится сообщение * Running on http://127.0.0.1:5000.

Выполните в терминале команду ngrokhttp 5000, чтобы получить URL, связанный с вашим приложением. Скопируйте эту ссылку.

На консоли Meta for Developers под пунктом WhatsApp на панели навигации в левой части экрана нажмите Конфигурация.

Конфигурация WhatsApp

На карточке Webhook нажмите Редактировать. В диалоговом окне в поле URL обратного вызова добавьте скопированный URL и добавьте /webhook. В поле Маркер подтверждения добавьте маркер из переменной TOKEN файла .env.

Когда всё будет готово, нажмите Подтвердить и сохранить. Диалоговое окно закроется. Нажмите Управлять и проверьте поле messages. Информация должна быть похожей на данные на изображении ниже: поле URL обратного вызова, скрытая информация под пунктом Маркер подтверждения и значение messages под пунктом Поля Webhook.

Конфигурация WhatsApp

Настройка Webhook завершена.

Запуск приложения

В новом экземпляре терминала выполните команду cURL (как указано ниже), чтобы создать сценарий.

curl --location --request POST 'http://127.0.0.1:5000/create-flow'
        

В экземпляре терминала, который показывает вывод данных Webhook, появится сообщение, похожее на приведенное ниже.

Запуск приложения

Когда пользователь отправляет сообщение на ваш номер WhatsApp, он получает сценарий "Опрос" в качестве ответа, как показано на скриншоте ниже.

Пример сценария "Опрос"

После прохождения опроса пользователь получит сообщение со своими ответами.

Пример ответа на опрос в сценарии

Любое сообщение, которое отправит пользователь на ваш номер, вызовет сценарий опроса. В вашем случае нужно кастомизировать код так, чтобы опрос отправлялся только в определенных ситуациях, например после завершения беседы между клиентом и компанией.

Заключение

WhatsApp Flows дает возможность использовать интерактивные интерфейсы для сбора информации пользователей — например, через опрос вымышленной туристической компании. Этот процесс улучшает впечатление клиентов от взаимодействия с вашей компанией. Компании просто нужно создать приложение Flask, внедрить код Python для генерации и развертывания сценария опроса через WhatsApp Flows API, настроить Webhook для прослушивания входящих сообщений и запустить приложение для сбора ответов на опрос.

WhatsApp Flows позволяет организации быстро и просто собирать данные, что может повысить показатель завершения взаимодействий с клиентами. Подобным образом можно создать собственный опрос, предлагать поддержку клиентам, помогать им совершать бронирования и многое другое.

Продолжайте исследовать возможности WhatsApp Flows. Скорее попробуйте этот инструмент.