عودة إلى أخبار المطوّرين

Creating Surveys with WhatsApp Flows

٦ مارس ٢٠٢٤بواسطة‏‎Gafi G & Iryna Wagner‎‏

يعمل WhatsApp Flows على تحسين وتبسيط كيفية قيام نشاطك التجاري بجمع بيانات العملاء. يمكن لمؤسستك بسهولة الحصول على معلومات منظمة من التفاعلات مع العملاء، الذين بدورهم يستمتعون بتجربة مستخدم إيجابية على واتساب. يعمل WhatsApp Flows بشكل جيد لتجميع بيانات العملاء المحتملين، وإجراء استطلاعات رأي، ومساعدة العملاء على حجز المواعيد، وإرسال أسئلة العملاء واستفساراتهم، وغير ذلك الكثير.

أفضل ما في هذا الأمر أنه يمكنك تقديم كل هذه الخيارات لعملائك دون الحاجة إلى تطوير تطبيق وبرامج معقدة: ببساطة، استخدِم واتساب كواجهة أمامية واستعِن بـ webhook لتسجيل الردود كرسائل JSON ومعالجة المعلومات واسترداد البيانات التي تحتاج إليها.

باستخدام شركة وهمية كمثال، يوضح العرض التوضيحي هذا إعداد استطلاع رأي العملاء على واتساب باستخدام Webhooks. سيجمع استطلاع الرأي الملاحظات ككيفية اكتشاف العميل للشركة وأنواع الجولات المفضلة لديه؛ حتى تتمكن الشركة من تقديم خدمة أفضل للعملاء الحاليين والمستقبليين.

إجراء استطلاع رأي باستخدام WhatsApp Flows

المتطلبات الأساسية

للمتابعة، تأكَّد من وجود ما يلي:

العملية

  1. أنشِئ تطبيق Flask.
  2. اكتب الرمز البرمجي للغة Python لإنشاء عمليات الدفق ونشرها باستخدام واجهة WhatsApp Flows API. سيرسل الرمز البرمجي للغة Python أيضًا الدفق الذي تم نشره باستخدام واجهة API السحابة.
  3. أنشِئ webhook للاستماع إلى رسائل الدردشة.
  4. تشغيل التطبيق.

إذا كنتَ ترغب في معاينة المشروع، فيمكنك الاطِّلاع على الرمز البرمجي الكامل.

إنشاء استطلاع رأي من خلال واجهة WhatsApp Flows API

هناك طريقتان لإنشاء دفق: استخدام واجهة مستخدم أداة إنشاء عمليات الدفق أو واجهة Flows API. يستخدم العرض التوضيحي هذا واجهة Flows API لإعداد استطلاع الرأي برمجيًا.

لإنشاء دفق يستخدم البيانات الديناميكية من الخادم الخاص بك، يمكنك إنشاء نقطة نهاية من شأنها ربط استطلاع الرأي بالخادم الخاص بك. باستخدام نقطة النهاية، يمكنك التحكم في منطق التنقل بين شاشات الدفق، أو تعبئة بيانات الدفق من الخادم الخاص بك، أو إظهار/إخفاء المكونات على الشاشة استنادًا إلى تفاعل المستخدم.

لا يستخدم مثال دفق استطلاع الرأي الذي سيتم مناقشته أي نقطة نهاية لعدم وجود تبادل ديناميكي للبيانات بينه وبين الخادم. ستستخدم دردشة webhook لتسجيل المعلومات الواردة من استطلاع الرأي. بالإضافة إلى ذلك، يمكنك إرفاق عمليات الدفق بقالب رسالة في مدير واتساب.

إنشاء تطبيق Flask

أولاً، قم بإنشاء تطبيق Flask للتفاعل مع واجهة Flows API. قم بتشغيل الأمر التالي في الوحدة الطرفية لإنشاء بيئة افتراضية.

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 الخاص بك، انقر على واتساب > إعداد واجهة API في لوحة التنقل على الجانب الأيمن كما هو موضح في لقطة الشاشة أدناه.

معاينة إعداد واجهة API

أخيرًا، أنشِئ في نفس الدليل ملفًا بالاسم main.py يحتوي على منطق Python من أجل إنشاء عمليات الدفق وwebhook.

إنشاء الدفق

لإنشاء دفق، قم أولاً بإضافة الحزم التالية إلى 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)

تستدعي الوظيفة نقطة نهاية عمليات الدفق (flow_base_url) خلال مرور حمولة البيانات (flow_creation_payload) التي تحتوي على الاسم وفئة الدفق. القيم المحتملة للفئة هي: SIGN_UP أو SIGN_IN أو APPOINTMENT_BOOKING أو LEAD_GENERATION أو CONTACT_US أو CUSTOMER_SUPPORT أو SURVEY أو OTHER.

استبدِل <FLOW-NAME> بالاسم الذي تريده — على سبيل المثال، survey_flow.

بعد أن يقوم الرمز البرمجي بإنشاء الدفق، يستخرج 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) أو كائنات البيانات. تُسمى المفاتيح المرتبطة بقيم حمولة البيانات هذه أسماء، على غرار كيفية تعيين المتغيرات في لغات البرمجة.

تحتوي عناصر data-source أيضًا على معرفات تعمل كمفاتيح للقيم. يرسل الرمز البرمجي هذا المعرفات للاختيارات. على سبيل المثال، إذا اختار المستخدم Likely من أجل data-source أدناه، فسيتم إرسال الرمز البرمجي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"
        }
    ]
}
...
        

يتضمن الدفق شاشة واحدة تحتوي على تسعة أسئلة متعددة الخيارات، كما هو موضح أدناه.

دفق استطلاع الرأي

يمكنك استكشاف تفاصيل عناصر 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 لإرسال الدفق إلى مستخدم واتساب. تستدعي الوظيفة نقطة نهاية رسائل واجهة 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 واضح ومباشر. ويحتوي هذا المنطق على وظيفتين، webhook_get وwebhook_post لمعالجة طلبات GET وPOST بالترتيب. يستخدم الرمز البرمجي طلب GET عند إضافة webhook إلى تطبيق Meta. ويقوم بإرجاع طلب 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 باستخراج حمولة بيانات الرسالة ومعالجتها. ولأن الرمز البرمجي يناسب فقط حمولة بيانات الرسالة، فإنه بذلك يتناول الأخطاء عند تسجيل أي حمولة بيانات أخرى. لهذا السبب، يمكنك استخدام if للتحقق من وجود نص messages. بعد التحقق من وجود نص JSON messages، يتم التحقق مرة أخرى لاستخراج رقم هاتف المرسل فقط في حالة وجود نص text في حمولة البيانات 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)
        

بالإضافة إلى ذلك، يتعين عليك استخدام وظيفة أداة المساعدة التالية التي تُسمى 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 السحابة.

يجب أن يكون التطبيق قيد التشغيل قبل تكوين webhook في وحدة تحكم Meta للمطوّرين. ولذلك، استخدِم الأمر flask --app main run --port 5000 في الوحدة الطرفية لتشغيل الرمز البرمجي. سترى رسالة * Running on http://127.0.0.1:5000 في الوحدة الطرفية إذا تم إعداد كل شيء بشكل صحيح.

بعد ذلك، قم بتشغيل الأمر ngrokhttp 5000 في الوحدة الطرفية للحصول على عنوان URL الذي يرتبط بتطبيقك. انسخ هذا الرابط.

في وحدة تحكم Meta للمطوّرين، ضمن واتساب في لوحة التنقل على الجانب الأيمن، انقر على تكوين.

تكوين واتساب

في بطاقة Webhook، انقر على تعديل. بعد ذلك، في مربع الحوار الموجود في حقل عنوان URL للاستدعاء، أضِف عنوان URL الذي تم نسخه وقم بإرفاق /webhook. في حقل التحقق من الرمز، أضِف الرمز من المتغير .env الخاص بالملف TOKEN.

بمجرد الانتهاء، انقر على تحقق وحفظ. وسيتم إغلاق مربع الحوار. انقر على إدارة وقم بتحديد حقل الرسائل. يجب أن تظهر المعلومات بصورة مشابهة للصورة أدناه، مع عنوان URL للاستدعاء والمعلومات التي تم إخفاؤها ضمن التحقق من الرمز والرسائل المدرجة ضمن حقول Webhook.

تكوين واتساب

أصبح webhook جاهز الآن.

تشغيل التطبيق

في مثيل الوحدة الطرفية الجديدة، قم بتشغيل أمر cURL أدناه لإنشاء دفق.

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

في مثيل الوحدة الطرفية الذي يعرض نتائج webhook الخاص بك، سترى رسالة مشابهة لتلك الموضحة أدناه.

تشغيل التطبيق

عندما يرسل مستخدم ما رسالة إلى رقم واتساب الخاص بك، فإنه بذلك يتلقى الدفق كرد، كما في لقطة الشاشة أدناه.

مثال على استطلاع رأي الدفق

يتلقى ردًا بإجاباتهم بعد إكمال استطلاع الرأي.

مثال على استجابة استطلاع رأي الدفق

أي رسالة يرسلها المستخدم إلى رقمك تؤدي إلى تشغيل الدفق. بالنسبة إلى حالة الاستخدام الخاصة بك، قم بتخصيص الرمز البرمجي الخاص بك لإرسال دفق استطلاع الرأي فقط في حالات معينة، على سبيل المثال، بعد قيام المستخدم بإجراء دردشة مع نشاطك التجاري.

الخلاصة

يعمل WhatsApp Flows على تحسين تجربة المستخدم من خلال واجهات تفاعلية لجمع البيانات المنظمة، مثل الردود على استطلاع الرأي لشركة سياحية افتراضية. يقوم النشاط التجاري فقط بإنشاء تطبيق Flask، وتنفيذ الرمز البرمجي للغة Python لإنشاء عمليات الدفق ونشرها عبر واجهة WhatsApp Flows API، وإعداد webhook للاستماع إلى الرسائل الواردة، وتشغيل التطبيق لتمكين مجموعة الردود على استطلاع الرأي.

يُتيح WhatsApp Flows لمؤسستك تجميع البيانات بصورة سهلة وبسيطة؛ حيث يمكن أن يساعد على تحسين معدلات إكمال تفاعلات العملاء. استخدِم عملية مشابهة لإعداد استطلاعات الرأي الخاصة بك وتقديم دعم العملاء ومساعدتهم على حجز المواعيد وغير ذلك الكثير.

تابِع استكشاف إمكانات WhatsApp Flows. جرِّبه الآن.