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.
Para proceder, asegúrate de contar con lo siguiente:
Si quieres obtener una vista previa del proyecto, puedes ver el código completo.
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.
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.
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.
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.
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.
Puedes explorar los detalles de los elementos JSON en la documentación para desarrolladores.
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.
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
.
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 ngrok
http 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.
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 .env
TOKEN
.
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.
El webhook ya está listo.
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.
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.
El usuario recibe un mensaje con sus respuestas tras completar la 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.
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.