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

Backend2026-10-22TryQuickToolBox

لقد قمت ببناء نقطة نهاية webhook لاستقبال الأحداث من خدمة خارجية. كل شيء يعمل في الاختبار، ولكن في الإنتاج تلاحظ أحداثًا مكررة، وإشعارات مفقودة، ومخاوف أمنية عرضية بشأن الطلبات المزورة. هذه مزالق شائعة عند تنفيذ webhooks دون ضمانات مناسبة.

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

كيف تعمل webhooks

webhook هو نقطة نهاية HTTP POST يحددها المستخدم. عند حدوث حدث، ترسل الخدمة المصدر طلبًا إلى عنوان URL هذا مع حمولة تصف الحدث. على سبيل المثال، قد ترسل معالج الدفع webhook عند نجاح عملية دفع.

التدفق بسيط:

  1. تسجل عنوان URL لـ webhook لدى الخدمة.
  2. ترسل الخدمة طلب HTTP POST إلى عنوان URL هذا عند إطلاق حدث.
  3. تعالج نقطة النهاية الخاصة بك الحمولة وتعيد حالة 2xx لتأكيد الاستلام.

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

تأمين webhooks باستخدام الحمولات الموقعة

لضمان أن webhook نشأ من المرسل المتوقع، تقوم الخدمات بتوقيع الحمولة باستخدام سر مشترك. التوقيع عادةً ما يكون HMAC (رمز مصادقة الرسائل القائم على التجزئة) لنص الطلب، ويُرسل في رأس مثل X-Signature.

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

يحسب المرسل HMAC باستخدام مفتاح سري ونص الطلب الخام، ثم يضمّن التوقيع المشفر بنظام hex في رأس. تعيد نقطة النهاية الخاصة بك حساب 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;

  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);
  // ... handle event
  res.status(200).send('OK');
});

نقاط رئيسية:

تدابير أمنية إضافية

تنفيذ إعادة محاولة موثوقة

تسليم webhook ليس مضمونًا. تفشل الشبكات، أو قد يكون الخادم الخاص بك معطلاً، أو قد تعيد حالة غير 2xx. للتعامل مع هذا، يقوم المرسلون عادةً بإعادة المحاولة مع تأخير أسي. بصفتك متلقيًا، يجب عليك تصميم نقطة النهاية الخاصة بك لتكون خاملة (idempotent) وللاستجابة بسرعة.

على جانب المرسل (إذا كنت ترسل webhooks)

إذا كنت تبني خدمة ترسل webhooks، فنفذ إعادة المحاولة مع التأخير. استراتيجية شائعة:

مثال على منطق إعادة المحاولة في Python:

import time
import requests
from requests.exceptions import RequestException

def send_webhook(url, payload, max_retries=5):
    for attempt in range(max_retries):
        try:
            response = requests.post(url, json=payload, timeout=5)
            if 200 <= response.status_code < 300:
                return True
            elif 400 <= response.status_code < 500 and response.status_code != 429:
                # Client error, don't retry
                return False
        except RequestException:
            pass
        # Exponential backoff with jitter
        delay = (2 ** attempt) + random.uniform(0, 1)
        time.sleep(delay)
    return False

على جانب المتلقي

يجب على نقطة النهاية الخاصة بك:

مثال على الخمول باستخدام قيد فريد في قاعدة البيانات:

CREATE TABLE processed_events (
  event_id VARCHAR(255) PRIMARY KEY,
  processed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

قبل المعالجة، حاول إدراج معرف الحدث. إذا انتهك المفتاح الأساسي، فتخطَّ المعالجة.

اختبار webhooks محليًا

أثناء التطوير، تحتاج إلى عنوان URL عام لتتمكن الخدمة من الوصول إلى خادمك المحلي. أدوات مثل ngrok أو localtunnel تعرض localhost الخاص بك. استخدم دائمًا أسرار الاختبار وتحقق من التوقيعات في التطوير لاكتشاف المشكلات مبكرًا.

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

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

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

ما هي حمولة webhook الموقعة؟

تتضمن الحمولة الموقعة توقيعًا تشفيريًا (عادةً HMAC) يتم إنشاؤه باستخدام سر مشترك. يسمح للمتلقي بالتحقق من أن الطلب جاء من المرسل المتوقع ولم يتم العبث به.

كيف أتعامل مع عمليات تسليم webhook المكررة؟

اجعل نقطة النهاية الخاصة بك خاملة (idempotent) باستخدام معرف حدث فريد من الحمولة. خزن المعرفات المعالجة وتجاهل التكرارات. هذا يضمن أن إعادة المحاولة أو مشكلات الشبكة لا تسبب معالجة مزدوجة.

هل يجب أن أعيد محاولة تسليم webhook الفاشلة؟

نعم، ولكن فقط للأخطاء المؤقتة (مشكلات الشبكة، 5xx). استخدم تأخيرًا أسيًا مع تشويش وحدد حدًا أقصى لإعادة المحاولة. تجنب إعادة المحاولة عند أخطاء 4xx (باستثناء 429) لأنها تشير إلى مشكلة في الطلب نفسه.

عند تصحيح حمولات webhook، غالبًا ما تحتاج إلى فحص بيانات JSON. يمكن أن تساعدك أداة JSON Formatter في تنسيق والتحقق من حمولات JSON بسرعة.