Zurück zu den Neuigkeiten für Entwickler

Creating Surveys with WhatsApp Flows

6. März 2024VonGafi G & Iryna Wagner

WhatsApp Flows optimiert und vereinfacht das Erfassen von Kund*innendaten für dein Unternehmen. Deine Organisation kann damit bei Interaktionen mit Kund*innen ganz bequem strukturierte Informationen erfassen. Deine Kundschaft wiederum genießt das positive Nutzungserlebnis von WhatsApp. WhatsApp Flows eignet sich zur Datenerfassung bei der Leadgenerierung, für Umfragen, zur Terminbuchung, bei Fragen und Anregungen von Kund*innen und für vieles mehr.

Das Beste: Du kannst deinen Kund*innen all diese Optionen anbieten, ohne komplexe Apps oder Backend-Lösungen zu entwickeln. Nutze einfach WhatsApp als Frontend und erfasse die Antworten per Webhook als JSON-Nachrichten, verarbeite die Informationen und zeichne die relevanten Daten auf.

Dieses Tutorial zeigt dir anhand eines fiktiven Unternehmens, wie du mit Webhooks eine Kund*innen-Umfrage auf WhatsApp einrichtest. Die Umfrage holt Feedback von Kund*innen ein (etwa wie sie das Unternehmen entdeckt haben oder welche Reisen sie bevorzugen), um besser auf die Bedürfnisse bestehender und neuer Kundschaft eingehen zu können.

Umfrage mit WhatsApp Flows

Voraussetzungen

Das benötigst du für dieses Tutorial:

Ablauf

  1. Erstelle eine Flask-App.
  2. Schreibe den Python-Code zum Erstellen und Veröffentlichen der Flows per WhatsApp Flows API. Über den Python-Code wird per Cloud API auch der veröffentlichte Flow gesendet.
  3. Erstelle einen Webhook, der Chat-Nachrichten sucht.
  4. Führe die App aus.

Für eine Vorschau des Projekts kannst du dir den vollständigen Code ansehen.

Umfrage erstellen mit WhatsApp Flows API

Es gibt zwei Möglichkeiten, einen Flow zu erstellen – mit der Flow Builder UI oder der Flows API. Dieses Tutorial nutzt die Flows API zur programmgesteuerten Einrichtung der Umfrage.

Um einen Flow zu erstellen, der dynamische Daten von deinem Server nutzt, könntest du einen Endpunkt erstellen, um die Umfrage mit deinem eigenen Server zu verknüpfen. Über einen Endpunkt kannst du die Navigationslogik der einzelnen Flow-Bildschirme steuern, Flow-Daten von deinem Server abrufen und/oder Komponenten interaktionsbasiert ein- oder ausblenden.

Der Umfrage-Flow in diesem Beispiel nutzt keine Endpunkte, da keine dynamischen Daten von einem Server abgerufen werden. Hier werden mit dem Chat-Webhook die Informationen der Umfrage erfasst. Daneben könntest du im WhatsApp Manager Flows mit einer Nachrichtenvorlage verknüpfen.

Flask-App erstellen

Erstelle zunächst eine Flask-App zur Interaktion mit der Flows API. Führe in deinem Terminal den folgenden Befehl aus, um eine virtuelle Umgebung zu erstellen.

python -m venv venv
        

Aktiviere die Umgebung anschließend mit dem nachstehenden Befehl.

source venv/bin/activate
        

Installiere dann mit folgendem Befehl die erforderlichen Pakete.

pip install requests flask python-dotenv
        

Mit Flask erstellst du Routen und interagierst mit der Flows API, mit Anfragen sendest du HTTP-Anfragen und mit python-dotenv lädst du Umgebungsvariablen.

Erstelle nun eine ENV-Umgebungsdatei und kopiere die folgenden Informationen.

VERIFY_TOKEN =
ACCESS_TOKEN =
WHATSAPP_BUSINESS_ACCOUNT_ID =
PHONE_NUMBER_ID =
        

Weise die Werte entsprechend den Informationen deines Entwickler*innen-Kontos zu. Als VERIFY_TOKEN kannst du einen beliebigen String verwenden. Die Variablen WHATSAPP_BUSINESS_ACCOUNT_ID und PHONE_NUMBER_ID sind individuelle IDs deines Kontos, die von Meta automatisch erstellt werden. The ACCESS_TOKEN dient zur Authentifizierung und Autorisierung der API-Anfragen.

Um über das Dashboard deiner Meta-App auf diese Informationen zuzugreifen, tippe wie im nachstehenden Screenshot im linken Navigationsmenü auf WhatsApp > API-Setup.

Vorschau der API-Einrichtung.

Erstelle abschließend im selben Verzeichnis die Datei „main.py“, welche die Python-Logik zur Erstellung der Flows und Webhooks enthält.

Flow erstellen

Um den Flow zu erstellen, füge als Erstes folgende Pakete zu main.py hinzu.

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

Füge dann diesen Code-Schnipsel zu main.py hinzu, um die Variablen zu initialisieren. Der Schnipsel startet auch Flask und ruft die Methode load_dotenv() auf, um die Variablen zu laden.

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

Füge dann die folgende Route hinzu, um einen Flow zu erstellen.

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

Die Funktion ruft den Flows-Endpunkt (flow_base_url) auf und sendet dabei die Payload (flow_creation_payload) mit dem Namen und der Flow-Kategorie. Mögliche Werte für die Kategorie: SIGN_UP, SIGN_IN, APPOINTMENT_BOOKING, LEAD_GENERATION, CONTACT_US, CUSTOMER_SUPPORT, SURVEY oder OTHER.

Ersetze <FLOW-NAME> durch den gewünschten Namen, z. B. „survey_flow“.

Nachdem der Code den Flow erstellt, wird die created_flow_id für den Upload des JSON-Bodys extrahiert.

JSON-Komponenten des Flows hochladen

Erstelle eine Datei survey.json mit diesem Inhalt. Die JSON-Datei enthält die Struktur des Flows.

Kopiere dann folgenden Code in die Datei 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())

Diese Funktion lädt die JSON-Daten aus survey.json im Assets-Endpunkt des Flows hoch.

Im nachstehenden Code-Schnipsel wird so durch Klicken des*der Nutzer*in on-click-action initiiert, sodass Daten in der Payload erfasst werden. Das Feld "name": "complete" gibt an, dass der Flow abgeschlossen wurde. Er wird geschlossen und die Payload an deinen Webhook-Server gesendet.

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

...
        

Die Werte in Payload-Objekten können Flow-Komponenten (ähnlich Element-Namen in HTML-Formularen) oder Datenobjekten entsprechen. Die mit diesen Payload-Werten verknüpften Schlüssel sind sogenannte Namen – vergleichbar mit der Zuweisung von Variablen in Programmiersprachen.

Die data-source-Elemente enthalten ebenfalls IDs, die als Schlüssel zu den Werten dienen. Der Code sendet diese IDs je nach Auswahl. Wählt der*die Nutzer*in etwa nachstehend bei data-source die Option Likely aus, sendet der Code 1. Wenn du Daten erhältst, kannst du die Datenquellen abgleichen.

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

Der Flow besteht wie unten dargestellt aus neun Multiple-Choice-Fragen.

Umfrage-Flow.

In der Entwickler*innen-Dokumentation findest du die Details der JSON-Elemente.

Flow veröffentlichen

Kopiere als Nächstes die Funktion unten in die Datei main.py, um die Logik zur Veröffentlichung des Flows hinzuzufügen. Ein veröffentlichter Flow ist produktionsreif und kann nicht mehr geändert werden.

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)
        

Die Funktion ruft den Veröffentlichungs-Endpunkt auf und sendet die Flow-ID.

Flow senden

Kopiere die folgende Funktion in die Datei main.py, um den Flow an eine*n WhatsApp-Nutzer*in zu senden. Die Funktion ruft den Nachrichten-Endpunkt der Cloud API auf und sendet die Flow-Payload.

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

Die Flow-Payload umfasst die Details des Flows. Über das Feld action.parameters.flow_token kannst du eine einzigartige ID für die Flow-Nachricht übertragen, die nach Abschluss des Flows vom Client an deinen Webhook gesendet wird. Für dieses Tutorial verwendest du eine zufällige ID (UUID). Dieser Code legt action.parameters.flow_action_payload.screen als SURVEY_SCREEN fest. Dabei handelt es sich um die ID des Bildschirms, der angezeigt werden soll, wenn ein*e Nutzer*in auf action.parameters.flow_cta klickt.

Webhook erstellen

Die Webhook-Logik ist vergleichsweise schlicht. Sie enthält die zwei Funktionen webhook_get und webhook_post, die für GET- bzw. POST-Anfragen zuständig sind. Um den Webhook zu deiner Meta-App hinzuzufügen, nutzt der Code die Anfrage GET. Bei erfolgreicher Ausführung wird die hub.challenge der Anfrage ausgegeben. Mit der Anfrage POST wird die Nachrichten-Payload auf dem Terminal festgehalten.

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

Die Anfrage POST extrahiert und verarbeitet die Nachrichten-Payload. Da der Code nur für die Nachrichten-Payload gilt, wird ein Fehler ausgegeben, wenn andere Payloads erfasst werden. Daher prüfst du mithilfe eines if-Statements, ob es einen messages-Body gibt. Nach der Prüfung auf einen messages-JSON-Body wird eine zweite Prüfung ausgeführt, wenn es einen text-Body in der messages-Payload gibt. Dabei wird die Telefonnummer des*der Absender*in extrahiert.

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

Daneben solltest du mit der folgenden Hilfstool-Funktion flow_reply_processor die Antwort aus dem Flow extrahieren und an den*die Nutzer*in senden. Die Flow-Antwort enthält die ID der ausgewählten Option, wenn du Daten von RadioButtonsGroups erfasst. Daher gleicht die Funktion die IDs mit den entsprechenden String-Werten ab.

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

Die Funktion sendet die Antwort über den Textnachrichten-Endpunkt der Cloud API.

Die Anwendung muss aktiv sein, bevor du den Webhook in der Konsole von Meta for Developers konfigurierst. Verwende daher den Befehl flask --app main run --port 5000 in deinem Terminal, um den Code auszuführen. Falls alles korrekt eingerichtet ist, solltest du in deinem Terminal die Nachricht * Running on http://127.0.0.1:5000 sehen.

Führe in deinem Terminal anschließend den Befehl ngrokhttp 5000 aus, um eine URL zu erhalten, die zu deiner App führt. Kopiere diesen Link.

Klicke in der Konsole von Meta for Developers im linken Navigationsbereich unter WhatsApp auf Konfiguration.

WhatsApp-Konfiguration.

Klicke in der Karte Webhook auf Bearbeiten. Füge dann im Dialogfeld unter Callback-URL die kopierte URL ein und hänge /webhook an. Füge im Feld Verifizierungstoken den Token aus der TOKEN-Variable deiner .env-Datei hinzu.

Wenn du fertig bist, klicke auf Bestätigen und speichern. Nun schließt sich der Dialog. Klicke auf Verwalten und wähle das Feld Nachrichten aus. Die Informationen sollten denen im folgenden Bild ähneln. Es sollte die Callback-URL aufgeführt sein, verborgene Informationen unter Verifizierungstoken und messages (Nachrichten) unter Webhook-Felder.

WhatsApp-Konfiguration.

Der Webhook ist nun bereit.

App ausführen

Führe in einer neuen Terminal-Instanz den folgenden cURL-Befehl aus, um einen Flow zu erstellen.

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

In der Terminal-Instanz mit dem Webhook-Output solltest du eine Nachricht wie die unten abgebildete sehen.

App ausführen.

Wenn ein*e Nutzer*in eine Nachricht an deine WhatsApp-Nummer sendet, erhält er*sie den Flow als Antwort wie im Screenshot unten.

Beispiel: Stichwort für Umfrage-Flow.

Hat er*sie die Umfrage abgeschlossen, erhält er*sie eine Nachricht mit seinen*ihren Antworten.

Beispiel: Antwort zu Umfrage-Flow.

Der Flow wird mit jeder Nachricht ausgelöst, die ein*e Nutzer*in an deine Nummer sendet. Passe deinen Code für deinen Anwendungsfall an, sodass der Umfrage-Flow beispielsweise nur gesendet wird, nachdem ein*e Nutzer*in mit deinem Unternehmen gechattet hat.

Fazit

WhatsApp Flows verbessert durch interaktive Oberflächen das Nutzungserlebnis beim Erfassen strukturierter Daten, etwa bei Umfragen eines fiktiven Reiseunternehmens. Das Unternehmen muss nur eine Flask-App erstellen, Python-Code implementieren, um Flows zu generieren und über die WhatsApp Flows API zu senden, einen Webhook einrichten, der eingehende Nachrichten erkennt, und die App ausführen, um Umfragen durchzuführen.

Mit WhatsApp Flows kann deine Organisation Daten schnell und einfach erfassen und so die Abschlussrate bei Kund*innen-Interaktionen erhöhen. Dabei kannst du mit einem ähnlichen Vorgehen deine eigenen Umfragen erstellen, Support anbieten, Termine vereinbaren und mehr.

Erkunde das Potenzial von WhatsApp Flows weiter. Jetzt ausprobieren