返回開發人員最新消息

Creating Surveys with WhatsApp Flows

2024年3月6日發佈者:Gafi G & Iryna Wagner

WhatsApp Flows 可改善並簡化企業蒐集顧客資料的流程。您的機構可輕鬆地透過顧客互動擷取結構化資訊,從而讓顧客在 WhatsApp 享有更優良的用戶體驗。WhatsApp Flows 特別適合用來蒐集開發潛在顧客資料、執行問卷調查、協助客戶預約服務、提交顧客問題和疑慮等等。

最令人雀躍的是,您可以為顧客提供所有這些選項,而無需構建複雜的應用程式和後端:您只需使用 WhatsApp 作為前端,並採用 Webhook 以 JSON 訊息形式擷取回應、處理資訊並檢索所需資料。

此教學導覽會以一間虛構公司為例,探索如何使用 Webhooks 在 WhatsApp 設定顧客問卷調查。此問卷調查將蒐集各類意見,例如顧客如何發掘到這間公司,以及他們喜歡的巡迴活動類型,以便此公司為現有及未來客戶提供更出色的服務。

透過 WhatsApp Flows 執行問卷調查

必要條件

若要使用此功能,請確保您具備下列條件:

程序

  1. 建立 Flask 應用程式。
  2. 編寫 Python 程式碼,以使用 WhatsApp Flows API 建立並發佈流程。Python 程式碼亦會使用雲端 API 傳送已發佈的流程。
  3. 建立 Webhook 以偵聽聊天室訊息。
  4. 運行應用程式。

若您想預覽此專案,可查看完整程式碼

使用 WhatsApp Flows API 建立問卷調查

您可透過兩種方式來建立流程:使用流程建造工具用戶介面 或 Flows API。此教學導覽使用 Flows API 以程式輔助的方式來設定問卷調查。

若要建立流程以使用來自您伺服器的動態資料,您可以建立一個端點,將問卷調查連結至自家伺服器。您可使用此端點來控制各個流程畫面之間的導覽邏輯、填入來自您伺服器的流程資料,或根據用戶互動顯示/隱藏畫面上的元件。

本文的問卷調查流程範例並未使用任何端點,這是因為其與伺服器之間沒有交換動態資料。我們將使用聊天室 Webhook 來擷取問卷調查中的資訊。此外,您可以透過 WhatsApp 管理工具將流程附加至訊息範本

建立 Flask 應用程式

首先,建立 Flask 應用程式以與 Flows API 互動。在終端上運行以下指令以建立虛擬環境。

python -m venv venv
        

接著,使用下方指令來啟用此環境。

source venv/bin/activate
        

接下來,使用以下指令安裝所需套件。

pip install requests flask python-dotenv
        

您需要使用 Flask 來建立路線並與 Flows API 互動、使用 requests 來傳送 HTTP 要求,並使用 python-dotenv 來載入環境變數。

現在,建立名為 .env 的環境檔案,並貼上下列資訊。

VERIFY_TOKEN =
ACCESS_TOKEN =
WHATSAPP_BUSINESS_ACCOUNT_ID =
PHONE_NUMBER_ID =
        

您可以根據自家開發人員帳戶資訊來分配相應的值。VERIFY_TOKEN 可使用任何字串。WHATSAPP_BUSINESS_ACCOUNT_IDPHONE_NUMBER_ID 變數是 Meta 為您帳戶自動產生的不重複識別碼。The ACCESS_TOKEN 會用於驗證和批准 API 要求。

若要從您 Meta 應用程式的管理中心存取此資訊,請在左側導覽面板中點擊「WhatsApp」>「API 設定」,如下方螢幕截圖所示。

預覽 API 設定

最後,在同一個目錄中建立名為 main.py 的檔案,以包含用於建立流程和 Webhook 的 Python 邏輯。

構建流程

若要構建流程,請先將下列套件加入 main.py

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

接下來,將下列程式碼片段加入 main.py 以初始化變數。此程式碼片段亦可啟動 Flask 並呼叫 load_dotenv() 方法,以助載入變數。

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

接著,新增下列路線以處理建立流程之事宜。

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

此函數會呼叫 Flows 端點 (flow_base_url),同時傳遞含有名稱和流程類別的裝載 (flow_creation_payload)。可用於類別的值包括:SIGN_UPSIGN_INAPPOINTMENT_BOOKINGLEAD_GENERATIONCONTACT_USCUSTOMER_SUPPORTSURVEYOTHER

使用您所需的名稱(例如 survey_flow)來替換 <FLOW-NAME>

程式碼建立流程後,便會擷取 created_flow_id 以用於上載其 JSON 主體。

上載流程的 JSON 元件

使用這些內容建立 survey.json 檔案。此 JSON 會包含流程的結構。

接著,將下列程式碼貼至 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())

此函數會將 JSON 資料從 survey.json 上載至流程資產端點。

在下方程式碼片段中,當用戶觸發點擊操作時便會啟動 on-click-action,以擷取裝載中的資料。"name": "complete" 欄位代表流程已完成並將關閉,且系統會將裝載傳送至您的 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}"
    }
}

...
        

裝載物件中的值可能會與流程元件(類似 HTML 表格中的元素名稱)或資料物件對應。與此等裝載值相關的鍵稱為「name」,其分配方式與您在程式語言中分配變數的方法類似。

data-source 元素亦包含充當值鍵的編號。程式碼會針對用戶選項傳送這些編號。舉例來說,若用戶為下方的 data-source 選擇 Likely,則程式碼會傳送 1。您收到資料後,便可配對資料來源。

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

流程有一個包含 9 條選擇題的畫面,如下所示。

問卷調查流程

您可以查閱開發人員文件,探索各項 JSON 元素的詳細資訊。

發佈流程

接下來,將下方函數貼至 main.py 檔案,以加入流程發佈邏輯。已發佈的流程屬於正式版本,因此您無法再對其作出變更。

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)
        

此函數會調用發佈端點,同時傳遞流程編號。

傳送流程

將以下函數貼至 main.py 檔案,以將流程傳送至 WhatsApp 用戶。此函數會呼叫雲端 API 的訊息端點,同時傳遞裝載。

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

流程裝載會包含流程詳細資訊action.parameters.flow_token 欄位會用於傳遞流程訊息的不重複識別碼;當流程完成後,系統便會將此識別碼從用戶端傳輸至您的 Webhook。在此教學導覽中,我們會使用隨機編號(uuid)。程式碼將 action.parameters.flow_action_payload.screen 設為 SURVEY_SCREEN,後者是您希望在用戶點擊 action.parameters.flow_cta 時顯示的畫面之編號。

設定 Webhook

Webhook 邏輯簡明易懂。它有 2 個函數 webhook_getwebhook_post,分別負責處理 GETPOST 要求。將 Webhook 加至您的 Meta 應用程式時,程式碼會使用 GET 要求。若要求成功,便會傳回要求的 hub.challengePOST 要求會用於將訊息裝載打印至終端。

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

POST 要求會用於擷取並處理訊息裝載。由於此程式碼僅用於處理訊息裝載,因此擷取任何其他裝載時會出現錯誤。因此,您可使用 messages 陳述式來檢查是否存在 if 主體。如果發現存在 messages JSON 主體,僅當 messages 裝載中存在 text 主體時,您才需要執行另一項檢查以擷取發件人的手機號碼。

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

此外,您應使用下方名為 flow_reply_processor 的協助工具函數,從流程中擷取回覆並將其傳回給用戶。由於從 RadioButtonsGroups 擷取資料時流程回覆會包含用戶所選的選項編號,因此此函數會將編號與相應字串值配對。

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

此函數會使用雲端 API 短訊端點來傳送回覆。

應用程式需要處於運作狀態,您才能在 Meta for Developers 主控台配置 Webhook。因此,請在您的終端使用指令 flask --app main run --port 5000 以執行程式碼。若一切設定正確,您應該會在終端看到 * Running on http://127.0.0.1:5000 訊息。

接下來,在您的終端執行指令 ngrokhttp 5000,以取得與您應用程式相應的網址。複製此連結。

在 Meta for Developers 主控台中,於左側導覽面板中的「WhatsApp」下方,點擊「配置」。

WhatsApp 配置

在「Webhook」小卡片中,點擊「編輯」。然後,在「回調網址」欄位中的對話框中,加入所複製的網址並附加 /webhook。在「驗證憑證」欄位中,加入 .env 檔案的 TOKEN 變數所提供的憑證。

完成後,點擊「驗證並儲存」。對話框會隨即關閉。點擊「管理」並檢查「訊息」欄位。此資訊應與下方圖像類似,當中包含「回調網址」、「驗證憑證」下方的隱藏資訊,以及列於「Webhook」欄位下方的訊息

WhatsApp 配置

Webhook 現已就緒。

運行應用程式

在新終端實例中,執行下方的 cURL 指令以建立流程。

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

在顯示 Webhook 輸出的終端實例中,您應該會看到一則如下所示的訊息。

運行應用程式

當用戶向您的 WhatsApp 手機號碼傳送訊息時,對方所收到的回覆會是您的流程,如下方螢幕截圖中所示。

流程問卷調查提示範例

完成問卷調查後,用戶會收到包含其回答的回覆。

流程問卷調查回覆範例

只要用戶向您的手機號碼傳送訊息,便會觸發流程。您可以針對自己的使用案例來自訂程式碼,以僅在特定情況下才傳送問卷調查流程,例如在用戶與您的企業聊天後才傳送此流程。

結論

WhatsApp Flows 透過互動式介面蒐集結構化資料,例如上述虛構旅行社的問卷調查回覆,從而改善用戶體驗。此企業僅建立 Flask 應用程式、執行 Python 程式碼以透過 WhatsApp Flows API 產生並部署流程、設定 Webhook 以偵聽傳入訊息,以及運行應用程式,以蒐集問卷調查回覆。

透過 WhatsApp Flows,您的機構可以輕鬆快速地蒐集資料,進而提高顧客互動的完成率。使用類似程序來設定您自己的問卷調查、提供顧客支援、協助他們預約服務等等。

請即繼續探索 WhatsApp Flows 的無限潛力。立即試用