شرح Webhooks: الحمولات الموقعة وإعادة المحاولة
لقد قمت للتو بدمج بوابة دفع. تقول الوثائق: "سنرسل طلب POST إلى نقطة النهاية الخاصة بك عند نجاح الدفع." سهل، أليس كذلك؟ تبني مسارًا بسيطًا يقبل JSON، ويحدث قاعدة البيانات، ويعيد 200. ثم يحدث ذلك: ينجح دفع العميل، لكن خادمك متوقف مؤقتًا. لا يصل webhook أبدًا. أو الأسوأ من ذلك، يصل مرتين، وتضاعف رصيد الحساب. تبدو webhooks بسيطة حتى تفشل.
تشرح هذه المقالة كيفية عمل webhooks حقًا، ولماذا تعتبر الحمولات الموقعة ضرورية للأمان، وكيفية بناء منطق إعادة المحاولة الذي يحافظ على موثوقية التكامل الخاص بك — حتى عند حدوث أخطاء.
ما هو Webhook؟
webhook هو استدعاء HTTP: بدلاً من أن يستفسر تطبيقك واجهة برمجة تطبيقات (API) عن التغييرات، يرسل المزود طلب POST إلى عنوان URL تحدده عند حدوث حدث. إنه دفع وليس سحب.
تشمل الأمثلة الشائعة تأكيدات الدفع (Stripe، PayPal)، وإشعارات البناء CI/CD (GitHub، GitLab)، ومنصات المراسلة (Slack، Discord). النمط بسيط: تسجل عنوان URL، ويرسل المزود حمولات JSON إلى ذلك العنوان، ويعالجها خادمك.
لكن هذه البساطة تخفي تحديين حاسمين: الأصالة (كيف تعرف أن الطلب جاء بالفعل من المزود؟) والموثوقية (ماذا يحدث إذا كانت نقطة النهاية الخاصة بك متوقفة أو بطيئة؟).
لماذا تهم الحمولات الموقعة
نقطة نهاية webhook الخاصة بك هي عنوان URL عام. يمكن لأي شخص إرسال طلب POST إليه. بدون التحقق، يمكن للمهاجم تزوير حدث نجاح دفع، أو تشغيل شحنة، أو إفساد بياناتك.
تحل الحمولات الموقعة هذه المشكلة عن طريق تضمين توقيع تشفيري في رؤوس الطلب. يحسب المزود HMAC (رمز مصادقة الرسالة المستند إلى التجزئة) باستخدام سر مشترك ونص الطلب، ثم يرسله مع الحمولة. يعيد خادمك حساب HMAC ويقارنه. إذا تطابقا، يكون الطلب أصليًا.
كيف يعمل توقيع HMAC
- يتشارك المزود وأنت مفتاحًا سريًا (يُقدم أثناء الإعداد).
- لكل webhook، يدمج المزود الطابع الزمني ونص الطلب الخام، ثم يحسب تجزئة HMAC-SHA256 باستخدام السر.
- يُرسل التوقيع في رأس، غالبًا شيء مثل
X-SignatureأوStripe-Signature. - يعيد خادمك حساب HMAC باستخدام نفس السر ويقارنه بالتوقيع المستلم.
- إذا تطابقا، تعالج الحدث؛ وإلا، ترفضه بـ 401.
استخدم دائمًا نص الطلب الخام لحساب HMAC. إذا قام إطار العمل الخاص بك بتحليل JSON قبل التحقق، فقد يتم تغيير النص (مسافات، ترتيب المفاتيح)، مما يؤدي إلى فشل التحقق.
مثال: التحقق من توقيع webhook في Node.js
const crypto = require('crypto');
const express = require('express');
const app = express();
// Use raw body for signature verification
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-signature'];
const secret = process.env.WEBHOOK_SECRET;
const payload = req.body; // Buffer
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
return res.status(401).send('Invalid signature');
}
// Process the event
const event = JSON.parse(payload.toString());
// ... handle event
res.status(200).send('OK');
});
لاحظ استخدام timingSafeEqual لمنع هجمات التوقيت. لا تستخدم أبدًا مقارنة === البسيطة للتوقيعات.
تصميم منطق إعادة المحاولة
حتى مع التوقيعات، يمكن أن تفشل webhooks. قد يكون خادمك متوقفًا، أو قد تسقط الشبكة الطلب، أو قد تنتهي مهلة المعالجة. عادةً ما يعيد المزودون محاولة التسليمات الفاشلة، لكن يجب عليك أيضًا تصميم نظامك للتعامل مع إعادة المحاولة بسلاسة.
ما الذي يعتبر فشلًا؟
يعتبر معظم المزودين أي استجابة بخلاف 2xx فشلًا. يشمل ذلك إعادة التوجيه 3xx، وأخطاء العميل 4xx، وأخطاء الخادم 5xx. بعضهم يفرض أيضًا مهلة (مثل 5 ثوانٍ). إذا لم تستجب بسرعة، يتم وضع علامة فشل على التسليم وإعادة المحاولة.
التراجع الأسي مع التذبذب
عادةً ما يعيد المزودون المحاولة باستخدام التراجع الأسي: أول إعادة محاولة بعد دقيقة، ثم 5 دقائق، 30 دقيقة، إلخ. إضافة التذبذب (تغيير عشوائي) يمنع القطعان المدوية إذا فشلت العديد من webhooks في وقت واحد.
إذا كنت تبني مرسل webhook، فنفذ التراجع كما يلي:
- إعادة المحاولة 1: بعد 10 ثوانٍ
- إعادة المحاولة 2: بعد دقيقة واحدة
- إعادة المحاولة 3: بعد 5 دقائق
- إعادة المحاولة 4: بعد 30 دقيقة
- إعادة المحاولة 5: بعد ساعتين
بعد الحد الأقصى لعدد المحاولات، انقل الحدث إلى قائمة الانتظار الميتة للفحص اليدوي.
Idempotency: التعامل مع التسليمات المكررة
تعني إعادة المحاولة أن نقطة النهاية الخاصة بك قد تتلقى نفس الحدث عدة مرات. يجب أن تكون معالجتك idempotent: معالجة نفس الحدث مرتين يجب أن يكون لها نفس تأثير معالجته مرة واحدة.
الاستراتيجيات الشائعة:
- إزالة تكرار معرف الحدث: قم بتخزين معرفات الأحداث المعالجة في قاعدة بيانات أو ذاكرة تخزين مؤقت. قبل المعالجة، تحقق مما إذا كان المعرف قد شوهد.
- Upsert بدلاً من insert: استخدم عمليات قاعدة البيانات التي تتعامل بشكل طبيعي مع التكرارات (مثل
INSERT ... ON CONFLICT DO NOTHING). - مفاتيح Idempotency: لعمليات مثل شحن بطاقة، مرر مفتاحًا فريدًا حتى يتجاهل مزود الدفع الطلبات المكررة.
أفضل الممارسات لمستهلكي Webhook
| الممارسة | لماذا تهم |
|---|---|
| التحقق من التوقيعات | يمنع الطلبات المزورة |
| الاستجابة بسرعة (خلال 2 ثانية) | يتجنب المهلات وإعادة المحاولة غير الضرورية |
| المعالجة بشكل غير متزامن | يفصل الاستلام عن المعالجة الثقيلة |
| استخدام معالجات idempotent | آمن ضد التسليمات المكررة |
| تسجيل جميع الأحداث | يساعد في التصحيح والتدقيق |
| مراقبة الأخطاء | يكتشف المشكلات قبل تفاقمها |
فكر أيضًا في التحقق من الطابع الزمني في التوقيع لمنع هجمات إعادة التشغيل. يتضمن العديد من المزودين طابعًا زمنيًا في الحمولة الموقعة؛ ارفض الأحداث الأقدم من بضع دقائق.
أفضل الممارسات لمنتجي Webhook
إذا كنت تبني خدمة ترسل webhooks، اتبع هذه الإرشادات:
- وقع كل حمولة باستخدام HMAC-SHA256.
- قم بتضمين معرف حدث فريد وطابع زمني.
- أعد المحاولة باستخدام التراجع الأسي والتذبذب.
- وفر طريقة للمستهلكين لعرض سجلات التسليم وإعادة تشغيل الأحداث يدويًا.
- وثق عملية التحقق من التوقيع بوضوح.
الأسئلة الشائعة
ما هي حمولة webhook الموقعة؟
تتضمن الحمولة الموقعة توقيعًا تشفيريًا (عادة HMAC) محسوبًا من نص الطلب وسر مشترك. يتحقق المستقبل من التوقيع لضمان أن الطلب أصلي وغير معدل.
كم مرة يجب إعادة محاولة webhook؟
لا يوجد رقم عالمي، لكن معظم المزودين يعيدون المحاولة 3-5 مرات على مدى عدة ساعات. بعد ذلك، عادةً ما يتم وضع علامة فشل على الحدث وقد يكون متاحًا لإعادة التشغيل اليدوي. يجب أن يتعامل نظامك مع التكرارات بشكل idempotent بغض النظر عن عدد مرات إعادة المحاولة.
هل يمكنني استخدام webhooks بدون HTTPS؟
من الناحية الفنية نعم، لكن يجب عليك دائمًا استخدام HTTPS. غالبًا ما تحتوي webhooks على بيانات حساسة، وبدون TLS، يمكن للمهاجم اعتراض أو التلاعب بالحمولة. يضمن HTTPS أيضًا أنك ترسل إلى الخادم المقصود.
تصحيح أخطاء Webhooks باستخدام سجلات Nginx
عندما تفشل webhooks، فإن أول مكان تبحث فيه هو سجلات خادم الويب الخاص بك. إذا كنت تستخدم Nginx، فإن سجلات الوصول والأخطاء تسجل كل طلب، بما في ذلك رموز الحالة وأوقات الاستجابة. يمكنك بسرعة اكتشاف الأنماط: 401s المتكررة (فشل التحقق من التوقيع)، 500s (أخطاء التطبيق)، أو المهلات.
لتحليل أسرع، جرب محلل سجلات Nginx لتحليل وتلخيص حركة webhook، والتصفية حسب رمز الحالة، وتحديد نقاط النهاية الإشكالية.