عنوان webhook يشبه الباب. يمكن لأي شخص يعرف العنوان أن يرسل إليه طلباً. إذا لم تتحققوا من العنوان الذي ترسل إليه Meta أحداث الرسائل، فعندما يرسل أحدهم حدثاً مزيفاً يقول «العميل قدّم طلباً» أو «تم تأكيد الدفع»، سيعتبره مساركم حقيقياً. ولهذا تضيف Meta توقيعاً إلى كل طلب. في هذا المقال نشرح ما هو هذا التوقيع وكيف يُفحص في n8n. لم نصادف هذا الموضوع كثيراً في المصادر التركية؛ فعادةً يُشرح فقط خطوة التحقق عند الإعداد الأول ويُتجاهل جانب التوقيع.
الإجابة المختصرة
تضيف Meta ترويسة X-Hub-Signature-256 إلى كل طلب POST. وهي قيمة HMAC-SHA256 للجسم الخام للطلب، محسوبة باستخدام المفتاح السري لتطبيقكم (app secret). تجرون الحساب نفسه في n8n وتقارنونه بالقيمة الموجودة في الترويسة؛ وإذا لم تتطابقا تتركون الطلب دون معالجة. أكثر الأخطاء شيوعاً هو محاولة حساب الـ hash على JSON بعد تحليله.
هناك عمليتا تحقق منفصلتان
كثيراً ما يحدث خلط بين الاثنتين. الأولى تحدث عند تعريف webhook لأول مرة: ترسل Meta طلب GET إلى عنوانكم يحتوي على hub.mode=subscribe وhub.verify_token وhub.challenge. verify_token هو نص تختارونه أنتم. إذا طابقت القيمة الواردة القيمة التي خزّنتموها، تعيدون قيمة hub.challenge مع الرمز 200. وبحسب وثائق Meta، فإن أي استجابة أخرى، بما فيها رموز 400، تترك العنوان غير موثَّق ولن يصل أي webhook.
الثانية هي فحص التوقيع الذي يعمل باستمرار، وهي التي توفر الأمان الحقيقي. لا يُطلب verify_token إلا مرة واحدة عند الإعداد. وبعد ذلك، فإن التوقيع هو ما يتحقق من مصدر أحداث الرسائل الواردة.
كيف يتم إنشاء التوقيع؟
الصيغة التي تصفها Meta هي التالية: تبدأ قيمة ترويسة X-Hub-Signature-256 بـ «sha256=» ثم يليها hash. هذا الـ hash هو HMAC-SHA256 محسوب باستخدام جسم الطلب والمفتاح السري لتطبيقكم. ما عليكم فعله هو إجراء الحساب نفسه باستخدام الجسم الوارد ومفتاحكم السري، ثم مقارنة النتيجة بالجزء الذي يلي «sha256=» في الترويسة. إذا تطابقا فالحمولة صالحة بحسب تعبير وثائق Meta وتعيدون 200؛ وإذا لم يتطابقا تعيدون رمزاً غير 200.
المفتاح السري هو قيمة app secret الموجودة في إعدادات تطبيقكم في لوحة Meta for Developers. وبدلاً من كتابتها كنص عادي داخل الـ workflow، من الأفضل حفظها في نظام credentials الخاص بـ n8n أو في متغير بيئة.
لماذا الجسم الخام ضروري؟
يعتمد HMAC على البايتات الدقيقة للجسم. فمجرد اختلاف في مسافة واحدة أو في حرف هروب واحد ينتج hash مختلفاً تماماً. تقوم عقدة Webhook في n8n بتحليل JSON الوارد تلقائياً؛ وعندما تحوّلون الكائن المحلَّل إلى نص مرة أخرى، قد يتغير ترتيب المفاتيح والمسافات وطريقة كتابة الأحرف الخاصة. فلن يطابق ناتجكم الصيغة التي استخدمتها Meta، وسيبدو حتى الطلب الصحيح «غير صالح». ولهذا السبب بالذات يوجد موضوع في منتدى مجتمع n8n يصف توقيعات لا تتطابق أبداً: ففي الجسم الخام لدى المرسل كانت الشرطات المائلة مُهرَّبة بحرف الهروب، بينما لم تكن كذلك في الجسم الذي تعرضه n8n.
الحل هو حساب الـ hash على البايتات الخام. ولهذا يوفر عقدة Webhook في n8n خيار Raw Body. تصف الوثائق هذا الخيار بأنه استقبال العقدة للبيانات بصيغة خام مثل JSON أو XML. ووفق رواية من المجتمع، عند تفعيل هذا الخيار تُحفظ بايتات الطلب كبيانات ثنائية (binary) إلى جانب الجسم المحلَّل. ولأن هذا السلوك قد يتغير بين الإصدارات، تحققوا منه في إعدادكم الخاص بطلب تجريبي.
الإعداد في n8n
الهيكل العام للمسار كالتالي:
- عقدة Webhook: فعّلوا خيار Raw Body. وإذا كنتم ستستخدمون طريقتين منفصلتين، فإن تعريف مسارين منفصلين لـ GET (التحقق) وPOST (الأحداث) يبسّط العمل.
- حساب التوقيع: يمكنكم استخدام عملية Hmac في عقدة Crypto. وفقًا للتوثيق، يتم دعم SHA256 ويمكن أن يكون الناتج HEX أو BASE64. وبما أن Meta ترسل قيمة التجزئة بصيغة hex، اختاروا HEX وقدّموا الجسم الخام (raw body) كمدخل. إذا فضّلتم عقدة Code فستحتاجون إلى وحدة crypto الخاصة بـ Node؛ فلأسباب أمنية تقيّد n8n استخدام الوحدات وتتطلب الإعداد NODE_FUNCTION_ALLOW_BUILTIN=crypto. وإذا كنتم تستخدمون Task Runner، فيجب إجراء هذا الإعداد على Task Runner وليس على العقدة الرئيسية.
- المقارنة: أزيلوا البادئة "sha256=" من القيمة الموجودة في الترويسة وقارنوها بقيمة hex المحسوبة. وإن أمكن، استخدموا مقارنة مقاومة لهجمات التوقيت؛ ففي عقدة Code توفّر crypto.timingSafeEqual ذلك.
- عقدة If: عند عدم تطابق الطلب، أوقفوا التدفق وأعيدوا رمزًا من فئة 400 باستخدام Respond to Webhook. وإذا تطابق فاستمروا.
افتراضيًا ترد عقدة Webhook فورًا بالرسالة "Workflow got started". وإذا أردتم إعادة رموز مختلفة بحسب نتيجة التوقيع، فعليكم تحويل وضع الاستجابة إلى عقدة Respond to Webhook.
متى يجب إرجاع 200 إلى Meta؟
بحسب ما توضحه Meta، تُعاد محاولة عمليات التسليم الفاشلة فورًا ثم بوتيرة متناقصة على مدى 7 أيام. أي أنه إذا كان تدفقكم بطيئًا أو يُرجع أخطاء، فقد تصلكم الرسالة نفسها مرات عديدة. وهناك إجراءان مفيدان. الأول: التحقق من التوقيع وإرجاع 200 فورًا، وتنفيذ المهام الطويلة بعد ذلك. والثاني: تسجيل معرّف كل رسالة في جدول وتخطي الرسائل التي سبقت معالجتها. وتذكر Meta أيضًا أن الطلبات قد تصل دفعات، وأن الطلب الواحد قد يحتوي على ما يصل إلى 1000 تحديث، وأن ذلك غير مضمون؛ لذا عالجوا كل طلب على حدة وضمن حلقة تكرار.
طبقات إضافية
مع أن التحقق من التوقيع كافٍ، فهناك إجراءات يمكن إضافتها فوقه. توثّق Meta أيضًا دعم TLS المتبادل (mTLS) للـ webhooks؛ ويجب تفعيله بشكل منفصل لكل تطبيق. وعلى جانب n8n، تتوفر في عقدة Webhook خيار قائمة سماح لعناوين IP، وتتلقى الطلبات الخارجية الرمز 403، لكن لكي ينجح ذلك عليكم إبقاء العناوين التي تستخدمها Meta محدّثة، كما أن قائمة العناوين قد تتغير. لذلك ضعوا هذا الإجراء إلى جانب التوقيع لا بدلًا منه. ويجب أن تحمل نقطة النهاية شهادة TLS صالحة؛ فـ Meta لا تدعم الشهادات الموقّعة ذاتيًا.
ويؤدي اختيار الاستضافة دورًا هنا أيضًا. ففي n8n الذي يعمل على خادمكم الخاص تديرون بأنفسكم متغيرات البيئة وإعدادات الوكيل العكسي والشهادة؛ أما في السحابة فيبقى جزء من ذلك لدى المزوّد. وللمقارنة يمكنكم الاطلاع على مقالنا عن تكلفة self-hosted مقابل cloud. أما مكان تخزين بيانات العملاء الواردة من الـ webhook فموضوع مستقل: تناولناه في مقال الأتمتة المتوافقة مع KVKK.
طريقة عملية للاختبار
أعدّوا التدفق أولًا باستخدام Test URL، وتأكدوا من أن قيمة التجزئة الصحيحة تُحسب، ثم انتقلوا إلى Production URL. ووفقًا لتوثيق n8n، لا يكون webhook الاختبار مسجَّلًا إلا أثناء وضع الاستماع، بينما يُسجَّل عنوان الإنتاج عند نشر الـ workflow. وإذا كان عنوان الإنتاج معرَّفًا في لوحة Meta فلن يرى التدفق الموجود في وضع الاختبار أي شيء. بعد ذلك استخدموا عمدًا مفتاحًا سريًا خاطئًا وتحققوا من أن التوقيع يُرفض. فالتحقق الذي لم يُختبر جانب الرفض فيه لا يُعدّ قد أثبت أنه يعمل.
من الجيد أن يكون هذا الأساس متينًا قبل بناء تدفقات أكثر تقدمًا، مثل ربط الرسائل الواردة من الإعلانات بالمبيعات؛ وقد تناولنا الجانب الإعلاني في مقال ctwa_clid.
الأسئلة الشائعة
هل التحقق من توقيع webhook الخاص بـ WhatsApp إلزامي؟
توضح وثائق Meta أنه ينبغي التحقق من التوقيع وإرجاع 200 إن كان صالحًا. وإذا لم تتحققوا، فبإمكان أي شخص يعرف عنوان الـ webhook الخاص بكم إرسال حدث رسالة مزيف، وسيظنه تدفقكم رسالة حقيقية من عميل.
لماذا لا يمكنني حساب التوقيع من JSON بعد تحليله؟
يُحسب HMAC على البايتات الفعلية للجسم تمامًا. وعندما تحللون JSON ثم تعيدون كتابته، قد تتغير المسافات وحروف الإفلات وترتيب الحقول، ولن تتطابق قيمة التجزئة. لهذا يجب العمل على الجسم الخام.
ماذا يحدث إذا أرسلت Meta الحدث نفسه أكثر من مرة؟
تعيد Meta محاولة عمليات التسليم الفاشلة فورًا ثم بوتيرة متناقصة على مدى 7 أيام. لذلك يجب ألا يعالج تدفقكم معرّف الرسالة نفسه مرة ثانية.
المصادر: Meta for Developers, "Create a webhook endpoint" · n8n Docs, Webhook node · n8n Docs, Crypto node · n8n Docs, Modules in the Code node · n8n Community, مثال على التحقق من HMAC · n8n Community, مشكلة الجسم الخام. قد تختلف الواجهة والسلوك بين الإصدارات؛ اختبروا ذلك على إعدادكم الخاص.
لنراجع معًا أمان تدفق الـ webhook لديكم
دعونا نراجع في نصف ساعة التحقق من التوقيع والأحداث المكررة ومعالجة الأخطاء في مسار WhatsApp لديكم.