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

Backend2026-10-18TryQuickToolBox

لقد أنشأت واجهة API وتحتاج الآن إلى إخطار الخدمات الأخرى عند حدوث أحداث. قمت بإعداد نقطة نهاية webhook، لكنك سرعان ما تكتشف أن أي شخص يمكنه إرسال حمولات مزيفة، وعندما يكون خادمك متوقفًا، تُفقد الأحداث إلى الأبد. هذا مأزق شائع: webhooks بسيطة في مفهومها لكنها تتطلب تصميمًا دقيقًا للأمان والموثوقية.

في هذا الدليل، سنستعرض أساسيات webhooks: كيفية توقيع الحمولات لمنع التلاعب، وكيفية التحقق من التوقيعات على الجانب المستقبِل، وكيفية تنفيذ منطق إعادة المحاولة مع التراجع الأُسّي للتعامل مع حالات الفشل. سنغطي أيضًا idempotency لتجنب المعالجة المكررة. بحلول النهاية، سيكون لديك نظام webhook قوي يمكنك الوثوق به.

ما هي Webhooks؟

Webhooks هي عمليات استدعاء HTTP محددة من قبل المستخدم. عند حدوث حدث في نظام المصدر (مثلًا، معالجة دفعة مالية)، يُرسل طلب HTTP POST إلى عنوان URL تحدده، مع حمولة تحتوي على تفاصيل الحدث. يتيح ذلك تواصلًا فوريًا بين الخدمات دون الحاجة إلى الاستقصاء (polling).

ومع ذلك، تفرض webhooks تحديين:

تأمين Webhooks بالحمولات الموقعة

لمنع الطلبات المزيفة، يجب على المُرسِل توقيع الحمولة باستخدام سر مشترك. يمكن للمستقبِل بعد ذلك التحقق من التوقيع لمصادقة المُرسِل. الطريقة الأكثر شيوعًا هي HMAC (رمز مصادقة الرسائل القائم على التجزئة) مع SHA-256.

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

  1. يتشارك المُرسِل والمستقبِل مفتاحًا سريًا (يُفضّل أن يكون سلسلة طويلة وعشوائية).
  2. لكل webhook، يحسب المُرسِل HMAC لجسم الطلب باستخدام السر ويضمّنه في ترويسة (مثل X-Signature).
  3. يحسب المستقبِل نفس HMAC على الجسم المستلَم ويقارنه بقيمة الترويسة. إذا تطابقا، فإن الطلب أصلي.

من الضروري توقيع جسم الطلب الخام، وليس نسخة مُحلّلة، لتجنب التناقضات الناتجة عن اختلافات تسلسل JSON.

مثال: توقيع حمولة في Node.js

const crypto = require('crypto');

function signPayload(payload, secret) {
  return crypto.createHmac('sha256', secret).update(payload).digest('hex');
}

// On the sender side:
const payload = JSON.stringify({ event: 'payment.success', amount: 100 });
const signature = signPayload(payload, 'your-secret-key');
// Include signature in header: X-Signature: signature

التحقق من التوقيع على المستقبِل

على الجانب المستقبِل، يجب عليك التحقق من التوقيع قبل معالجة الحمولة. استخدم مقارنة بزمن ثابت لمنع هجمات التوقيت.

const crypto = require('crypto');

function verifySignature(payload, signature, secret) {
  const expected = crypto.createHmac('sha256', secret).update(payload).digest('hex');
  return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}

// In your webhook endpoint:
app.post('/webhook', (req, res) => {
  const signature = req.headers['x-signature'];
  const payload = req.rawBody; // raw body as string
  if (!verifySignature(payload, signature, 'your-secret-key')) {
    return res.status(401).send('Invalid signature');
  }
  // Process the event
  res.status(200).send('OK');
});

ملاحظة: استخدم دائمًا HTTPS لمنع هجمات الوسيط (man-in-the-middle). كذلك، فكّر في تضمين طابع زمني في الحمولة الموقعة لمنع هجمات إعادة الإرسال (replay attacks).

تنفيذ إعادة المحاولة مع التراجع الأُسّي

حتى مع مستقبِل موثوق، قد تؤدي مشكلات الشبكة أو الانقطاعات المؤقتة إلى فشل تسليم webhook. يجب على المُرسِل القوي إعادة محاولة عمليات التسليم الفاشلة. ومع ذلك، فإن إعادة المحاولة الساذجة قد تُثقل المستقبِل أو تسبب معالجة مكررة. الحل: التراجع الأُسّي مع التشتيت (jitter) وحد أقصى لإعادة المحاولة.

استراتيجية إعادة المحاولة

مثال: منطق إعادة المحاولة في Node.js

async function deliverWebhook(url, payload, secret, maxRetries = 5) {
  const signature = signPayload(payload, secret);
  let attempt = 0;
  while (attempt < maxRetries) {
    try {
      const response = await fetch(url, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', 'X-Signature': signature },
        body: payload,
      });
      if (response.ok) return;
      throw new Error(`HTTP ${response.status}`);
    } catch (error) {
      attempt++;
      if (attempt >= maxRetries) throw error;
      const delay = Math.min(1000 * 2 ** attempt + Math.random() * 1000, 30000);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}

تعيد هذه الدالة المحاولة حتى 5 مرات مع التراجع الأُسّي والتشتيت، مع تحديد التأخير بحد أقصى 30 ثانية. اضبط المعاملات بناءً على احتياجاتك.

ضمان Idempotency

قد تسبب إعادة المحاولة عمليات تسليم مكررة، لذا يجب أن يكون معالج webhook الخاص بك متسقًا (idempotent). ضمّن معرّف حدث فريدًا في الحمولة واجعل المستقبِل يتتبع المعرّفات المعالَجة لتجنب المعالجة المزدوجة.

// On the receiver:
const processedEvents = new Set();

app.post('/webhook', (req, res) => {
  const { id } = req.body;
  if (processedEvents.has(id)) {
    return res.status(200).send('Already processed');
  }
  // Process event
  processedEvents.add(id);
  res.status(200).send('OK');
});

في بيئة الإنتاج، استخدم مخزنًا دائمًا (مثل Redis أو قاعدة بيانات) لتتبع المعرّفات المعالَجة عبر عمليات إعادة التشغيل.

مقارنة: Webhooks الموقعة مقابل غير الموقعة

الجانب غير موقعة موقعة (HMAC)
الأمان يمكن لأي شخص تزوير الطلبات فقط حامل السر يمكنه إنشاء توقيع صالح
السلامة يمكن التلاعب بالحمولة التلاعب يُبطل التوقيع
التعقيد منخفض متوسط (إدارة المفاتيح، التحقق)
حالة الاستخدام داخلية، شبكات موثوقة أي webhook عام

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

ما هو توقيع webhook؟

توقيع webhook هو تجزئة تشفيرية (عادة HMAC) للحمولة، تُنشأ باستخدام سر مشترك. يتيح للمستقبِل التحقق من أن الطلب جاء من المُرسِل المتوقع وأن الحمولة لم تُعدّل.

كم مرة يجب أن أعيد محاولة webhook فاشل؟

لا يوجد رقم ثابت، لكن الممارسة الشائعة هي 3 إلى 5 محاولات مع التراجع الأُسّي. بعد ذلك، ضع علامة على التسليم بأنه فشل ونبّه فريقك. فكّر في أهمية الحدث: قد تستدعي إشعارات الدفع محاولات أكثر من التحديثات غير الحرجة.

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

يجب ألا تستخدم أبدًا webhooks عبر HTTP العادي في بيئة الإنتاج. يشفّر HTTPS الحمولة أثناء النقل، مما يمنع التنصت وهجمات الوسيط. حتى مع التوقيعات، يكشف HTTP البيانات الحساسة.

هل أنت مستعد لاختبار حمولات webhook الخاصة بك؟ استخدم JSON Formatter لطباعة حمولات JSON بشكل منسق والتحقق من صحتها قبل إرسالها أو تصحيح أخطائها.