•  


WhatsApp Flows를 使用하여 設問調査 만들기
開發者 消息으로 돌아가기

Creating Surveys with WhatsApp Flows

2024年 3月 6日 製作: Gafi G & Iryna Wagner

WhatsApp Flows는 비즈니스가 顧客 데이터를 蒐集하는 方式을 最適化 및 簡素化합니다. 組織에서는 顧客과의 相互 作用으로부터 손쉽게 定型化된 情報를 蒐集할 수 있고, 顧客은 WhatsApp에서 肯定的인 使用者 經驗을 滿喫할 수 있습니다. WhatsApp Flows는 潛在 顧客 確保 데이터 蒐集, 設問調査 進行, 顧客의 豫約 支援, 顧客 質問과 憂慮 事項 提出 等의 作業에 活用할 수 있습니다.

가장 좋은 點은 複雜한 애플리케이션과 백엔드를 빌드夏至 않고도 顧客에게 이 모든 옵션을 提供할 수 있다는 것입니다. 프런트엔드로는 WhatsApp을 使用하는 同時에, 應答을 JSON 메시지로 캡처하고, 情報를 處理하고 必要한 데이터를 가져오도록 Webhook을 構成하면 됩니다.

이 튜토리얼에서는 假想의 企業이 WhatsApp에서 Webhook을 使用하여 顧客 設問調査를 設定하는 事例를 살펴봅니다. 設問調査에서는 旣存 顧客과 潛在 顧客에게 더 나은 서비스를 提供하는 데 도움이 되도록 顧客이 企業을 發見한 方法과 顧客이 選好하는 觀光 類型 等의 피드백을 蒐集합니다.

WhatsApp Flows를 使用하여 設問調査 進行하기

必須 條件

튜토리얼을 따라서 進行하려면 다음이 必要합니다.

節次

  1. Flask 앱을 만듭니다.
  2. WhatsApp Flows API 를 使用하여 Flows를 만들고 揭示하는 Python 코드를 作成합니다. 이 Python 코드는 揭示된 Flow를 클라우드 API를 使用하여 電送하는 作業도 遂行합니다.
  3. 채팅 메시지를 受信 待機하는 Webhook을 만듭니다.
  4. 애플리케이션을 實行합니다.

프로젝트를 미리 보려면 完成된 코드 를 봅니다.

WhatsApp Flows API를 使用하여 設問調査 만들기

Flow는 Flow Builder UI 또는 Flows API를 使用하여 만들 수 있습니다. 이 튜토리얼에서는 Flows API를 使用하여 프로그래밍 方式으로 設問調査를 設定합니다.

서버의 다이내믹 데이터를 使用하는 Flow를 빌드하려면 設問調査를 自體 서버에 連結하는 엔드포인트를 만듭니다 . 엔드포인트를 使用하여 Flow 畵面 間의 移動 老職을 制御하고, 서버의 Flow 데이터를 入力하고, 使用者 相互 作用에 따라 畵面 構成 要素를 標示하거나 숨길 수 있습니다.

여기에서 다루는 設問調査 Flow 例示의 境遇 Flow와 서버 사이에서 다이내믹 데이터 交換이 이루어지지 않으므로 엔드포인트를 使用하지 않습니다. 設問調査의 情報를 蒐集하는 데는 채팅 Webhook을 使用합니다. 追加로 WhatsApp 管理者에서 Flow를 메시지 템플릿에 連結 할 수 있습니다.

Flask 앱 만들기

먼저 Flows API와 相互 作用하는 Flask 앱을 만듭니다. 터미널에서 다음 命令을 實行하여 假想 環境을 만듭니다.

python -m venv venv
        

그런 다음 아래 命令을 使用하여 環境을 活性化합니다.

source venv/bin/activate
        

다음으로, 아래 命令을 使用하여 必須 패키지를 設置합니다.

pip install requests flask python-dotenv
        

이 Flask 앱을 使用하여 Flows API와 相互 作用하는 經路를 만들고, HTTP 要請을 電送하는 要請을 만들고, 環境 變數를 읽어들이는 python-dotenv를 만들 것입니다.

.env라는 環境 파일을 만든 後 다음 情報를 붙여넣습니다.

VERIFY_TOKEN =
ACCESS_TOKEN =
WHATSAPP_BUSINESS_ACCOUNT_ID =
PHONE_NUMBER_ID =
        

開發者 計定 情報의 값을 割當합니다. VERIFY_TOKEN 으로는 任意의 文字列을 使用합니다. WHATSAPP_BUSINESS_ACCOUNT_ID PHONE_NUMBER_ID 變數는 Meta에 依해 自動으로 生成된, 計定의 固有한 識別者입니다. The ACCESS_TOKEN 은 API 要請의 認證 및 權限 附與에 使用됩니다.

Meta 앱의 대시보드에서 이 情報에 액세스하려면 아래 스크린샷과 같이 왼쪽 探索窓에서 WhatsApp > API 설정 을 클릭합니다.

API 설정 미리 보기

마지막으로, 同一한 디렉토리에서 Flow 및 Webhook을 만드는 Python 老職을 包含하는 main.py 파일을 만듭니다.

Flow 빌드하기

Flow를 빌드하려면 먼저 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}",
}
        

그런 다음 Flow 만들기를 處理하는 다음과 같은 經路를 追加합니다.

@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 카테고리를 包含하는 (flow_creation_payload) 페이로드를 傳達합니다. 카테고리에 使用할 수 있는 값은 SIGN_UP , SIGN_IN , APPOINTMENT_BOOKING , LEAD_GENERATION , CONTACT_US , CUSTOMER_SUPPORT , SURVEY OTHER 입니다.

<FLOW-NAME> 을 願하는 이름(예: survey_flow)으로 바꿉니다.

이 코드는 Flow를 만든 後 JSON 本文을 업로드하기 위한 created_flow_id 를 抽出합니다.

Flow의 JSON 構成 要素 업로드하기

이 코드 를 使用하여 survey.json 파일을 만듭니다. 이 JSON에는 Flow의 構造가 包含되어 있습니다.

그런 다음 아래 코드를 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())

이 函數는 survey.json 에서 Flow 資産 엔드포인트로 JSON 데이터를 업로드합니다.

아래 코드 彫刻에서, 使用者가 클릭 銅雀乙 트리거하면 on-click-action 이 트리거되어 페이로드에 들어 있는 데이터가 蒐集됩니다. "name": "complete" 필드는 Flow가 完了되었음을 나타냅니다. 卽, Flow가 終了되고 페이로드가 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의 要素 이름 形式과 비슷한) Flow 構成 要素 또는 데이터 個體에 對應될 수 있습니다. 이러한 페이로드 값에 連結된 키가 바로 name입니다. 이는 프로그래밍 言語에서 變數가 割當되는 方式과 비슷합니다.

data-source 要素에는 값의 키로 기능하는 ID도 包含되어 있습니다. 코드는 이러한 ID가 옵션으로 使用될 수 있도록 電送합니다. 例를 들어, 使用者가 아래의 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"
        }
    ]
}
...
        

이 Flow에는 아래와 같이 客觀式 옵션을 갖는 9個의 質問이 있습니다.

설문조사 Flow

JSON 要素의 詳細 情報는 開發者 文書 를 參照하세요.

Flow 揭示하기

다음으로, 아래 函數를 main.py 파일에 追加하여 Flow 揭示 老職을 追加합니다. 揭示된 Flow는 프로덕션用으로 使用할 準備가 된 것이므로 追加로 變更 事項을 適用할 수 없습니다.

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)
        

이 函數는 Flow ID를 傳達하고 엔드포인트를 揭示합니다.

Flow 電送하기

아래 函數를 main.py 파일에 붙여넣어 Flow를 WhatsApp 使用者에게 電送합니다. 이 函數는 Flow 페이로드를 傳達하고 클라우드 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")
        

Flow 페이로드에는 Flow 詳細 情報 가 包含되어 있습니다. action.parameters.flow_token 필드를 使用하여 Flow 메시지의 固有한 識別者를 傳達할 수 있습니다. 識別者는 Flow가 完了되면 클라이언트에서 Webhook으로 傳達됩니다. 이 튜토리얼에서는 任意의 ID(uuid)를 使用합니다. 이 코드는 action.parameters.flow_action_payload.screen SURVEY_SCREEN (使用者가 action.parameters.flow_cta 를 클릭하면 標示할 畵面의 ID)으로 設定합니다.

Webhook 設定하기

Webhook 로직은 比較的 簡單합니다. 이 로직에는 GET 要請을 處理하는 webhook_get 函數와 POST 要請을 處理하는 webhook_post 函數가 있습니다. 이 코드는 Meta 앱에 Webhook을 追加할 때 GET 要請을 使用합니다. 成功한 境遇 要請의 hub.challenge 를 返還합니다. POST 要請은 메시지 페이로드를 터미널에 出力합니다.

@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에서 應答을 抽出하여 使用者에게 돌려보내는 아래와 같은 도우미 函數 flow_reply_processor 를 使用해야 합니다. Flow 應答은 RadioButtonsGroups 에서 데이터를 캡處할 때 選擇된 옵션 ID도 包含하므로, 函數는 ID를 對應되는 文字列 값에 매칭합니다.

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 메시지가 출력됩니다.

다음으로, 터미널에서 ngrok http 5000 命令을 實行하여 애플리케이션에 매핑되는 URL을 가져옵니다. 링크를 複寫합니다.

Meta for Developers 콘솔의 왼쪽 探索窓에 있는 WhatsApp 아래에서 構成 을 클릭합니다.

WhatsApp 구성

Webhook 카드에서 修正 을 클릭합니다. 그런 다음 콜백 URL 필드의 對話 箱子에 複寫한 URL을 넣고 그 뒤에 /webhook 을 追加합니다. 確認 토큰 필드에 .env 파일의 TOKEN 變數에서 確認한 토큰을 追加합니다.

作業을 마쳤으면 確認 및 貯藏 을 클릭합니다. 對話 箱子가 닫힙니다. 管理 를 클릭하고 메시지 필드를 選擇합니다. 아래 이미지와 같이 콜백 URL , 確認 토큰 아래의 숨겨진 情報, Webhook 필드 아래의 메시지 가 標示됩니다.

WhatsApp 구성

이제 Webhook이 準備되었습니다.

애플리케이션 實行하기

새 터미널 인스턴스에서 아래 cURL 命令을 實行하여 Flow를 만듭니다.

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

Webhook 出力이 標示된 터미널 인스턴스에서 아래와 같은 메시지를 볼 수 있습니다.

애플리케이션 실행하기

使用者가 비즈니스의 WhatsApp 番號로 메시지를 보내면 아래 스크린샷과 같이 答狀으로 Flow를 受信합니다.

Flow 설문조사 프롬프트 예시

使用者가 設問調査를 完了하면 答辯이 채워진 應答을 受信합니다.

Flow 설문조사 응답 예시

使用者가 비즈니스의 番號로 보내는 모든 메시지가 Flow를 트리거합니다. 비즈니스의 使用 事例에 맞게 特定 狀況에서만(예: 使用者가 비즈니스와 채팅한 境遇) 設問調査 Flow가 電送되도록 코드를 맞춤 設定하세요.

結論

WhatsApp Flows는 인터랙티브 인터페이스를 통해 假想의 觀光 業體를 위한 設問調査 應答과 같은 定型 데이터를 蒐集함으로써 使用者 經驗을 向上합니다. 비즈니스는 Flask 앱을 만들고, 生成할 Python 코드를 具現하고, WhatsApp Flows API를 통해 Flows를 配布하고, 受信 메시지를 受信 待機하는 Webhook을 設定하고, 애플리케이션을 實行하여 設問調査 應答 蒐集을 設定하기만 하면 됩니다.

組織에서는 WhatsApp Flows를 使用하여 쉽고 빠르게 데이터를 蒐集함으로써 顧客 相互 作用의 응답률을 높일 수 있습니다. 위와 비슷한 節次를 使用하여 自體 設問調査를 設定하고, 顧客 支援을 提供하고, 顧客의 豫約을 支援하세요.

繼續해서 WhatsApp Flows의 더 많은 機能을 살펴보세요. 只今 使用해보세요 .


- "漢字路" 한글한자자동변환 서비스는 교육부 고전문헌국역지원사업의 지원으로 구축되었습니다.
- "漢字路" 한글한자자동변환 서비스는 전통문화연구회 "울산대학교한국어처리연구실 옥철영(IT융합전공)교수팀"에서 개발한 한글한자자동변환기를 바탕하여 지속적으로 공동 연구 개발하고 있는 서비스입니다.
- 현재 고유명사(인명, 지명등)을 비롯한 여러 변환오류가 있으며 이를 해결하고자 많은 연구 개발을 진행하고자 하고 있습니다. 이를 인지하시고 다른 곳에서 인용시 한자 변환 결과를 한번 더 검토하시고 사용해 주시기 바랍니다.
- 변환오류 및 건의,문의사항은 juntong@juntong.or.kr로 메일로 보내주시면 감사하겠습니다. .
Copyright ⓒ 2020 By '전통문화연구회(傳統文化硏究會)' All Rights reserved.
 한국   대만   중국   일본