شرح Webhooks: الحمولات الموقعة ومنطق إعادة المحاولة

Backend2026-10-15TryQuickToolBox

لقد قمت للتو بدمج بوابة دفع. تقول الوثائق: "سنرسل webhook عند نجاح الدفع." رائع. لكنك تتساءل: كيف أعرف أن الطلب جاء منهم حقًا؟ ماذا لو كان خادمي معطلًا عند إرساله؟ ماذا لو أرسلوا نفس الحدث مرتين؟

webhooks قوية ولكنها تأتي مع مطبات. يشرح هذا الدليل الميكانيكا والأمان وأنماط الموثوقية التي تحتاجها للتعامل معها بشكل صحيح.

ما هو webhook؟

webhook هو طلب HTTP POST يرسله نظام إلى آخر عند حدوث حدث معين. بدلاً من أن يقوم تطبيقك باستقصاء API كل بضع ثوانٍ ("هل هناك طلبات جديدة؟ هل هناك طلبات جديدة؟")، يدفع المزود البيانات إليك بمجرد حدوث شيء ما.

التدفق النموذجي:

  1. تسجل عنوان URL (مثل https://yourapp.com/webhooks/payments) لدى المزود.
  2. يحدث حدث (نجاح الدفع، تسجيل مستخدم، رفع ملف).
  3. يرسل المزود طلب POST إلى عنوان URL الخاص بك مع جسم JSON يصف الحدث.
  4. يعالج خادمك الحدث ويرد بـ 2xx للإقرار بالاستلام.

إذا لم ترد بسرعة (عادة خلال 5-30 ثانية)، يعتبر المزود التسليم فاشلاً وقد يعيد المحاولة.

لماذا يجب عليك التحقق من الحمولات الموقعة

نقطة نهاية webhook الخاصة بك متاحة للعامة. أي شخص يكتشف عنوان URL يمكنه إرسال أحداث مزيفة. بدون التحقق، يمكن للمهاجم تزوير حدث "نجاح الدفع" والحصول على سلع مجانية.

الحل: الحمولات الموقعة. يحسب المزود توقيعًا تشفيريًا على جسم الطلب باستخدام سر مشترك (معروف فقط لك ولهم) ويضمنه في رأس. تعيد حساب التوقيع من جانبك وتقارن.

كيف يعمل توقيع HMAC

يستخدم معظم المزودين HMAC-SHA256. الخطوات:

  1. يقوم المزود بربط جسم الطلب الخام مع طابع زمني (لمنع هجمات إعادة الإرسال).
  2. يحسب المزود HMAC-SHA256(secret, timestamp + '.' + body).
  3. يرسل المزود التوقيع في رأس، مثل X-Signature: t=1234567890,v1=abc123....
  4. تستخرج الطابع الزمني والتوقيع، وتعيد حساب HMAC باستخدام نسختك من السر، وتقارن باستخدام دالة مقارنة ثابتة الزمن.

هذا مثال بسيط بـ Node.js:

const crypto = require('crypto');

function verifySignature(req, secret) {
  const signatureHeader = req.headers['x-signature'];
  const [tPart, vPart] = signatureHeader.split(',');
  const timestamp = tPart.split('=')[1];
  const receivedSig = vPart.split('=')[1];

  const payload = timestamp + '.' + JSON.stringify(req.body);
  const expectedSig = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');

  // مقارنة ثابتة الزمن لمنع هجمات التوقيت
  return crypto.timingSafeEqual(
    Buffer.from(receivedSig, 'hex'),
    Buffer.from(expectedSig, 'hex')
  );
}

هام: استخدم دائمًا جسم الطلب الخام للتحقق من التوقيع. إذا قام إطار العمل الخاص بك بتحليل JSON وإعادة تسلسله، فقد يختلف تسلسل البايتات، مما يؤدي إلى فشل التحقق. التقط الجسم الخام قبل التحليل.

التحقق من الطابع الزمني

تحقق من أن الطابع الزمني حديث (مثل خلال 5 دقائق). هذا يمنع هجمات إعادة الإرسال حيث يلتقط المهاجم طلبًا صالحًا ويعيد إرساله لاحقًا.

منطق إعادة المحاولة: معالجة الفشل بلطف

قد يكون خادمك معطلًا أو محملاً بشكل زائد أو يواجه انقطاعًا في الشبكة. يتعامل المزودون مع هذا بإعادة محاولة التسليمات الفاشلة. لكن يجب عليك تصميم نقطة النهاية الخاصة بك للتعامل مع إعادة المحاولات بأمان.

التراجع الأسي مع التذبذب

عادةً ما يعيد المزودون المحاولة بتأخيرات متزايدة: دقيقة واحدة، 5 دقائق، 30 دقيقة، ساعتان، إلخ. يسمى هذا التراجع الأسي. يضيف البعض تذبذبًا عشوائيًا لتجنب مشاكل القطيع المتدافع.

من جانبك، لا تتحكم في جدول إعادة المحاولة الخاص بالمزود، لكن يجب عليك:

Idempotency: مفتاح إعادة المحاولة الآمنة

تعني إعادة المحاولة أنك قد تتلقى نفس الحدث عدة مرات. يجب أن تكون معالجتك خاملة التأثير (idempotent) - معالجة نفس الحدث مرتين يجب أن يكون لها نفس تأثير معالجته مرة واحدة.

النهج الشائع: تضمين معرف حدث فريد في الحمولة. قبل المعالجة، تحقق مما إذا كنت قد تعاملت مع هذا المعرف بالفعل. إذا نعم، أرجع 200 وتخطى.

async function handleWebhook(event) {
  const existing = await db.events.findOne({ id: event.id });
  if (existing) {
    return { status: 200, body: 'Already processed' };
  }

  await db.events.insert({ id: event.id, processedAt: new Date() });
  await processEvent(event);
  return { status: 200, body: 'OK' };
}

يضمن هذا النمط أنه حتى لو أرسل المزود الحدث ثلاث مرات، فإنك تتصرف مرة واحدة فقط.

مقارنة: الاستقصاء مقابل webhooks

الجانب الاستقصاء webhooks
زمن الاستجابة يعتمد على الفاصل الزمني (مثل 30 ثانية) شبه فوري
حمل الخادم ثابت، حتى عند عدم وجود أحداث فقط عند حدوث الأحداث
التعقيد بسيط التنفيذ يتطلب نقطة نهاية، تحقق، إعادة محاولات
الموثوقية أنت تتحكم في إعادة المحاولات تعتمد على سياسة إعادة المحاولة للمزود

webhooks تفوز للتحديثات الفورية، لكن الاستقصاء يمكن أن يكون بديلاً للأحداث الحرجة إذا كنت قلقًا بشأن التسليمات المفقودة.

قائمة أفضل الممارسات

تصحيح مشكلات webhook

عندما تفشل webhooks، الخطوة الأولى هي فحص الطلب الخام. المشكلات الشائعة:

إذا كنت تقوم بتصحيح حركة مرور HTTP، يمكن لأدوات مثل Nginx Log Analyzer مساعدتك في اكتشاف الأنماط في فشل الطلبات ورموز الاستجابة.

الأسئلة الشائعة

ما هو سر webhook وكيف أخزنه؟

السر هو مفتاح مشترك يستخدم لتوقيع الحمولات. خزنه في متغيرات البيئة أو مدير أسرار - لا تخزنه أبدًا في الكود أو التحكم في الإصدارات. قم بتدويره بشكل دوري إذا كان المزود يدعم ذلك.

كم مرة سيعيد المزود محاولة webhook فاشل؟

يختلف حسب المزود. بعضهم يعيد المحاولة لمدة 24 ساعة مع التراجع الأسي، والبعض الآخر لمدة 3 أيام. تحقق من وثائق المزود للجدول الدقيق والحد الأقصى للمحاولات.

هل يمكنني استخدام webhooks مع الدوال بدون خادم؟

نعم، ولكن كن حذرًا من حدود وقت التنفيذ. أقر باستلام webhook بسرعة (أرجع 200) وانقل المعالجة الثقيلة إلى قائمة انتظار أو دالة خلفية لتجنب المهلات.

هل أنت مستعد لبناء تكاملات webhook قوية؟ ابدأ بالتحقق من التوقيعات وتنفيذ idempotency - هاتان الخطوتان ستوفران عليك ساعات من تصحيح الأخطاء وتمنعان حوادث أمنية مكلفة.