أساسيات الويب هوك
تُسلِّم واجهة برمجة تطبيقات واتساب السحابية الأحداث إلى تطبيقك عبر نظام ويب هوك قائم على الدفع. بدلاً من استطلاع نقطة نهاية بحثًا عن أحداث جديدة، تُرسِل خوادم ميتا حمولة JSON عبر POST إلى عنوان URL تسجِّله — نقطة نهاية الويب هوك الخاصة بك. يعالج خادمك الحمولة ويُعيد HTTP 200 لتأكيد الاستلام.
تعمل طريقتا HTTP على نفس العنوان:
- GET — تحدي التحقق لمرة واحدة أثناء التسجيل. يجب أن تُعيد نقطة النهاية الخاصة بك معلمة الاستعلام
hub.challengeكنص عادي. - POST — إشعارات الأحداث المباشرة. كل رسالة واردة، وتحديث حالة، وحدث حساب يصل هنا.
أهم القيود التي يجب استيعابها قبل كتابة أي كود:
- الاستجابة خلال 20 ثانية — أي شيء أبطأ يُفعِّل إعادة المحاولة. أكد فورًا، عالج بشكل غير متزامن.
- التسليم at-least-once — قد يصل نفس الحدث أكثر من مرة. يجب أن يكون المعالج الخاص بك متطابقًا (idempotent).
- لا ضمان للترتيب — قد تصل الأحداث خارج التسلسل. لا تفترض أبدًا الترتيب الزمني.
- حد حجم الحمولة 3 ميجابايت — لن تتجاوز حمولات الويب هوك الفردية 3 ميجابايت. لا يتم تضمين ملفات الوسائط مضمَّنة.
- HTTPS مطلوب — ترفض ميتا نقاط النهاية HTTP العادية. شهادة SSL صالحة مطلوبة.
مرجع خصائص الويب هوك الكامل
| الخاصية | القيمة / المواصفة | ملاحظات |
|---|---|---|
| البروتوكول | HTTPS فقط | شهادة SSL صالحة مطلوبة. يتم رفض HTTP تمامًا. |
| الاتجاه | الموفِّر → المستهلك (دفع) | ميتا تدفع إليك. لا حاجة للاستطلاع. |
| طريقة التحقق | GET + hub.verify_token | لمرة واحدة عند التسجيل. أعد hub.challenge كنص عادي. |
| المصادقة على الأحداث | X-Hub-Signature-256 HMAC-SHA256 | موقَّع باستخدام App Secret. تحقق من كل POST. |
| طريقة HTTP للأحداث | POST | POST دائمًا. لا تستخدم GET للأحداث المباشرة. |
| تنسيق البيانات | JSON | Content-Type: application/json |
| مهلة الاستجابة | 20 ثانية | تجاوزه يُفعِّل إعادة المحاولة. أعد 200 فورًا. |
| معيار النجاح | HTTP 200 | أي رمز غير 200 يُفعِّل آلية إعادة المحاولة. |
| حد حجم الحمولة | 3 ميجابايت | الوسائط غير مضمَّنة — مُشار إليها بالمعرف. |
| مدة إعادة المحاولة | حتى 7 أيام | backoff أسي. راجع الجدول الكامل أدناه. |
| ضمان التسليم | at-least-once | التكرارات ممكنة. اجعل المعالج متطابقًا (idempotent). |
| ضمان الترتيب | لا يوجد | قد تصل الأحداث خارج الترتيب الزمني. |
| مستويات الويب هوك | رقم الهاتف + WABA | رقم الهاتف له أولوية على fallback لـ WABA. |
| إعادة التشغيل اليدوية | غير مدعومة | لا إعادة تشغيل أصلية. تُفقد الأحداث بعد 7 أيام. |
| خوارزمية التوقيع | HMAC-SHA256 | المفتاح = App Secret. المدخلات = بايتات الجسم الخام. |
| رأس التوقيع | X-Hub-Signature-256 | التنسيق: sha256=<hex> |
| عمليات التسليم المتزامنة | متعددة في الثانية | الأرقام عالية الحجم تستقبل العديد من الأحداث/الثانية. |
جميع حقول اشتراك الويب هوك
تشترك في حقول فردية في لوحة مطوري ميتا (WhatsApp → Configuration → Webhooks → Manage). الاشتراك في messages إلزامي لمعظم عمليات التكامل. يمثل كل حقل فئة من الأحداث — اشترك فقط في ما تحتاجه لتقليل حجم الـ payload.
| الحقل | التغطية | الأولوية |
|---|---|---|
| messages | جميع رسائل العملاء الواردة + جميع تحديثات الحالة الصادرة (sent / delivered / read / failed). الحقل الأساسي لأي تكامل مراسلة. | اشترك دائمًا |
| account_update | انتهاكات السياسة (ACCOUNT_VIOLATION) والقيود النشطة (ACCOUNT_RESTRICTION) على أرقام هواتفك. أساسي للمراقبة في البيئة الإنتاجية. |
موصى به |
| message_template_status_update | أحداث الموافقة على القوالب، أو الرفض، أو الإيقاف المؤقت، أو التعطيل. يُفعَّل عندما تغير ميتا حالة قالب أرسلته. | موصى به |
| phone_number_quality_update | تغييرات في تقييم الجودة (GREEN / YELLOW / RED) لأرقام هواتفك. تؤثر الجودة على مستوى المراسلة الخاص بك. اشترك للكشف عن التدهور مبكرًا. | موصى به |
| phone_number_name_update | أحداث الموافقة على اسم العرض أو رفضه. يُفعَّل عندما تراجع ميتا اسمًا أرسلته لرقم واتساب للأعمال الخاص بك. | حسب الحالة |
| business_capability_update | تغييرات في مستوى حد المراسلة — عندما ترفع ميتا أو تخفض مستوى المحادثات اليومية الخاص بك (1K / 10K / 100K / Unlimited). | حسب الحالة |
| flows | تفاعلات واتساب فلو — أحداث إرسال البيانات عندما يكمل مستخدم أو يتفاعل مع فلو مرفق برقمك. | إذا كنت تستخدم فلو |
| security | أحداث رمز التحقق بخطوتين. يُفعَّل عند تغيير أو تعطيل رمز الهاتف لرقم هاتف. | اختياري |
| message_template_components_update | يُفعَّل عندما تعدل ميتا مكونات قالب مُوافق عليه (نادر — قد تعدل ميتا القوالب للامتثال للسياسة). | اختياري |
| account_alerts | تنبيهات الفواتير، وتحذيرات السعة، وإشعارات تشغيلية أخرى على مستوى الحساب من ميتا. | اختياري |
الأحداث المُسلَّمة لكل حقل اشتراك
حقل messages — أنواع الرسائل الواردة
يُسلِّم حقل messages فئتين: رسائل واردة من العملاء (يتم تحديدها بمصفوفة messages في كائن value) وتحديثات حالة التسليم الصادرة (يتم تحديدها بمصفوفة statuses). تحقق من المصفوفة الموجودة قبل المعالجة.
| msg.type | موقع المحتوى | ملاحظات |
|---|---|---|
| text | msg.text.body | نص عادي لجسم الرسالة. قد يتضمن روابط URL. |
| image | msg.image.id, .mime_type, .caption | المعرف → حل عنوان URL → تنزيل. التعليق اختياري. |
| audio | msg.audio.id, .voice | voice: true إذا تم التسجيل داخل التطبيق. نزِّل دائمًا فورًا. |
| video | msg.video.id, .caption | التعليق اختياري. |
| document | msg.document.id, .filename, .mime_type | اسم الملف مُضمَّن — احفظه. |
| sticker | msg.sticker.id, .animated | علامة animated تميز بين WebP وملصق متحرك. |
| location | msg.location.latitude, .longitude, .name, .address | الاسم والعنوان اختياريان. |
| contacts | msg.contacts[n].name, .phones | مصفوفة — قد يشارك العميل جهات اتصال متعددة. |
| reaction | msg.reaction.emoji, .message_id | يشير إلى معرف الرسالة التي تفاعل معها العميل. |
| interactive | msg.interactive.type، ثم .button_reply أو .list_reply | تحقق من type قبل قراءة الكائن الفرعي. |
| order | msg.order.catalog_id, .product_items | واتساب للتجارة — طلب منتج من الكتالوج. |
| system | msg.system.body, .type | أحداث النظام: غيّر العميل الرقم، إلخ. |
| button | msg.button.text, .payload | نقرة زر الرد السريع من رسالة قالب. |
| referral | msg.referral.source_url, .source_type, .source_id | بيانات إحالة إعلان انقر-إلى-واتساب بجانب الرسالة. |
حقل messages — تحديثات الحالة الصادرة
| قيمة الحالة | المعنى | حقول إضافية |
|---|---|---|
| sent | تم قبول الرسالة من قِبل ميتا وإحالتها إلى واتساب. لم تُسلَّم بعد إلى الجهاز. | timestamp, recipient_id, conversation, pricing |
| delivered | وصلت الرسالة إلى جهاز المستلم (علامة رمزية مزدوجة). | timestamp, recipient_id, conversation, pricing |
| read | فتح المستلم الدردشة (علامة زرقاء مزدوجة). يُفعَّل فقط إذا كانت إيصالات القراءة مفعَّلة. | timestamp, recipient_id |
| failed | فشل التسليم بشكل دائم. تحقق من status.errors[0].code للخطأ المحدد. | مصفوفة errors تحتوي على code و title |
الجدول الزمني الدقيق لإعادة المحاولة مع توقيتات backoff
تعيد ميتا محاولة تسليم الويب هوك الفاشل لمدة تصل إلى 7 أيام باستخدام backoff أسي. تعني "فاشل" أن نقطة النهاية الخاصة بك أعادت رمزًا غير 200، أو انتهت مهلتها (استغرقت أكثر من 20 ثانية)، أو كانت غير قابلة للوصول. بعد 7 أيام، يتم التخلص من الحدث نهائيًا — لا يوجد مسار استرداد أصلي.
سلوك رموز استجابة HTTP
ما تُعيده إلى ميتا يحدد ما إذا كان الحدث يُعتبر مُسلَّمًا، أو تمت إعادة محاولته، أو يُفعِّل تنبيهًا. شجرة القرار أبسط مما يتوقع معظم المطورين — كل شيء ثنائي: 200 يعني النجاح، وأي شيء آخر يعني إعادة المحاولة.
مواصفات توقيع HMAC-SHA256 الكاملة
يتضمن كل طلب POST من ميتا توقيعًا تشفيريًا يتيح لك التحقق من أن الطلب جاء فعليًا من ميتا ولم يتم العبث به أثناء النقل. تخطي هذا التحقق يعني أن أي مهاجم يكتشف عنوان الويب هوك الخاص بك يمكنه تغذية بيانات عشوائية لتطبيقك.
تنسيق الرأس
التحقق في Node.js
التحقق في Python
JSON.stringify(JSON.parse(body)) بايتات مختلفة عن الأصل — يمكن أن تتغير المسافات البيضاء، وترتيب المفاتيح، ودقة الأرقام كلها. احسب HMAC دائمًا على البايتات الدقيقة التي وصلت في طلب HTTP، قبل أي تحويل.سلوك رموز استجابة HTTP
ما تُعيده إلى ميتا يحدد ما إذا كان الحدث يُعتبر مُسلَّمًا، أو تمت إعادة محاولته، أو يُفعِّل تنبيهًا. شجرة القرار أبسط مما يتوقع معظم المطورين — كل شيء ثنائي: 200 يعني النجاح، وأي شيء آخر يعني إعادة المحاولة.
مواصفات توقيع HMAC-SHA256 الكاملة
يتضمن كل طلب POST من ميتا توقيعًا تشفيريًا يتيح لك التحقق من أن الطلب جاء فعليًا من ميتا ولم يتم العبث به أثناء النقل. تخطي هذا التحقق يعني أن أي مهاجم يكتشف عنوان الويب هوك الخاص بك يمكنه تغذية بيانات عشوائية لتطبيقك.
تنسيق الرأس
التحقق في Node.js
التحقق في Python
JSON.stringify(JSON.parse(body)) بايتات مختلفة عن الأصل — يمكن أن تتغير المسافات البيضاء، وترتيب المفاتيح، ودقة الأرقام كلها. احسب HMAC دائمًا على البايتات الدقيقة التي وصلت في طلب HTTP، قبل أي تحويل.قائمة التحقق الأمنية
X-Hub-Signature-256 مفقودًا، أو مشوهًا، أو غير متطابق. بدون هذا، يقبل نقطة النهاية الخاصة بك أحداثًا مُزوَّرة من أي شخص.crypto.timingSafeEqual() في Node.js، hmac.compare_digest() في Python. تساوي السلاسل البسيط (=== / ==) يُسرِّب معلومات توقيت يمكن للمهاجمين استغلالها لتزوير التوقيعات حرفًا بحرف.express.raw() على مسار الويب هوك. في FastAPI: await request.body() قبل أي إزالة تسلسل لـ JSON.process.env.WHATSAPP_APP_SECRET (Node.js) أو os.environ["WHATSAPP_APP_SECRET"] (Python). قم بتدويره فورًا إذا تسرَّب.msg.id التي تمت معالجتها في Redis مع TTL يزيد عن 24 ساعة. تحقق قبل المعالجة. تخطَّ التكرارات بصمت — لا تُخطئ أبدًا بسببها.crypto.randomBytes(32).toString('hex').الأخطاء الشائعة والحلول
| الخطأ / العرض | السبب الجذري | الحل |
|---|---|---|
| فشل التحقق عند التسجيل | إعادة JSON بدلاً من نص عادي، أو رمز تحقق خاطئ، أو إعادة hub.challenge مغلفًا في كائن JSON |
أعد hub.challenge كنص عادي مع Content-Type: text/plain. سلسلة دقيقة — بدون تغليف JSON. |
| يفشل HMAC دائمًا | حساب HMAC بعد تحليل JSON، أو استخدام رمز الوصول بدلاً من App Secret، أو الترميز المزدوج للجسم | استخدم express.raw() في Express. استدعِ await request.body() في FastAPI قبل التحليل. المفتاح = App Secret من App Settings → Basic. |
| الأحداث لا تصل على الإطلاق | حقل messages غير مُشترك، أو الويب هوك غير محفوظ/غير مُتحقَّق |
لوحة التحكم → WhatsApp → Configuration → Webhooks → Manage → تفعيل حقل messages. تأكد من أن الويب هوك يظهر كمُتحقَّق. |
| استقبال نفس الحدث عدة مرات | التسليم at-least-once — سلوك متوقع، وليس خطأ | نفِّذ إزالة التكرار باستخدام msg.id كمفتاح Redis مع TTL لمدة 24 ساعة. تحقق قبل المعالجة. |
| الأحداث تصل خارج الترتيب | لا ضمان للترتيب — حسب التصميم | استخدم حقل timestamp للفرز عند بناء سلاسل المحادثة. لا تفترض أبدًا ترتيب تسليم تسلسلي. |
| تستمر إعادة محاولات ميتا في القدوم | المعالج يُعيد رمزًا غير 200، أو انتهاء المهلة (>20 ثانية)، أو التعطل | أعد 200 فورًا. انقل كل المعالجة إلى قائمة انتظار غير متزامنة. غلِّف المعالج في try-catch. راقب استقرار الخادم. |
| تنزيل الوسائط يُعيد 401 | استخدام رمز خاطئ أو انتهاء صلاحية الرمز | تتطلب تنزيلات الوسائط نفس رمز الوصول المستخدم للواجهة البرمجية. تأكد من أنه رمز مستخدم نظام دائم، وليس رمز مستخدم مؤقت. تحقق من أن الرمز لم يتم سحبه. |
مصفوفة messages مفقودة من الـ payload |
إنه تحديث حالة — المصفوفة هي statuses، وليس messages |
تحقق من value.messages و value.statuses بشكل منفصل. كلاهما يصل تحت اشتراك حقل messages. |
| رقم المرسل لا يحتوي على بادئة + | تُسلِّم Cloud API from بدون بادئة + (مثلاً 15550001234 وليس +15550001234) |
طَبِّع عند الاستلام: '+' + msg.from للحصول على تنسيق E.164. يُطَبِّع SocialHook هذا تلقائيًا. |
| Timestamp هو سلسلة، وليس عددًا صحيحًا | تُسلِّم Cloud API timestamp كسلسلة طابع زمني لـ Unix |
دائمًا parseInt(msg.timestamp, 10) (Node.js) أو int(msg["timestamp"]) (Python) قبل عمليات التاريخ. |
SocialHook: طبقة الويب هوك المُدارة
كل ما في هذا المرجع — التحقق من HMAC، استخراج الـ payload من الغلاف المتداخل، تحليل الطابع الزمني، تطبيع المرسل، معالجة إعادة المحاولة، بنية إزالة التكرار، التوجيه على مستوى WABA مقابل مستوى الهاتف — هو عمل بنية تحتية لا يميِّز منتجك عليه.
يتعامل SocialHook مع طبقة ويب هوك Cloud API بأكملها ويُسلِّم حدثًا مُطَبَّعًا إلى نقطة النهاية الخاصة بك، لذا يرى كود تطبيقك فقط JSON نظيفًا ومتسقًا — أبدًا الـ payload الخام المتداخل من ميتا مع غرابتها.
| الجانب | Cloud API الخام | SocialHook مُطَبَّع |
|---|---|---|
| استخراج الرسالة | entry[0].changes[0].value.messages[0] | كائن message مسطَّح على المستوى الأعلى |
| تنسيق المرسل | "15550001234" — بدون بادئة + | "+15550001234" — مُطَبَّع وفق E.164 |
| الطابع الزمني | "1747231892" — سلسلة | 1747231892 — عدد صحيح |
| التحقق من HMAC | أنت تُنفِّذه | تم — signature_verified: true |
| إعادة المحاولة أثناء تعطل خادمك | تعيد ميتا المحاولة (7 أيام) | تعيد SocialHook المحاولة (3× أسي) |
| تنسيق متعدد القنوات | مخطط مختلف لكل منصة | نفس المخطط: واتساب + فيسبوك + إنستغرام |
| سجلات التسليم | غير متوفرة | سجل كامل لكل حدث |
| التكلفة الشهرية | تكاليف بنية الخادم الخاصة بك | 50 دولار ثابت |
يغطي SocialHook واتساب، و فيسبوك ماسنجر، و رسائل إنستغرام المباشرة — جميع قنوات المراسلة الثلاث من ميتا — تحت حساب واحد، وعنوان ويب هوك واحد، وتنسيق payload مُطَبَّع واحد. راجع مرجع الـ payload الكامل أو ابدأ بـ الدليل السريع في 5 دقائق.
أسئلة شائعة
msg.id في Redis مع TTL لمدة 24 ساعة باستخدام SET ... NX. قبل معالجة أي حدث، تحقق مما إذا كان المعرف موجودًا. إذا كان موجودًا، تخطَّه. إذا لم يكن، أضفه وعالجه. هذا يمنع سجلات CRM المكررة، وردود الذكاء الاصطناعي المزدوجة، والمدفوعات المكررة.sha256=<hex بأحرف صغيرة مكون من 64 حرفًا>. قيمة الـ hex هي HMAC-SHA256 لبايتات جسم الطلب الخام، باستخدام App Secret كمفتاح (تجده في App Settings → Basic — هذا مختلف عن رمز الوصول الخاص بك). أزل البادئة sha256= قبل المقارنة. استخدم دائمًا مقارنة آمنة ضد التوقيت.messages، وتحتوي تحديثات الحالة على مصفوفة statuses. تحقق دائمًا من المصفوفة الموجودة قبل المعالجة — محاولة قراءة value.messages[0] على payload تحديث حالة تُعيد undefined.metadata.phone_number_id، وأضف ويب هوك على مستوى رقم الهاتف فقط حيث يكون التوجيه المختلف مطلوبًا.تمت قراءة المرجع.
الآن استقبل أول ويب هوك.
أنت تعرف المواصفة. يتعامل SocialHook مع التحقق من HMAC، وتطبيع الـ payload، ومنطق إعادة المحاولة، وتسجيل التسليم — لذا يرى كود تطبيقك فقط JSON نظيفًا. اربط رقمك في أقل من 5 دقائق.