Torna alle notizie per sviluppatori

Creating Surveys with WhatsApp Flows

6 marzo 2024DiGafi G & Iryna Wagner

WhatsApp Flows ottimizza e semplifica il modo in cui la tua azienda raccoglie i dati dei clienti. La tua organizzazione può raccogliere informazioni strutturate dalle interazioni con i clienti, che in cambio usufruiranno di un'esperienza utente positiva su WhatsApp. WhatsApp Flows è la soluzione ottimale per raccogliere dati per la generazione di contatti, condurre sondaggi, aiutare i clienti a prenotare appuntamenti e permettere loro di condividere domande o dubbi e molto altro.

L'aspetto più interessante è che puoi offrire ai clienti tutte queste opzioni senza dover sviluppare un'app e un back-end complessi. Ti basta usare WhatsApp come front-end e sfruttare un webhook che acquisisca le risposte come messaggi JSON, elabori le informazioni e recuperi i dati di cui hai bisogno.

Questo tutorial illustra come configurare un sondaggio per i clienti tramite i webhook di WhatsApp, usando un'azienda fittizia a titolo esemplificativo. Il sondaggio raccoglie feedback su come i clienti hanno scoperto l'azienda e quali sono i tipi di visite che preferiscono per permettere all'azienda di migliorare il servizio per i clienti esistenti e futuri.

Esecuzione di un sondaggio con WhatsApp Flows

Prerequisiti

Ecco i prerequisiti necessari per procedere:

Procedura

  1. Crea un'app Flask.
  2. Scrivi il codice Python per la creazione e la pubblicazione dei flussi usando l'API Flows di WhatsApp. Il codice invierà anche il flusso pubblicando usando l'API Cloud.
  3. Crea un webhook per l'acquisizione dei messaggi delle chat.
  4. Esegui l'app.

Se vuoi vedere un'anteprima del progetto, puoi visualizzare il codice completo.

Creazione di un sondaggio con l'API Flows di WhatsApp

Sono disponibili due modi per creare un flusso: l'uso di Flow Builder UI o dell'API Flows. In questo tutorial, viene usata l'API Flows per configurare il sondaggio in modo programmatico.

Per creare un flusso che usa dati dinamici dal tuo server, puoi creare un endpoint che collega il sondaggio al server. Usando un endpoint, puoi controllare la logica di navigazione tra le schermate del flusso, popolare i dati del flusso dal server o mostrare/nascondere i componenti nella schermata in base all'interazione degli utenti.

L'esempio del flusso del sondaggio in esame non usa endpoint perché non è previsto uno scambio di dati dinamici tra di esso e un server. Userai il webhook della chat per acquisire le informazioni dal sondaggio. Inoltre, puoi allegare i flussi a un modello di messaggio in WhatsApp Manager.

Creazione di un'app Flask

Innanzitutto, crea un'app Flask per interagire con l'API Flows. Esegui il seguente comando nel terminale per creare un ambiente virtuale.

python -m venv venv
        

Dopodiché, usa il comando di seguito per attivare l'ambiente.

source venv/bin/activate
        

A questo punto, usa il comando di seguito per installare i pacchetti necessari.

pip install requests flask python-dotenv
        

Userai Flask per creare percorsi e interagire con l'API Flows, richieste per inviare richieste HTTP e python-dotenv per caricare variabili dell'ambiente.

Ora crea un file dell'ambiente in formato .env e incolla e completa le seguenti informazioni.

VERIFY_TOKEN =
ACCESS_TOKEN =
WHATSAPP_BUSINESS_ACCOUNT_ID =
PHONE_NUMBER_ID =
        

Assegna i valori in base alle informazioni del tuo account sviluppatore. Puoi usare qualsiasi stringa per VERIFY_TOKEN. Le variabili WHATSAPP_BUSINESS_ACCOUNT_ID e PHONE_NUMBER_ID rappresentano gli identificatori univoci del tuo account autogenerati da Meta. The ACCESS_TOKEN serve per l'autenticazione e l'autorizzazione delle richieste dell'API.

Per accedere a queste informazioni nella dashboard dell'app Meta, clicca su WhatsApp > Configurazione API nel riquadro di navigazione a sinistra come riportato nello screenshot di seguito.

Anteprima di Configurazione API

Infine, nella stessa directory, crea un file denominato main.py che conterrà la logica Python per la creazione di flussi e il webhook.

Creazione del flusso

Per creare il flusso, aggiungi i seguenti pacchetti in main.py.

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

A questo punto, aggiungi lo snippet di codice seguente in main.py per inizializzare le variabili. Lo snippet inizializza anche Flask e chiama il metodo load_dotenv() per il caricamento delle variabili.

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

Dopodiché, aggiungi il percorso seguente per gestire la creazione di un flusso.

@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 funzione chiama l'endpoint del flusso (flow_base_url) trasmettendo il payload (flow_creation_payload) che contiene il nome e la categoria del flusso. I possibili valori per la categoria sono: SIGN_UP, SIGN_IN, APPOINTMENT_BOOKING, LEAD_GENERATION, CONTACT_US, CUSTOMER_SUPPORT, SURVEY e OTHER.

Sostituisci <FLOW-NAME> con il nome desiderato, ad esempio survey_flow.

Dopo che il codice crea il flusso, estrae created_flow_id per caricare il corpo JSON.

Caricamento dei componenti JSON del flusso

Crea un file survey.json con questi contenuti. Il file JSON contiene la struttura del flusso.

A questo punto, incolla il codice seguente nel file 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())

La funzione carica i dati JSON da survey.json all'endpoint delle risorse del flusso.

Nello snippet di codice di seguito, quando l'utente attiva l'azione del clic, viene avviato on-click-action, che acquisisce i dati all'interno del payload. Il campo "name": "complete" indica che il flusso è completo. Verrà chiuso, mentre il payload sarà inviato al tuo server 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}"
    }
}

...
        

I valori all'interno degli oggetti del payload potrebbero corrispondere a componenti del flusso (analogamente ai nomi degli elementi nei moduli HTML) o a oggetti dati. Le chiavi associate a questi valori del payload sono definiti nomi: equivale al modo in cui assegni le variabili nei linguaggi di programmazione.

Anche gli elementi data-source contengono ID che fungono da chiavi per i valori. Il codice invia questi ID per le diverse opzioni. Ad esempio, se l'utente sceglie Likely per data-source di seguito, il codice invia 1. Una volta ricevuti i dati, puoi abbinarli alle origini dei dati.

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

Il flusso presenta una schermata che include nove domande a risposta multipla, come mostrato di seguito.

Flusso del sondaggio

Puoi esplorare i dettagli degli elementi JSON nella documentazione per gli sviluppatori.

Pubblicazione del flusso

A questo punto, incolla la funzione di seguito nel file main.py per aggiungere la logica per la pubblicazione del flusso. Un flusso pubblicato è pronto per la produzione, quindi non potrai apportarvi ulteriori modifiche.

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 funziona richiama l'endpoint di pubblicazione trasmettendo l'ID del flusso.

Invio del flusso

Incolla la funzione seguente nel file main.py per inviare il flusso a un utente di WhatsApp. La funziona chiama l'endpoint dei messaggi dell'API Cloud trasmettendo il payload del flusso.

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

Il payload del flusso contiene i dettagli del flusso. Il campo action.parameters.flow_token ti consente di trasmettere un identificatore univoco per il messaggio del flusso che verrà inviato dal client al webhook al completamento del flusso. Per questo tutorial, useremo un ID casuale (uuid). Il codice imposta la schermata action.parameters.flow_action_payload.screen come SURVEY_SCREEN che è l'ID della schermata che vuoi mostrare quando l'utente clicca su action.parameters.flow_cta.

Impostazione del webhook

La logica del webhook è piuttosto semplice. Ha due funzioni, ovvero webhook_get e webhook_post, che gestiscono rispettivamente le richieste GET e POST. Il codice usa la richiesta GET quando aggiunge il webhook alla tua app Meta. Restituisce hub.challenge della richiesta quando l'operazione va a buon fine. La richiesta POST stampa il payload del messaggio nel terminale.

@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 richiesta POST estrae ed elabora il payload del messaggio. Dato che il codice è dedicato al payload del messaggio, viene restituito un errore in caso di acquisizione di qualsiasi altro payload. Per tale motivo, userai un'istruzione if per verificare la presenza del corpo messages. Dopo aver verificato che il corpo JSON messages è presente, viene eseguita un'ulteriore verifica per estrarre il numero di telefono del mittente solo in presenza del corpo text nel payload 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)
        

Inoltre, è consigliabile usare la funzione dell'helper seguente chiamata flow_reply_processor per estrarre la risposta dal flusso e rimandarla all'utente. Dato che la risposta del flusso contiene l'ID dell'opzione selezionata durante l'acquisizione dei dati da RadioButtonsGroups, la funzione abbina gli ID ai valori delle stringhe corrispondenti.

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 funzione invia la risposta usando l'endpoint per i messaggi dell'API Cloud.

L'app deve essere in esecuzione affinché tu possa configurare il webhook nella console di Meta for Developers. Pertanto, usa il comando flask --app main run --port 5000 nel terminale per eseguire il codice. Dovresti vedere il messaggio * Running on http://127.0.0.1:5000 nel terminale se la configurazione è stata completata correttamente.

A questo punto, esegui il comando ngrokhttp 5000 nel terminale per ricevere un URL associato alla tua app. Copia il link.

Nella console Meta for Developers, clicca su Configurazione sotto WhatsApp nel riquadro di navigazione a sinistra.

Configurazione WhatsApp

Nella scheda Webhook, clicca su Modifica. Dopodiché, nella casella di dialogo del campo URL di callback, aggiungi l'URL copiato inserendo /webhook alla fine. Nel campo Token di verifica, aggiungi il token dalla variabile .env del file TOKEN.

Al termine, clicca su Verifica e salva. La casella di dialogo si chiude. Clicca su Gestisci e controlla il campo messages. Le informazioni devono essere simili a quelle riportate nell'immagine seguente, con l'URL di callback, le informazioni nascoste sotto Token di verifica e messages presenti sotto Campi dei webhook.

Configurazione WhatsApp

Il webhook è ora pronto.

Esecuzione dell'app

In una nuova istanza del terminale, esegui il comando cURL per creare un flusso.

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

Nell'istanza del terminale che mostra l'output del tuo webhook, dovresti vedere un messaggio simile a quello riportato di seguito.

Esecuzione dell'app

Quando un utente invia un messaggio al tuo numero WhatsApp, riceve il flusso in risposta, come nello screenshot di seguito.

Esempio di richiesta del sondaggio del flusso

Dopo aver completato il sondaggio, l'utente riceve un messaggio con le sue risposte.

Esempio di risposta del sondaggio del flusso

Qualsiasi messaggio inviato da un utente al tuo numero attiva il flusso. Per il tuo caso d'uso, personalizza il codice per inviare il flusso del sondaggio solo in situazioni specifiche, ad esempio se un utente ha chattato con la tua azienda.

Conclusioni

WhatsApp Flows migliora l'esperienza utente grazie a interfacce interattive che permettono di raccogliere dati strutturati, come risposte ai sondaggi per un'ipotetica visita dell'azienda. L'azienda deve solo creare un'app Flask, implementare il codice Python da generare e distribuire i flussi tramite l'API Flows di WhatsApp, configurare un webhook per acquisire i messaggi in entrata ed eseguire l'app per abilitare la raccolta delle risposte al sondaggio.

WhatsApp Flows consente alla tua organizzazione di raccogliere i dati in modo semplice e veloce, aiutandola a migliorare i tassi di completamento in relazione alle interazioni dei clienti. Usa una procedura simile per configurare i tuoi sondaggi, offrire assistenza ai clienti, aiutarli a prenotare appuntamenti e molto altro.

Continua a esplorare il potenziale di WhatsApp Flows. Provala subito.