Voltar para Notícias para desenvolvedores

Creating Surveys with WhatsApp Flows

6 de março de 2024PorGafi G & Iryna Wagner

O WhatsApp Flows otimiza e simplifica como as empresas coletam dados do cliente. Sua organização pode facilmente absorver informações estruturadas provenientes das interações com os clientes, que, por sua vez, desfrutam de uma experiência positiva do usuário. O WhatsApp Flows funciona bem para a coleta de dados de geração de cadastros, realizando pesquisas, ajudando clientes a agendar horários, enviando perguntas e preocupações do cliente e muito mais.

O melhor de tudo é que você pode oferecer aos seus clientes essas opções sem precisar desenvolver um app e back-end complexos. Para isso, basta usar o WhatsApp como front-end e empregar um webhook para capturar as respostas como mensagens JSON, processar as informações e recuperar os dados de que você precisa.

Usando uma empresa fictícia como exemplo, este tutorial explica como configurar uma pesquisa do cliente no WhatsApp usando webhooks. A pesquisa coletará feedbacks para saber como o cliente descobriu a empresa e quais são seus tipos preferidos de passeios. Dessa forma, a empresa poderá atender melhor aos clientes futuros e atuais.

Realizando uma pesquisa com o WhatsApp Flows

Pré-requisitos

Para conseguir acompanhar o tutorial, você deve ter:

O processo

  1. Crie um app Flask.
  2. Escreva o código Python para criar e publicar o fluxo usando a WhatsApp Flows API. O código Python também enviará o fluxo publicado usando a API de Nuvem.
  3. Crie um webhook para ouvir a mensagens de bate-papo.
  4. Execute o app.

Se quiser ver uma prévia do projeto, visualize o código completo.

Criando uma pesquisa com a API do WhatsApp Flows

Há duas formas de criar um fluxo: usando o Flow Builder UI ou a Flows API. Este tutorial usa a Flows API para configurar a pesquisa programaticamente.

Para criar um fluxo que usa dados dinâmicos do seu servidor, você pode criar um ponto de extremidade conectando a pesquisa ao seu próprio servidor. Com um ponto de extremidade, você pode controlar a lógica de navegação entre as telas de fluxo, preencher os dados de fluxo do seu servidor, ou mostrar/ocultar componentes na tela com base na interação com o usuário.

O exemplo de fluxo de pesquisa que será discutido não usa nenhum ponto de extremidade, pois não há troca de dados dinâmicos entre ele e um servidor. Você usará o webhook do bate-papo para coletar as informações da pesquisa. Além disso, pode anexar fluxos a um modelo de mensagem no Gerenciador do WhatsApp.

Crie um app Flask

Primeiro, crie um app Flask para interagir com a Flows API. Execute o comando a seguir no seu terminal para criar um ambiente virtual.

python -m venv venv
        

Depois, use o comando abaixo para ativar o ambiente.

source venv/bin/activate
        

Em seguida, use o comando a seguir para instalar os pacotes necessários.

pip install requests flask python-dotenv
        

Você usará o Flask para criar rotas e interagir com a Flows API, para enviar solicitações HTTP e python-dotenv para carregar variáveis de ambientes.

Agora, crie um arquivo de ambiente chamado .env e cole-o nas seguintes informações.

VERIFY_TOKEN =
ACCESS_TOKEN =
WHATSAPP_BUSINESS_ACCOUNT_ID =
PHONE_NUMBER_ID =
        

Atribua os valores com base nas informações da conta de desenvolvedor. Você pode usar qualquer cadeia de caracteres para VERIFY_TOKEN. As variáveis WHATSAPP_BUSINESS_ACCOUNT_ID e PHONE_NUMBER_ID são os identificadores únicos da sua conta e são gerados automaticamente pela Meta. The ACCESS_TOKEN é para autenticar e autorizar as soluções da API.

Para acessar essas informações no painel do app da Meta, clique em WhatsApp > Configuração da API no painel de navegação esquerdo, como na captura de tela abaixo.

Prévia da configuração da API

Por fim, no mesmo diretório, crie um arquivo chamado main.py para conter a lógica Python para a criação dos fluxos e webhooks.

Criando o fluxo

Para criar o fluxo, primeiro adicione os seguintes pacotes a main.py.

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

Depois, adicione o seguinte trecho de código a main.py para inicializar as variáveis. O trecho também inicia o Flask e chama o método load_dotenv() para ajudar a carregar as variáveis.

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}",
}
        

Em seguida, adicione a seguinte rota para criar um fluxo.

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

A função chama o ponto de extremidade dos fluxos (flow_base_url) enquanto transmite a carga (flow_creation_payload) contendo o nome e a categoria do fluxo. Os possíveis valores da categoria são: SIGN_UP, SIGN_IN, APPOINTMENT_BOOKING, LEAD_GENERATION, CONTACT_US, CUSTOMER_SUPPORT, SURVEY ou OTHER.

Substitua <FLOW-NAME> pelo nome desejado, por exemplo, survey_flow.

Depois que o código criar o fluxo, ele extrairá o created_flow_id para carregar o corpo JSON.

Carregando os componentes JSON do fluxo

Crie um arquivo survey.json com estes conteúdos. O JSON contém a estrutura do fluxo.

Em seguida, cole o seguinte código no arquivo 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())

Essa função carrega os dados JSON de survey.json para o ponto de extremidade dos ativos do fluxo.

No trecho de código abaixo, quando o usuário aciona a ação do clique, ela inicia o on-click-action, coletando dados na carga. O campo "name": "complete" indica que o fluxo está completo. Ele será fechado e a carga será enviada para o servidor do 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}"
    }
}

...
        

Os valores dentro dos objetos de carga podem corresponder aos componentes do fluxo (que lembra nomes de elementos nos formulários de HTML) ou aos objetos de dados. As chaves associadas a esses valores de carga são chamadas de nomes, semelhante a como você atribui variáveis em linguagens de programação.

Os elementos data-source também contêm identificações que agem como chaves para os valores. O código envia essas identificações para as opções. Por exemplo, se o usuário selecionar Likely para data-source abaixo, o código enviará 1. Quando tiver recebido os dados, você poderá correspondê-los com as fontes de dados.

...
{
    "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"
        }
    ]
}
...
        

O fluxo tem uma tela contendo nove perguntas com opções de múltipla escolha, como abaixo.

Fluxo de pesquisa

Você pode explorar os detalhes dos elementos JSON na documentação do desenvolvedor.

Publicando o fluxo

Depois, cole a função abaixo no arquivo main.py para adicionar a lógica para publicar o fluxo. Um fluxo publicado está pronto para produção e você não poderá fazer mais mudanças.

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)
        

A função invoca o ponto de extremidade de publicação enquanto transmite a identificação do fluxo.

Enviando o fluxo

Cole a seguinte função no arquivo main.py para enviar o fluxo a um usuário do WhatsApp. A função chama o ponto de extremidade das mensagens da API de Nuvem enquanto transmite a carga do fluxo.

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

A carga do fluxo contém os detalhes do fluxo. O campo action.parameters.flow_token permite que você transmita um identificador único para a mensagem do fluxo que será enviada do cliente para seu webhook quando o fluxo estiver concluído. Para este tutorial, você usará uma identificação aleatória (uuid). O código define o action.parameters.flow_action_payload.screen como SURVEY_SCREEN, que é a identificação da tela que você quer exibir quando o usuário clica em action.parameters.flow_cta.

Definindo o webhook

A lógica do webhook é bastante objetiva. Ela tem duas funções, webhook_get e webhook_post, que lida com soluções GET e POST respectivamente. O código usa solicitações GET quando adiciona o webhook ao seu app da Meta. Ele retorna o hub.challenge da solicitação quando bem-sucedido. A solicitação POST imprime a carga da mensagem no terminal.

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

A solicitação POST extrai e processa a carga da mensagem. Como o código só atende à carga da mensagem, ele gera erros ao capturar qualquer outra carga. Por esse motivo, você usa uma instrução if para verificar se há um corpo messages. Após verificar se o corpo JSON das messages está presente, outra verificação é realizada para extrair apenas o número de telefone do remetente se houver um corpo text na carga das messages.

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

Além disso, você deve usar a seguinte função auxiliar chamada flow_reply_processor para extrair uma resposta do fluxo e enviá-la para o usuário. Como a resposta do fluxo contém a identificação da opção selecionada ao coletar dados do RadioButtonsGroups, a função corresponde as identificações com valores de cadeia de caracteres correspondentes.

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

A função envia a resposta usando o ponto de extremidade de mensagem de texto da API de Nuvem.

O app precisa estar sendo executado antes de você configurar o webhook no console do Meta for Developers. Portanto, use o comando flask --app main run --port 5000 no seu terminal para executar o código. Você verá a mensagem * Running on http://127.0.0.1:5000 no terminal se tudo estiver configurado corretamente.

Em seguida, execute o comando ngrokhttp 5000 no terminal para obter um URL que mapeia seu app. Copie o link.

No console do Meta for Developers, em WhatsApp no painel de navegação esquerdo, clique em Configuração.

Configuração do WhatsApp

No cartão Webhook, clique em Editar. Depois, na caixa de diálogo no campo URL de retorno, adicione o URL copiado e o /webhook. No campo Verificar token, adicione o token da variável TOKEN do arquivo .env.

Quando tiver concluído, clique em Verificar e salvar. A caixa de diálogo será fechada. Clique em Gerenciar e consulte o campo Mensagens. As informações devem ser semelhantes à imagem abaixo, com o URL de retorno, informações ocultas sob Verificar token, e Mensagens listadas em Campos de webhook.

Configuração do WhatsApp

Agora, o webhook está pronto.

Executando o app

Em uma nova instância terminal, execute o comando cURL abaixo para criar um fluxo.

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

Na instância terminal que exibe a saída do seu webhook, você verá uma mensagem semelhante à mostrada abaixo.

Executando o app

Quando um usuário enviar uma mensagem ao seu número do WhatsApp, ele receberá o fluxo como resposta, conforme mostrado na captura de tela abaixo.

Exemplo de comando do fluxo da pesquisa

Depois de concluir a pesquisa, ele receberá uma cópia com as respostas fornecidas.

Exemplo de resposta do fluxo da pesquisa

Qualquer mensagem que um usuário enviar para seu número aciona o fluxo. Para seu caso de uso, personalize o código para enviar o fluxo da pesquisa apenas em situações específicas, por exemplo, depois que um usuário tiver conversado com sua empresa.

Conclusão

O WhatsApp Flows melhora a experiência do usuário por meio de interfaces interativas para coletar dados estruturados, como respostas a pesquisas para uma empresa de turismo hipotética. A empresa cria um app Flask, implementa um código Python para gerar e implementa os fluxos via a WhatsApp Flows API, configura um webhook para ouvir mensagens recebidas e executa o app para habilitar a coleta de respostas à pesquisa.

Com o WhatsApp Flows, sua empresa pode coletar dados de forma simples e rápida, o que pode ajudar a melhorar as taxas de conclusão para as interações com clientes. Use um processo semelhante para configurar suas próprias pesquisas, oferecer suporte ao cliente, ajudá-los a agendar horários e muito mais.

Continue explorando o potencial do WhatsApp Flows. Experimente agora mesmo.