Volver a las novedades para desarrolladores

Creating Surveys with WhatsApp Flows

6 de marzo de 2024DeGafi G & Iryna Wagner

WhatsApp Flows optimiza y simplifica la forma en que tu empresa recopila datos de sus clientes. Tu organización puede incorporar fácilmente información estructurada a partir de las interacciones con los clientes, quienes, a su vez, disfrutan de una experiencia del usuario positiva en WhatsApp. WhatsApp Flows es ideal para recopilar datos para generación de clientes potenciales, realizar encuestas, ayudar a los clientes a reservar citas, permitirles enviar preguntas e inquietudes y mucho más.

Lo mejor de todo es que puedes ofrecerles a tus clientes todas estas opciones sin tener que crear una aplicación y un back-end complejos: simplemente utiliza WhatsApp como front-end y usa un webhook para capturar las respuestas como mensajes JSON, procesar la información y recuperar los datos que necesites.

Este tutorial utiliza una empresa ficticia como ejemplo para explorar la configuración de una encuesta de clientes en WhatsApp usando webhooks. La encuesta te permitirá recopilar comentarios como de qué forma descubrió la empresa el cliente y sus excursiones preferidas para ayudar a la empresa a brindar un mejor servicio a sus clientes, tanto actuales como futuros.

Crear una encuesta con WhatsApp Flows

Requisitos previos

Para proceder, asegúrate de contar con lo siguiente:

El proceso

  1. Crea una app Flask.
  2. Escribe el código Python para crear y publicar los flujos usando la API de WhatsApp Flows. El código Python también enviará el flujo publicado a través de la API de la nube.
  3. Crea un webhook para reproducir los mensajes del chat.
  4. Ejecuta la aplicación.

Si quieres obtener una vista previa del proyecto, puedes ver el código completo.

Crear una encuesta con la API de WhatsApp Flows

Hay dos formas de crear un flujo: usando la UI de Flow Builder o la API de flujos. En este tutorial, se utiliza la API de flujos para configurar la encuesta mediante programación.

Para crear un flujo que utilice datos dinámicos de tu servidor, puedes crear un punto de conexión que conecte la encuesta a tu propio servidor. Un punto de conexión te permite controlar la lógica de navegación entre las pantallas del flujo, completar los datos del flujo de tu servidor o mostrar u ocultar los componentes de la pantalla en función de la interacción con el usuario.

En el ejemplo de flujo de encuesta que se analizará, no se utiliza ningún punto de conexión, ya que no existe un intercambio de datos dinámicos entre este y un servidor. Utilizarás el webhook del chat para capturar la información de la encuesta. Además, puedes adjuntar flujos a una plantilla de mensaje en el administrador de WhatsApp.

Crear una app Flask

Primero, crea una app Flask para interactuar con la API de flujos. Ejecuta el siguiente comando en tu terminal para crear un entorno virtual.

python -m venv venv
        

Luego, usa el siguiente comando para activar el entorno.

source venv/bin/activate
        

A continuación, usa el siguiente comando para instalar los paquetes necesarios.

pip install requests flask python-dotenv
        

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

Ahora, crea un archivo de entorno denominado .env y pega la siguiente información.

VERIFY_TOKEN =
ACCESS_TOKEN =
WHATSAPP_BUSINESS_ACCOUNT_ID =
PHONE_NUMBER_ID =
        

Para asignar los valores, ten en cuenta 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 los identificadores únicos de tu cuenta, generados automáticamente 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 > Configuración de la API en el panel de navegación de la izquierda, como se muestra en la captura de pantalla a continuación.

Obtener vista previa de la configuración de la API

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

Crear el flujo

Para crear el flujo, primero agrega 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
        

Luego, agrega el siguiente fragmento de código a main.py para inicializar las variables. El fragmento también inicia Flask e invoca el método load_dotenv() para ayudar 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}",
}
        

Luego, agrega la siguiente ruta para crear un flujo.

@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 invoca el punto de conexión de los flujos (flow_base_url) a la vez que comunica la carga (flow_creation_payload) que contiene el nombre y la categoría del flujo. Los posibles valores de la categoría son: SIGN_UP, SIGN_IN, APPOINTMENT_BOOKING, LEAD_GENERATION, CONTACT_US, CUSTOMER_SUPPORT, SURVEY o OTHER.

Reemplaza <FLOW-NAME> por el nombre que desees, por ejemplo, survey_flow.

Una vez que el código crea el flujo, extrae created_flow_id para cargar su cuerpo JSON.

Cargar los componentes JSON del flujo

Crea un archivo survey.json con estos contenidos. El archivo JSON contiene la estructura del flujo.

Luego, 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 JSON de survey.json al punto de conexión de los activos del flujo.

En el siguiente fragmento de código, cuando el usuario activa la acción de clic, inicia on-click-action, lo que captura los datos de la carga. El campo "name": "complete" indica que el flujo está completo. Se cerrará y la carga se enviará al servidor del 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 carga pueden corresponderse con componentes del flujo (que se asemejan a los nombres de los elementos en formularios HTML) o con objetos de datos. Las claves asociadas con estos valores de carga se denominan nombres, de manera similar a cómo asignas variables en los lenguajes de programación.

Los elementos data-source también contienen identificadores que actúan como claves de los valores. El código envía estos identificadores según las opciones seleccionadas. Por ejemplo, si el usuario elige Likely para data-source a continuación, el código envía 1. Una vez que recibes los datos, puedes buscar coincidencias entre 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 flujo tiene una pantalla que contiene nueve preguntas de opción múltiple, como se muestra a continuación.

Flujo de encuesta

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

Publicar el flujo

A continuación, pega la siguiente función en el archivo main.py a fin de agregar la lógica para publicar el flujo. Un flujo publicado está listo para producción, así que no podrás incorporar ningún otro cambio.

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 punto de conexión de publicación a la vez que comunica el identificador del flujo.

Enviar el flujo

Pega la siguiente función en el archivo main.py para enviarle el flujo a un usuario de WhatsApp. La función invoca el punto de conexión de los mensajes de la API de la nube a la vez que comunica la carga del flujo.

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 del flujo contiene los detalles del flujo. El campo action.parameters.flow_token te permite comunicar un identificador único del mensaje del flujo que se transmitirá del cliente al webhook una vez completado el flujo. En este tutorial, usarás un identificador aleatorio (uuid). El código define action.parameters.flow_action_payload.screen como SURVEY_SCREEN, que es el identificador de la pantalla que quieres mostrar cuando el usuario hace clic en action.parameters.flow_cta.

Configurar el webhook

La lógica del webhook es bastante simple. Tiene dos funciones, webhook_get y webhook_post, que gestionan las solicitudes de GET y de POST, respectivamente. El código utiliza la solicitud de GET al agregar el webhook a tu app de Meta. Devuelve el hub.challenge de la solicitud cuando se realiza correctamente. La solicitud de POST imprime la carga 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 de POST extrae y procesa la carga del mensaje. Como el código solo se adapta a la carga del mensaje, genera errores al capturar cualquier otra carga. Por este motivo, utilizas una instrucción if para verificar si existe un cuerpo de messages. Tras verificar la presencia de un cuerpo JSON de messages, se realiza otra verificación para extraer el número de teléfono del emisor únicamente si hay un cuerpo de text en la carga 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, es aconsejable usar la siguiente función del asistente denominada flow_reply_processor para extraer la respuesta del flujo y volver a enviarla al usuario. Como la respuesta del flujo contiene el identificador de la opción seleccionada al capturar datos de RadioButtonsGroups, la función busca coincidencias entre los identificadores y los valores de la cadena 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 punto de conexión de los mensajes de texto de la API de la nube.

Es necesario que la aplicación se esté ejecutando antes de configurar el webhook en la consola de Meta for Developers. Por lo tanto, usa el comando flask --app main run --port 5000 en tu terminal para ejecutar el código. Deberías ver el mensaje * Running on http://127.0.0.1:5000 en tu terminal si todo se configuró correctamente.

A continuación, ejecuta el comando ngrokhttp 5000 en tu terminal para obtener una URL que se corresponda con tu aplicación. Copia ese enlace.

En la consola de Meta for Developers, en WhatsApp en el panel de navegación de la izquierda, haz clic en Configuración.

Configuración de WhatsApp

En la tarjeta Webhook, haz clic en Editar. Luego, en el cuadro de diálogo del campo URL de devolución de llamada, agrega la URL copiada y anexa /webhook. En el campo Token de verificación, agrega el token de la variable del archivo .envTOKEN.

Al finalizar, haz clic en Verificar y guardar. Se cerrará el cuadro de diálogo. Haz clic en Administrar y marca el campo mensajes. La información debe verse como la imagen a continuación, con URL de devolución de llamada, la información oculta debajo de Token de verificación y mensajes debajo de Campos del webhook.

Configuración de WhatsApp

El webhook ya está listo.

Ejecutar la aplicación

En una nueva instancia del terminal, ejecuta el comando cURL a continuación para crear un flujo.

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

En la instancia del terminal que muestra la salida del webhook, deberías ver un mensaje similar al que aparece a continuación.

Ejecutar la aplicación

Cuando un usuario envía un mensaje a tu número de WhatsApp, recibe el flujo como respuesta, como se muestra en la siguiente captura de pantalla.

Ejemplo de pregunta del flujo de encuesta

El usuario recibe un mensaje con sus respuestas tras completar la encuesta.

Ejemplo de respuesta del flujo del encuesta

Cualquier mensaje que un usuario envía a tu número activa el flujo. En nuestro caso de uso, personaliza tu código para enviar el flujo de encuesta solo en determinadas situaciones, por ejemplo, después de que un usuario ya chateó con tu empresa.

Conclusión

WhatsApp Flows mejora la experiencia del usuario a través de interfaces interactivas para recopilar datos estructurados, como las respuestas a una encuesta de una empresa de turismo ficticia. La empresa simplemente crea una app Flask, utiliza el código Python para generar flujos y los implementa a través de la API de WhatsApp Flows, configura un webhook para reproducir mensajes entrantes y ejecuta la aplicación para habilitar la recopilación de las respuestas de la encuesta.

WhatsApp Flows le permite a tu organización recopilar datos de manera rápida y sencilla, lo que hace posible mejorar los porcentajes de interacciones con clientes completadas. Utiliza un proceso similar para configurar tus propias encuestas, brindar un servicio de atención al cliente, ayudar al cliente a reservar citas y mucho más.

Sigue explorando el potencial de WhatsApp Flows. Probar.