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

عندما يقول أحدهم "الويب هوكات معطلة"، فعادةً ما يعني أحد ثلاثة أشياء: الأحداث لم تصل أبدًا، الأحداث وصلت مرتين، أو الأحداث وصلت بترتيب مُربك. من وجهة نظرهم، النظام "فقد" شيئًا. من وجهة نظرك، المُزوِّد أرسلها، لكن نقطة النهاية لديك لم تقبلها، أو لم تعالجها، أو لم تسجّلها كما توقعت.
الويب هوكات تعمل عبر الإنترنت العام. الطلبات تتأخر، تُعاد المحاولة، وأحيانًا تُسلَّم خارج الترتيب. معظم المزودين يعيدون المحاولة بقسوة عندما يرون مهلات أو استجابات ليست من فئة 2xx. هذا يحوِّل عائقًا صغيرًا (قاعدة بيانات بطيئة، نشر، عطل مؤقت) إلى تكرارات وحالات تسابق.
السجلات السيئة تجعل هذا يبدو عشوائيًا. إذا لم تتمكن من إثبات ما إذا كان الطلب أصليًا، لا يمكنك التصرف بأمان بناءً عليه. إذا لم تستطع ربط شكوى العميل بمحاولة تسليم محددة، ستبدأ بالتخمين.
تركز معظم الأخطاء الواقعية ضمن بضعة صناديق:
الهدف العملي بسيط: قبول الأحداث الحقيقية مرة واحدة، رفض المزيفة، وترك أثر واضح حتى تتمكن من تصحيح تقرير عميل خلال دقائق.
الويب هوك هو مجرد طلب HTTP يرسله المزود إلى نقطة نهاية تعرضها. أنت لا تسحبه مثل استدعاء API. المرسل يدفعه عند حدوث شيء، ومهمتك استقباله، الرد بسرعة، ومعالجته بأمان.
تتضمن عملية التسليم النموذجية جسم الطلب (عادة JSON) بالإضافة إلى رؤوس تساعدك على التحقق والتتبع. الكثير من المزودين يضيفون طابعًا زمنيًا، نوع الحدث (مثل invoice.paid) ومعرّف حدث فريد يمكنك تخزينه لاكتشاف التكرارات.
الجزء الذي يفاجئ الفرق: التسليم نادرًا ما يكون "مرة واحدة بالضبط". معظم المزودين يهدفون إلى "مرة واحدة على الأقل"، مما يعني أن نفس الحدث قد يصل عدة مرات، أحيانًا بفواصل دقائق أو ساعات.
تحدث إعادة المحاولات لأسباب مملة: الخادم بطيء أو يتجاوز المهلة، تعيد أنت 500، شبكتهم لم ترَ 200 الخاصة بك، أو نقطة النهاية غير متاحة مؤقتًا أثناء النشر أو ذروة الحركة.
المهلة خاصةً مزعجة. قد يستلم خادمك الطلب ويكمل المعالجة، لكن الرد لا يصل إلى المرسل في الوقت المناسب. من وجهة نظر المزود فشل، لذا يعيد المحاولة. بدون حماية، تعالج نفس الحدث مرتين.
نموذج ذهني مفيد هو اعتبار طلب HTTP كـ "محاولة تسليم"، لا كـ "الحدث". يتم تحديد الحدث بمعرّفه. يجب أن تستند معالجتك إلى ذلك المعرف، لا على عدد مرات نداء المزود لك.
توقيع الويب هوك هو كيف يثبت المرسل أن الطلب فعلاً جاء منهم ولم يتغير في الطريق. بدون توقيع، أي شخص يخمن رابط الويب هوك يمكنه نشر أحداث مزيفة مثل "تم الدفع" أو "ترقية المستخدم". والأسوأ أن حدثًا حقيقيًا قد يُعدَّل في النقل (المبلغ، معرف العميل، نوع الحدث) ولا يزال يبدو صالحًا لتطبيقك.
النمط الأكثر شيوعًا هو HMAC مع سر مشترك. الطرفان يعلمان القيمة السرية نفسها. المرسل يأخذ حمولة الويب هوك الدقيقة (عادةً جسم الطلب الخام)، يحسب HMAC مستخدمًا ذلك السر، ويُرسل التوقيع جنبًا إلى جنب مع الحمولة. مهمتك إعادة حساب HMAC على نفس البايتات والتحقق من تطابق التواقيع.
غالبًا ما توضع بيانات التوقيع في رأس HTTP. بعض المزودين يتضمنون طابعًا زمنيًا هناك حتى تضيف حماية من إعادة التشغيل. أقل شيوعًا، يُدمج التوقيع داخل جسم JSON، وهذا أخطر لأن المحللات أو إعادة التسلسل قد تغيّر التنسيق وتكسر التحقق.
عند مقارنة التواقيع، لا تستخدم مقارنة نصية عادية. المقارنات البسيطة قد تكشف فروق زمنية تساعد المهاجم على تخمين التوقيع الصحيح عبر محاولات متعددة. استخدم دالة مقارنة ثابتة-الزمن من مكتبة اللغة أو التشفير لديك، ورفض عند أي عدم تطابق.
إذا أبلغك عميل أن "نظامك قبل حدثًا لم نرسله أبدًا" فابدأ بفحوصات التوقيع. إذا فشل التحقق، فربما لديك عدم تطابق في السر أو تحسب HMAC على البايتات الخاطئة (مثلاً JSON محلل بدل الجسم الخام). إذا نجح، يمكنك الوثوق بهوية المرسل والمضي إلى إلغاء التكرار والترتيب وإعادة المحاولات.
معالجة الويب هوك الموثوقة تبدأ بقانون ممل واحد: تحقق مما استلمته، لا مما تتمنى أن تستلمه.
التقط جسم الطلب الخام بالضبط كما وصل. لا تحلل JSON وتُعيد تسلسله قبل فحص التوقيع. الاختلافات الطفيفة (المسافات، ترتيب المفاتيح، اليونيكود) تغيّر البايتات وقد تجعل التوقيعات الصالحة تبدو غير صالحة.
ثم ابنِ السلسلة الدقيقة التي يتوقع المزود توقيعها. كثير من الأنظمة توقّع سلسلة مثل timestamp + " . " + raw_body. الطابع الزمني ليس تزيينًا؛ هو موجود حتى ترفض الطلبات القديمة.
احسب HMAC باستخدام السر المشترك وخوارزمية الهاش المطلوبة (غالبًا SHA-256). خزّن السر في مخزن آمن وتعامل معه ككلمة مرور.
أخيرًا، قارن القيمة المحسوبة برأس التوقيع باستخدام مقارنة ثابتة-الزمن. إذا لم تطابق، أعد 4xx وتوقف. لا "تقبل على أي حال".
قائمة فحص سريعة للتنفيذ:
أبلغك عميل أن "الويب هوكات توقفت عن العمل" بعد أن أضفت وسيط تحليل JSON. ترى عدم تطابق في التواقيع، خاصةً على الحمُلات الأكبر. الإصلاح عادةً هو التحقق باستخدام الجسم الخام قبل أي تحليل، وتسجيل أي خطوة فشلت (مثلاً "رأس التوقيع مفقود" مقابل "الطابع الزمني خارج النافذة"). تلك التفاصيل غالبًا ما تقلل وقت التصحيح من ساعات إلى دقائق.
المزوّدون يعيدون المحاولة لأن التسليم غير مضمون. قد يكون خادمك متعطلًا لدقيقة، قد تفقد نقطة تمرير شبكة الطلب، أو يتجاوز معالجك المهلة. يفترض المزود "ربما نجح" ويرسل نفس الحدث مرة أخرى.
مفتاح عدم التكرار هو رقم الإيصال الذي تستخدمه للتعرف على حدث قد عالجته بالفعل. ليس ميزة أمنية، وليس بديلاً عن التحقق من التوقيع. كما أنه لن يحل حالات السباق ما لم تخزنه وتتحقق منه بأمان تحت التزامن.
اختيار المفتاح يعتمد على ما يزودك به المزود. فضّل قيمة تبقى ثابتة عبر المحاولات:
عند استلام ويب هوك، اكتب المفتاح في التخزين أولًا باستخدام قاعدة تفرد حتى يفوز طلب واحد فقط. ثم عالج الحدث. إذا رأيت نفس المفتاح مرة أخرى، أعد نجاحًا دون تنفيذ العمل مرتين.
احفظ "الإيصال" بشكل صغير لكن مفيد: المفتاح، حالة المعالجة (مستلم/معالج/فشل)، طوابع زمنية (أول ظهور/آخر ظهور)، وملخص مختصر (نوع الحدث ومعرّف الكائن المرتبط). تحتفظ فرق كثيرة بالمفاتيح لمدة 7 إلى 30 يومًا حتى تغطي معظم محاولات الإعادة وتقارير العملاء المتأخرة.
حماية إعادة التشغيل توقف مشكلة بسيطة لكنها قبيحة: شخص يلتقط طلب ويب هوك حقيقي (بتوقيع صالح) ويعيد إرساله لاحقًا. إذا عاملت كل تسليم كجديد، يمكن لذلك التكرار أن يثير ردودًا مالية مكررة، دعوات مستخدم مكررة، أو تغييرات حالة مكررة.
النهج الشائع هو توقيع الحمولة وأيضًا الطابع الزمني. يتضمن ويب هوك رؤوسًا مثل X-Signature و X-Timestamp. عند الاستلام، تحقق من التوقيع وتأكد أن الطابع الزمني حديث ضمن نافذة قصيرة.
انحراف الساعة هو ما يسبب عادة الرفض الخاطئ. خوادمك وخوادم المرسل قد تختلف بدقيقة أو دقيقتين، والشبكات قد تؤخر التسليم. احتفظ بهوامش وسجّل سبب الرفض.
قواعد عملية تعمل جيدًا:
abs(now - timestamp) <= window (على سبيل المثال، 5 دقائق مع مهلة صغيرة إضافية).إذا كانت الطوابع مفقودة، لا يمكنك إجراء حماية حقيقية من إعادة التشغيل بناءً على الوقت وحده. في هذه الحالة، اعتمد أكثر على عدم التكرار (خزن ورفض معرّفات الأحداث المكررة) وفكّر في طلب الطوابع في نسخة الويب هوك التالية.
ميزة مهمة: عند تدوير الأسرار، احتفظ بعدة أسرار نشطة لفترة تداخل قصيرة. تحقّق أولًا بالسر الأحدث، ثم ارجع إلى الأقدم. هذا يتجنّب تعطيل العملاء أثناء النشر. إذا كانت فرقكم تنشر نقاط نهاية بسرعة (مثلاً توليد كود باستخدام Koder.ai واستخدام لقطات واسترجاع أثناء النشر)، فإن نافذة التداخل تلك تساعد لأن النسخ الأقدم قد تبقى حيّة لفترة وجيزة.
إعادة المحاولات أمور طبيعية. افترض أن كل تسليم قد يكون مكررًا، مؤجلًا، أو خارج الترتيب. يجب أن يتصرّف معالجك بنفس الطريقة سواء رأى الحدث مرة واحدة أو خمس مرات.
اختصر مسار الطلب. قم فقط بما يلزم لقبول الحدث، ثم انقل العمل الأثقل إلى وظيفة خلفية.
نمط بسيط يصمد في الإنتاج:
أعد 2xx فقط بعد أن تتحقق من التوقيع وتسجّل الحدث (أو تضعه في الطابور). إذا رددت 200 قبل حفظ أي شيء، قد تفقد أحداثًا إذا حدث تعطل. إذا قمت بعمل ثقيل قبل الرد، تؤدي المهلات إلى إعادة المحاولات وقد تكرِّر الآثار الجانبية.
الأنظمة البطيئة أسفل السلسلة هي السبب الرئيسي لأن إعادة المحاولات تصبح مؤلمة. إذا كان مزود البريد الإلكتروني، CRM، أو قاعدة البيانات بطيئة، اترك الطابور يمتص التأخير. العامل يعيد المحاولة بتراجع، ويمكنك تنبيه على الوظائف العالقة دون حجب المرسل.
تحدث أيضًا أحداث خارجة عن الترتيب. على سبيل المثال، قد يصل subscription.updated قبل subscription.created. بنِ تضامناً بفحص الحالة الحالية قبل تطبيق التغييرات، السماح بالـ upserts، ومعاملة "لم يُعثر" كسبب لإعادة المحاولة لاحقًا (عندما يكون ذلك منطقيًا) بدلًا من فشل دائم.
الكثير من مشكلات الويب هوك "العشوائية" هي من صنع البشر. تبدو كشبكات متقلبة، لكنها تتكرر بأنماط، عادة بعد نشر، تدوير أسرار، أو تغيير صغير في التحليل.
أشهر خطأ في التوقيع هو حساب HMAC على البايتات الخاطئة. إذا حللت JSON أولًا، قد تعيد الخادم إعادة تنسيقه (المسافات، ترتيب المفاتيح، تنسيق الأرقام). ثم تتحقق من التوقيع على جسم مختلف عما وقّعه المرسل، ويُعتَبر التحقق فاشل حتى لو كانت الحمولة أصلية. تحقق دائمًا من بايتات جسم الطلب الخام كما استقبلتها بالضبط.
المصدر الكبير التالي للارتباك هو الأسرار. الفرق تختبر في البيئة التجريبية لكن تتحقق بالسر الإنتاجي عن طريق الخطأ، أو تحتفظ بسر قديم بعد التدوير. عندما يبلغك عميل أن الفشل "فقط في بيئة واحدة"، افترض أولًا سر خاطئ أو إعداد خاطئ.
بعض الأخطاء التي تؤدي إلى تحقيقات طويلة:
مثال: عميل يقول "order.paid لم يصل أبدًا". ترى فشلًا في التوقيع بدأ بعد إعادة هيكلة غيرت وسيط تحليل الطلب. الوسيط قرأ وأعاد ترميز JSON، لذا تحقق التوقيع الآن من جسم معدل. الإصلاح بسيط، لكن فقط إن نظرت للجزئية الصحيحة.
عندما يقول عميل "الويب هوك لم يُطلِق"، اعتبرها مشكلة تتبع أثر، لا مشكلة تخمين. ركّز على محاولة تسليم واحدة من المزود وتتبّعها عبر النظام.
ابدأ بالحصول على معرّف تسليم المزود، معرف الطلب، أو معرّف الحدث للمحاولة الفاشلة. بقيمة واحدة يمكنك العثور بسرعة على إدخال السجل المطابق.
من هناك، افحص ثلاثة أشياء بالترتيب:
ثم تأكد مما رددته للمزوّد. 200 بطئ يمكن أن يكون سيئًا مثل 500 إذا أن المزوِّد انتهت مهلته وأعاد المحاولة. انظر إلى رمز الحالة، زمن الاستجابة، وما إذا كان معالجك أقر قبل تنفيذ العمل الثقيل.
إذا احتجت لإعادة التشغيل، افعل ذلك بأمان: خزّن عينة خام من الطلب مُشَفَّرة (رؤوس رئيسية وجسم خام مختصر) وأعد تشغيلها في بيئة اختبار باستخدام نفس السر وكود التحقق.
عندما يبدأ تكامل ويب هوك بالفشل "عشوائيًا"، السرعة أهم من الكمال. هذا الدليل يلتقط الأسباب الشائعة.
استخرج مثالًا واحدًا ملموسًا أولًا: اسم المزود، نوع الحدث، الطابع الزمني التقريبي (مع المنطقة الزمنية)، وأي معرّف حدث يمكن للعميل رؤيته.
ثم تحقّق:
إذا قال المزود "أعدّنا المحاولة 20 مرة"، افحص الأنماط الشائعة أولًا: سر خاطئ (فشل التوقيع)، انحراف الساعة (نافذة إعادة التشغيل)، حدود حجم الحمولة (413)، مهلات (لا رد)، وانفجارات 5xx من التبعيات.
يراسلك عميل: "لقد فقدنا حدث invoice.paid بالأمس. نظامنا لم يحدث الحالة." إليك طريقة سريعة لتتبعه.
أولًا، أكد ما إذا حاول المزود التسليم. استخرج معرّف الحدث، الطابع الزمني، عنوان الوجهة، ورمز الاستجابة الذي أعادته نقطة النهاية الخاصة بك. إذا كانت هناك محاولات إعادة، لاحظ سبب الفشل الأول وما إذا نجح إعادة المحاولة لاحقًا.
بعد ذلك، تحقّق مما رآه الكود لديك عند الحافة: أكد سر التوقيع المكوّن لتلك النقطة، أعد حساب التحقق باستخدام جسم الطلب الخام، وتحقق من الطابع الزمني مقارنةً بالنافذة المسموح بها.
كن حذرًا مع نوافذ إعادة التشغيل أثناء إعادة المحاولات. إذا كانت نافذتك 5 دقائق وأعاد المزود المحاولة بعد 30 دقيقة، فقد ترفض إعادة المحاولة الشرعية. إن كانت هذه سياساتك فتأكد من توثيقها. إن لم تكن كذلك، وسّع النافذة أو غيّر المنطق بحيث يظل عدم التكرار هو الدفاع الأساسي ضد التكرارات.
إذا بدا التوقيع والطابع الزمني جيدين، اتبع معرّف الحدث عبر نظامك وأجب: هل عالجته، هل أزيل تكراره، أم هل سقط؟
النتائج الشائعة:
عند الرد على العميل، كن محددًا ومباشرًا: "استلمنا محاولتي تسليم في 10:03 و10:33 UTC. الأولى انتهت مهلة بعد 10s؛ إعادة المحاولة رُفضت لأن الطابع الزمني كان خارج نافذتنا البالغة 5 دقائق. وسّعنا النافذة وأضفنا اعترافًا أسرع. الرجاء إعادة إرسال الحدث ID X إن لزم الأمر."
أسرع طريقة لإيقاف الحرائق حول الويب هوك هي جعل كل تكامل يتبع نفس الدليل. دوّن العقد الذي تتفق عليه أنت والمرسل: الرؤوس المطلوبة، طريقة التوقيع بالضبط، أي طابع زمني يُستخدم، وأي معرفات تعتبرها فريدة.
ثم وِحّد ما تسجّله عن كل محاولة تسليم. سجل إيصال صغير عادةً يكفي: received_at, event_id, delivery_id, signature_valid, idempotency_result (new/duplicate), handler_version، و response status.
سير عمل يبقى مفيدًا أثناء النمو:
إذا بنيت تطبيقات على Koder.ai (koder.ai)، فإن Planning Mode طريقة جيدة لتعريف عقد الويب هوك أولًا (الرؤوس، التوقيع، المعرفات، سلوك إعادة المحاولة) ثم توليد نقطة نهاية متناسقة وسجل إيصالات عبر المشاريع. هذا الاتساق هو ما يجعل التصحيح سريعًا بدلاً من أن يكون بطوليًا.
لأن تسليم الويب هوك عادة ما يكون بمبدأ الـ at-least-once وليس بالتسليم بالضبط مرة واحدة. يقوم المُرسِل بإعادة المحاولة عند المهلات، استجابات 5xx، وأحيانًا عندما لا يرى 2xx في الوقت المناسب، لذا قد تحصل على تكرارات، تأخيرات، وتسليمات خارجة عن الترتيب حتى لو بدا كل شيء "يعمل".
اتبع القاعدة التالية: تحقق من التوقيع أولاً، ثم سجّل/أزل التكرار عن الحدث، ثم أعِد 2xx، ثم نفّذ العمل الثقيل بشكل غير متزامن.
إذا نفذت عملًا ثقيلًا قبل الرد، ستواجه مهلات وتولّد إعادة محاولات؛ إذا رددت قبل تخزين أي شيء، فقد تفقد أحداثًا عند حدوث عطل.
استخدم بايتات جسم الطلب الخام تمامًا كما وصلت. لا تقم بتحليل JSON وإعادة تسلسله قبل التحقق — المسافات البيضاء، ترتيب المفاتيح، وتنسيق الأرقام يمكن أن تكسر التواقيع.
تأكد أيضًا من أنك تعيد إنشاء سلسلة التوقيع التي يتوقعها المُزوّد بدقة (غالبًا timestamp + " . " + raw_body).
أعد 4xx (شائع 400 أو 401) ولا تقم بمعالجة الحمولة.
سجّل سببًا مختصرًا (رأس التوقيع مفقود، عدم تطابق، نافذة زمنية سيئة) لكن لا تسجّل الأسرار أو الحمولة الحساسة كاملة.
مفتاح عدم التكرار هو معرّف ثابت وفريد تخزّنه حتى لا تُطبق إعادة المحاولة آثارًا جانبية مرتين.
أفضل الخيارات:
طبّق قيدًا فريدًا حتى يفوز طلب واحد فقط تحت التزامن.
اكتب مفتاح عدم التكرار قبل القيام بأي آثار جانبية، مع قاعدة تفرد. ثم إما:
إذا فشل الإدراج لأن المفتاح موجود بالفعل، أعد 2xx وتخطّ العمل التجاري.
وقّع الحمولة وأيضًا الطابع الزمني. تتضمّن رؤوس الويب هوك مثل X-Signature وX-Timestamp. عند الاستلام تحقق من التوقيع وتأكد أيضًا أن الطابع الزمني داخل نافذة قصيرة.
لتجنب رفض المحاولات المشروعة:
لا تفترض أن ترتيب التسليم يطابق ترتيب الأحداث. اجعل المعالجات متسامحة:
سجّل معرّف الحدث والنوع حتى تتمكن من استنتاج ما حدث حتى لو كان الترتيب غريبًا.
سجّل إيصالًا صغيرًا لكل محاولة تسليم حتى تتمكن من تتبّع حدث واحد عبر النظام:
اجعل السجلات قابلة للبحث بواسطة معرّف الحدث حتى يتمكن الدعم من الإجابة على تقارير العملاء بسرعة.
ابدأ بطلب معرّف واحد ملموس: معرّف الحدث أو معرّف التسليم، مع وقت تقريبي.
ثم افحص بهذا الترتيب:
إذا بنيت نقاط نهاية باستخدام Koder.ai، اجعل نمط المعالج موحّدًا (تحقق → سجل/أزل تكرار → طابور → رد). الاتساق يجعل هذه الفحوصات سريعة عند وقوع الحوادث.
اسحب مثالًا واحدًا ملموسًا أولاً: اسم المزود، نوع الحدث، الطابع الزمني التقريبي (مع المنطقة الزمنية)، وأي معرّف حدث يمكن للعميل رؤيته.
ثم تحقّق من:
إذا قال المزود "أعدّنا المحاولة 20 مرة"، افحص الأنماط الشائعة: سر خاطئ (فشل التوقيع)، انحراف الساعة (نافذة إعادة التشغيل)، حدود حجم الحمولة (413)، مهلات (لا رد)، وارتفاع استجابات 5xx من تبعياتك.
إذا كانت الطوابع مفقودة، لا يمكنك حماية إعادة التشغيل بالوقت وحده — اعتمد أكثر على عدم التكرار واطلب الطوابع في النسخة التالية.