Volver a las novedades para desarrolladores

Creating Surveys with WhatsApp Flows

6 de marzo de 2024DeGafi G & Iryna Wagner

WhatsApp Flows permite optimizar y simplificar la forma en la que tu empresa recopila datos de los clientes. Tu organización podrá obtener información estructurada a partir de interacciones con los clientes que, a cambio, disfrutarán de una agradable experiencia de usuario en WhatsApp. WhatsApp Flows sirve para recopilar datos sobre la generación de clientes potenciales, realizar encuestas, ayudar a los clientes a reservar citas, enviar dudas y preguntas de los clientes, y mucho más.

Y lo mejor de todo es que te permite ofrecerle a los clientes todas estas opciones sin necesidad de back-end o una aplicación compleja. Solo tendrás que usar WhatsApp como front-end y un webhook para obtener respuestas en forma de mensajes JSON, procesar la información y recuperar los datos necesarios.

A través del ejemplo de una empresa ficticia, en este tutorial mostramos cómo configurar una encuesta para clientes en WhatsApp mediante webhooks. Para que la empresa pueda prestar un mejor servicio a los clientes actuales y futuros, la encuesta recopilará información como los tipos de tours favoritos de los clientes o cómo descubrieron la empresa.

Realización de encuestas con WhatsApp Flows

Requisitos previos

Para continuar, asegúrate de que dispones de lo siguiente:

El proceso

  1. Crea una aplicación Flask.
  2. Escribe el código Python para crear y publicar Flows con la API de WhatsApp Flows. El código Python también se enviará al Flow publicado mediante la API de nube.
  3. Crea un webhook para escuchar los mensajes de chat.
  4. Publica la aplicación.

Si quieres obtener una vista previa del proyecto, puedes acceder al código completo.

Creación de una encuesta con la API de WhatsApp Flows

Hay dos formas de crear un Flow: con la interfaz de usuario de Flow Builder o con la API de Flows. En este tutorial se emplea la API de Flows para configurar la encuesta mediante programación.

Para desarrollar un Flow que emplee datos dinámicos de tu servidor, puedes crear un extremo que conecte la encuesta con tu propio servidor. Al hacerlo, podrás controlar la lógica de navegación entre las pantallas del Flow, poblar datos del Flow desde tu servidor o mostrar u ocultar componentes en la pantalla según la interacción de los usuarios.

El ejemplo de Flow de encuesta que se aborda no utiliza ningún extremo, ya que no existe ningún intercambio de datos dinámicos entre él y un servidor. Utilizarás el webhook de chat para obtener la información de la encuesta. Además, podrás adjuntar Flows a una plantilla de mensajes en el Administrador de WhatsApp.

Crea una aplicación Flask

En primer lugar, crea una aplicación Flask para interactuar con la API de Flows. Publica el siguiente comando en tu terminal para crear un entorno virtual.

python -m venv venv
        

A continuación, utiliza el siguiente comando para activar el entorno.

source venv/bin/activate
        

Después, usa este comando para instalar los paquetes necesarios.

pip install requests flask python-dotenv
        

Usarás Flask para crear rutas e interactuar con la API de Flows, peticiones para enviar solicitudes HTTP y python-dotenv para cargar variables de entorno.

Ahora crea un archivo de entorno que se llame .env y pega en él la siguiente información.

VERIFY_TOKEN =
ACCESS_TOKEN =
WHATSAPP_BUSINESS_ACCOUNT_ID =
PHONE_NUMBER_ID =
        

Asigna los valores según la información de tu cuenta de desarrollador. Puedes usar cualquier cadena para VERIFY_TOKEN. Las variables WHATSAPP_BUSINESS_ACCOUNT_ID y PHONE_NUMBER_ID son identificadores exclusivos de tu cuenta generados por Meta. The ACCESS_TOKEN sirve para autenticar y autorizar las solicitudes de la API.

Para acceder a esta información desde el panel de tu aplicación de Meta, haz clic en WhatsApp > API Setup (WhatsApp > Configuración de la API)en el panel de navegación de la izquierda, tal y como se muestra en la siguiente captura de pantalla.

Vista previa de la configuración de la API

Por último, en el mismo directorio, crea un archivo que se llame main.py que contenga la lógica de Python para crear Flows y el webhook.

Creación del Flow

Para crear el Flow, lo primero que debes hacer es añadir los siguientes paquetes a main.py.

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

Después, añade este fragmento de código a main.py para iniciar las variables. El fragmento también inicia Flask y llama al método load_dotenv() para que le ayude a cargar las variables.

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

A continuación, añade la siguiente ruta para procesar la creación de un Flow.

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

La función llama al extremo de Flows (flow_base_url) a la vez que proporciona la carga útil(flow_creation_payload) que contiene el nombre y la categoría del Flow. Los posibles valores de la categoría son los siguientes: SIGN_UP, SIGN_IN, APPOINTMENT_BOOKING, LEAD_GENERATION, CONTACT_US, CUSTOMER_SUPPORT, SURVEY u OTHER.

Sustituye <FLOW-NAME> por el nombre que prefieras; por ejemplo, survey_flow.

Una vez que el código cree el Flow, extrae created_flow_id para cargar su cuerpo de JSON.

Carga de componentes de JSON del Flow

Crea un archivo survey.json con este contenido. JSON contiene la estructura del Flow.

Después, pega el siguiente código en el archivo 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())

Esa función carga los datos de JSON de survey.json en el extremo de los activos del Flow.

En el siguiente fragmento de código, cuando el usuario active la acción de clic, se iniciará on-click-action y se recopilarán datos de la carga útil. El campo "name": "complete" indica que el Flow está completo. Se cerrará y la carga útil se enviará a tu servidor de 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}"
    }
}

...
        

Los valores de los objetos de la carga útil pueden corresponderse con componentes del Flow (que recuerdan a nombres de elementos en formularios HTML) u objetos de datos. Las claves asociadas a estos valores de la carga útil reciben nombres, del mismo que asignas variables en lenguajes de programación.

Los elementos data-source también contienen identificadores que actúan como claves para los valores. El código envía estos identificadores a las opciones. Por ejemplo, si el usuario elige Likely para data-source, el código envía 1. Una vez recibas los datos, podrás emparejar los orígenes de datos.

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

El Flow cuenta con una pantalla que contiene nueve preguntas con varias opciones, como se muestra abajo.

Flow de la encuesta

Puedes explorar los detalles de los elementos de JSON en la documentación para desarrolladores.

Publicación del Flow

Ahora pega la siguiente función en el archivo main.py para añadir la lógica y publicar el Flow. Los Flows publicados están listos para producción, así que no podrás hacer más cambios.

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)
        

La función invoca el extremo de publicación a la vez que proporciona el identificador del Flow.

Envío del Flow

Pega la siguiente función en el archivo main.py para enviar el Flow a un usuario de WhatsApp. La función llama al extremo de mensajes de la API de nube a la vez que proporciona la carga útil del Flow.

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

La carga útil del Flow contiene los detalles del Flow. El campo action.parameters.flow_token te permite proporcionarle un identificador único al mensaje del Flow que se transmitirá del cliente a tu webhook cuando se complete el Flow. Para este tutorial hemos usado un identificador aleatorio (uuid). El código establece action.parameters.flow_action_payload.screen como SURVEY_SCREEN, que es el identificador de la pantalla que quieres mostrar cuando el usuario haya clic en action.parameters.flow_cta.

Configuración del webhook

La lógica del webhook es bastante clara. Consta de dos funciones, webhook_get y webhook_post, que procesan solicitudes GET y POST respectivamente. El código utiliza la solicitud GET al añadir el webhook a tu aplicación de Meta. Si se produce con éxito, devuelve hub.challenge de la solicitud. La solicitud POST imprime la carga útil del mensaje en el 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)
        

La solicitud POST extrae y procesa la carga útil del mensaje. Dado que el código solo se adapta a la carga útil del mensaje, detecta errores al capturar otra carga útil. Por este motivo, utilizas la función if para comprobar si hay un cuerpo de messages. Tras comprobar que el cuerpo de JSON messages está presente, se realiza otra comprobación para extraer el número de teléfono del remitente solo si hay un cuerpo text en la carga útil de 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)
        

Además, deberías utilizar la siguiente función del asistente, denominada flow_reply_processor, para extraer la respuesta del Flow y devolverla al usuario. Dado que la respuesta del Flow contiene el identificador de opción seleccionado al capturar datos de RadioButtonsGroups, la función empareja los identificadores con los valores de las cadenas correspondientes.

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

La función envía la respuesta usando el extremo del mensaje de texto de la API.

La aplicación se debe publicar antes de configurar el webhook en la consola de Meta for Developers. Por tanto, usa el comando flask --app main run --port 5000 en tu terminal para publicar el código. Deberías ver el mensaje * Running on http://127.0.0.1:5000 en tu terminal si lo has configurado correctamente.

Después, publica el comando ngrokhttp 5000 en tu terminal para obtener una URL para tu aplicación. Copia el enlace.

Desde la consola de Meta for Developers, en la sección WhatsApp del panel de navegación de la izquierda, haz clic en Configuration (Configuración).

Configuración de WhatsApp

En el apartado Webhook, haz clic en Edit (Editar). Después, en el cuadro de diálogo del campo Callback URL (URL de devolución de llamada), añade la URL copiada y adjunta /webhook. En el campo Verify token (Verificar token), añade el token de tu variable TOKEN del archivo .env.

Cuando hayas acabado, haz clic en Verify and save (Verificar y guardar). Se cerrará el cuadro de diálogo. Haz clic en Manage (Administrar) y comprueba el campo messages (mensajes). La información debería ser parecida a la de la imagen de abajo y mostrar Callback URL (URL de devolución de llamada), información oculta en Verify token (Verificar token) y messages (mensajes) en Webhook fields (Campos del webhook).

Configuración de WhatsApp

El webhook ya está listo.

Publicación de la aplicación

En una nueva instancia de terminal, ejecuta el siguiente comando cURL para crear un Flow.

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

En la instancia de terminal que muestra tu salida de webhook, deberías ver un mensaje parecido al que aparece abajo.

Publicación de la aplicación

Cuando un usuario envíe un mensaje a tu número de WhatsApp, recibirá el Flow como respuesta, tal y como se muestra en esta captura de pantalla.

Ejemplo de mensaje de la encuesta del Flow

Recibirá una contestación con sus respuestas tras completar la encuesta.

Ejemplo de respuesta de la encuesta del Flow

Cualquier mensaje que un usuario envíe a tu número activará el Flow. Personaliza tu código para enviar el Flow de encuesta únicamente en situaciones concretas; por ejemplo, después de que un usuario haya chateado con tu empresa.

Conclusión

WhatsApp Flows mejora la experiencia de usuario gracias a interfaces interactivas con las que recopila datos estructurados, como respuestas a encuestas para una hipotética empresa de tours. La empresa crea una aplicación Flask, implementa el código Python, despliega Flows mediante la API de WhatsApp Flows, configura un webhook para escuchar los mensajes entrantes y publica la aplicación para activar la recopilación de respuestas de la encuesta.

WhatsApp Flows permite a tu organización recopilar datos de forma rápida y sencilla, lo cual puede ayudarte a mejorar los porcentajes de finalización de las interacciones con los clientes. Utiliza un proceso similar para configurar tus propias encuestas, ofrecer asistencia a los clientes, ayudarlos a reservar una cita y mucho más.

Sigue explorando el potencial de WhatsApp Flows. Pruébalo.