شرح Webhooks: الحمولات الموقعة وإعادة المحاولة
لقد قمت بدمج بوابة دفع أو خدمة CI/CD، والآن تحتاج إلى استقبال الأحداث في الوقت الفعلي. webhooks هي الحل القياسي، لكنها تأتي مع تحديين كبيرين: التحقق من أن الطلب جاء بالفعل من المرسل المتوقع، والتعامل مع حالات الفشل عندما يكون نقطة النهاية الخاصة بك معطلة أو بطيئة. تشرح هذه المقالة كيفية تنفيذ حمولات webhook الموقعة ومنطق إعادة المحاولة القوي، مع أمثلة عملية على الكود.
ما هي Webhooks؟
webhook هو استدعاء HTTP معرف من قبل المستخدم. بدلاً من أن يقوم تطبيقك بالاستقصاء المتكرر لواجهة برمجة التطبيقات (API) للحصول على التحديثات، يرسل المزود طلب HTTP POST إلى عنوان URL تحدده كلما حدث حدث ما. هذا يجعل التكاملات فعالة وشبه فورية.
ومع ذلك، تعتمد webhooks على الدفع، مما يعني أن نقطة النهاية الخاصة بك يجب أن تكون متاحة للجمهور، وتتعامل مع الحمل غير المتوقع، وتتعامل مع الطلبات المكررة أو الضارة. هنا يأتي دور التوقيع وإعادة المحاولة.
لماذا توقيع حمولات Webhook؟
بدون التحقق، يمكن لأي شخص يعرف عنوان URL الخاص بـ webhook أن يرسل أحداثًا مزيفة. يتيح لك توقيع الحمولات باستخدام سر مشترك مصادقة المرسل وضمان عدم التلاعب بالحمولة.
النهج الأكثر شيوعًا هو HMAC (رمز مصادقة الرسالة القائم على التجزئة) باستخدام SHA-256. يقوم المزود بحساب توقيع على جسم الطلب الخام باستخدام مفتاح سري، ويضمّنه في رأس (على سبيل المثال، X-Signature). يقوم الخادم الخاص بك بإعادة حساب التوقيع ومقارنتهما.
تنفيذ التحقق من HMAC
إليك مثال بسيط في Node.js باستخدام Express:
const crypto = require('crypto');
const express = require('express');
const app = express();
// Use raw body for signature verification
app.use(express.raw({ type: 'application/json' }));
app.post('/webhook', (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);
console.log('Received event:', event.type);
res.status(200).send('OK');
});
app.listen(3000, () => console.log('Webhook server running'));
النقاط الرئيسية:
- استخدم الجسم الخام: قم بتحليل الجسم كبايتات خام، وليس JSON، لأن أي تحويل يغير التوقيع.
- مقارنة آمنة ضد هجمات التوقيت: استخدم
crypto.timingSafeEqualلمنع هجمات التوقيت. - إدارة الأسرار: قم بتخزين الأسرار بشكل آمن (متغيرات البيئة، مديري الأسرار).
التعامل مع إعادة المحاولة: جعل Webhooks موثوقة
تفشل الشبكات، وتتعطل الخوادم، وتسبب عمليات النشر توقفًا. يجب على نظام webhook الموثوق إعادة محاولة التسليمات الفاشلة. لكن إعادة المحاولة الساذجة يمكن أن تسبب معالجة مكررة وتحميلاً زائدًا على نظامك.
استراتيجيات إعادة المحاولة
أنماط إعادة المحاولة الشائعة تشمل:
- التراجع الأسي (Exponential backoff): انتظر فترة أطول بين كل محاولة (على سبيل المثال، 1 ثانية، 2 ثانية، 4 ثوانٍ، 8 ثوانٍ...).
- الحد الأقصى لإعادة المحاولة: توقف بعد N محاولات (على سبيل المثال، 5) وحدد التسليم كفاشل.
- الاهتزاز (Jitter): أضف عشوائية لتجنب مشاكل القطيع المتدافع.
تنفذ معظم المزودين شيئًا مثل: إعادة المحاولة حتى 5 مرات مع التراجع الأسي على مدى 24 ساعة.
Idempotency: مفتاح إعادة المحاولة الآمنة
نظرًا لأن إعادة المحاولة يمكن أن تسلم نفس الحدث عدة مرات، يجب أن تكون نقطة النهاية الخاصة بك idempotent. قم بتضمين معرف حدث فريد في الحمولة وقم بتخزين المعرفات المعالجة لتخطي التكرارات.
مثال باستخدام مجموعة في الذاكرة (استخدم قاعدة بيانات في الإنتاج):
const processedEvents = new Set();
app.post('/webhook', (req, res) => {
// ... signature verification ...
const event = JSON.parse(payload);
if (processedEvents.has(event.id)) {
return res.status(200).send('Already processed');
}
// Process event
processedEvents.add(event.id);
res.status(200).send('OK');
});
للإنتاج، استخدم قاعدة بيانات مع قيد فريد على معرف الحدث.
أفضل الممارسات لمستهلكي Webhook
- الرد بسرعة: أقر بالاستلام بحالة 2xx قبل القيام بالمعالجة الثقيلة. ضع الحدث في قائمة الانتظار للمعالجة غير المتزامنة.
- التحقق من الطابع الزمني: إذا قام المزود بتضمين طابع زمني، فارفض الطلبات الأقدم من بضع دقائق لمنع هجمات إعادة التشغيل.
- استخدم HTTPS: قم دائمًا بتعريض نقطة نهاية webhook الخاصة بك عبر HTTPS لحماية الحمولات أثناء النقل.
- سجل كل شيء: سجل الطلبات الواردة والتوقيعات ونتائج المعالجة لتصحيح الأخطاء.
- مراقبة حالات الفشل: قم بإعداد تنبيهات لفشل التسليم المتكرر.
مقارنة سياسات إعادة المحاولة
| السياسة | الإيجابيات | السلبيات |
|---|---|---|
| فاصل ثابت | سهل التنفيذ | يمكن أن يفرط في تحميل نظام يتعافى |
| التراجع الأسي | يقلل الحمل أثناء الانقطاعات | يؤخر التسليم إذا كان الفشل عابرًا |
| التراجع الأسي مع الاهتزاز | يمنع إعادة المحاولة المتزامنة | أكثر تعقيدًا قليلاً |
الأسئلة الشائعة
كيف أتحقق من توقيع webhook؟
احسب HMAC لجسم الطلب الخام باستخدام السر المشترك وقارنه برأس التوقيع باستخدام دالة آمنة ضد هجمات التوقيت. لا تقارن أبدًا باستخدام المساواة البسيطة.
ماذا أفعل إذا كانت نقطة نهاية webhook الخاصة بي معطلة؟
يجب على المزود إعادة المحاولة مع التراجع الأسي. تأكد من أن نقطة النهاية الخاصة بك تعيد 2xx بسرعة وتعالج الأحداث بشكل غير متزامن لتقليل تأثير التوقف.
هل يمكنني استخدام webhooks بدون توقيع؟
لا ينصح بذلك. بدون توقيعات، لا يمكنك التحقق من المرسل، مما يجعل نقطة النهاية الخاصة بك عرضة للأحداث المزيفة. استخدم دائمًا التوقيعات عند توفرها.
عند اختبار حمولات webhook، قد تحتاج إلى فحص بيانات JSON. استخدم JSON Formatter لتنسيق الحمولات والتحقق منها بسرعة.