شرح Webhooks: الحمولات الموقعة وإعادة المحاولة
لماذا تحتاج Webhooks إلى التوقيعات وإعادة المحاولة
لقد أنشأت نقطة نهاية لتلقي webhooks من مزود خدمة الدفع أو منصة SaaS. كل شيء يعمل في الاختبار، ولكن في الإنتاج تواجه مشكلتين: كيف تعرف أن الطلب جاء بالفعل من المزود، وماذا يحدث عندما يكون خادمك معطلاً أو بطيئًا؟ بدون حمولات موقعة، يمكن لأي شخص تزوير طلب. وبدون إعادة المحاولة، يمكن أن تؤدي أي عثرة في الشبكة إلى فقدان حدث إلى الأبد.
يشرح هذا الدليل كيفية تنفيذ كليهما بشكل صحيح. سنغطي توقيع HMAC، وخطوات التحقق، واستراتيجيات إعادة المحاولة، وidempotency — الأعمدة الثلاثة للتعامل الموثوق مع webhooks.
كيف تعمل توقيعات Webhook
توقيع webhook هو تجزئة تشفيرية تثبت أن الحمولة تم إنشاؤها بواسطة شخص يعرف سرًا مشتركًا. يستخدم معظم المزودين HMAC-SHA256. يقوم المرسل بحساب التوقيع على جسم الطلب الخام (أحيانًا مع طابع زمني) ويضمّنه في رأس. يقوم المستقبل بإعادة حسابه ومقارنته.
إليك تدفقًا نموذجيًا:
- يشترك المزود والمستهلك في مفتاح سري (يتم تكوينه في لوحة تحكم المزود).
- ينشئ المزود الحمولة، ويحسب
HMAC-SHA256(secret, payload)، ويرسلها في رأس مثلX-Signature. - يستقبل المستهلك الطلب، ويقرأ الجسم الخام، ويعيد حساب HMAC، ويقارن باستخدام دالة ثابتة الوقت.
- إذا تطابقت، يعالج الحدث؛ وإلا يرفض بـ 401.
هام: استخدم دائمًا جسم الطلب الخام كما تم استلامه، وليس نسخة محللة ومعاد تسلسلها. يمكن أن يغير تسلسل JSON المسافات وترتيب المفاتيح، مما يكسر التوقيع.
إضافة الطوابع الزمنية لمنع هجمات إعادة التشغيل
يمكن للمهاجم الذي يلتقط طلبًا صالحًا إعادة تشغيل الحمولة الموقعة وحدها. للتخفيف من ذلك، قم بتضمين طابع زمني في البيانات الموقعة وارفض الطلبات الأقدم من بضع دقائق. على سبيل المثال:
signed_payload = timestamp + "." + raw_body
signature = HMAC-SHA256(secret, signed_payload)
يتحقق المستهلك من أن الطابع الزمني ضمن نافذة مقبولة (مثل 5 دقائق) وأن التوقيع يتطابق.
تنفيذ التحقق من التوقيع
لنلقِ نظرة على مثال ملموس في Python باستخدام Flask. سنتحقق من توقيع مرسل في رأس X-Signature.
import hmac
import hashlib
from flask import Flask, request, abort
app = Flask(__name__)
SECRET = b'your_shared_secret'
@app.route('/webhook', methods=['POST'])
def webhook():
signature = request.headers.get('X-Signature')
if not signature:
abort(401)
raw_body = request.get_data()
expected = hmac.new(SECRET, raw_body, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, signature):
abort(401)
# Process the event
event = request.json
# ... handle event ...
return '', 200
النقاط الرئيسية:
- استخدم
hmac.compare_digest(أو ما يعادلها) للمقارنة الثابتة الوقت لتجنب هجمات التوقيت. - الوصول إلى الجسم الخام عبر
request.get_data()قبل أي تحليل. - أعد 200 بسرعة؛ قم بالمعالجة الثقيلة بشكل غير متزامن.
تصميم استراتيجية إعادة المحاولة
حتى مع التوقيعات، يمكن أن تفشل webhooks: قد يكون خادمك معطلاً، أو يعيد 500، أو ينتهي الوقت. عادةً ما يعيد المزودون المحاولة مع تأخير أسي. بصفتك مستهلكًا، يجب عليك أيضًا التعامل مع إعادة المحاولة بسلاسة.
إليك ما يجب مراعاته:
- Idempotency: يجب أن يكون لكل حدث معرف فريد. قم بتخزين المعرفات المعالجة لتجنب المعالجة المكررة عند حدوث إعادة المحاولة.
- الرد بسرعة: أقر بالاستلام بحالة 2xx في غضون ثوانٍ قليلة. ضع الحدث في قائمة الانتظار للمعالجة في الخلفية.
- منطق إعادة المحاولة من جانبك: إذا كنت تستدعي خدمات لاحقة، فقم بتنفيذ إعادة المحاولة مع التأخير وقواطع الدائرة.
يعيد معظم المزودين المحاولة عند الردود غير 2xx أو المهلات، غالبًا لمدة تصل إلى 24 ساعة بفواصل زمنية متزايدة. راجع وثائق المزود للحصول على التفاصيل.
مثال: معالج أحداث Idempotent
استخدم جدول قاعدة بيانات لتتبع معرفات الأحداث المعالجة. إليك نمطًا مبسطًا:
def handle_event(event_id, payload):
if db.exists('processed_events', event_id):
return # already handled
# process payload
db.insert('processed_events', event_id)
# ... actual work ...
للحصول على إنتاجية عالية، استخدم قيدًا فريدًا على معرف الحدث والتقط أخطاء المفاتيح المكررة.
مقارنة: Webhooks الموقعة مقابل غير الموقعة
| الجانب | الحمولات الموقعة | الحمولات غير الموقعة |
|---|---|---|
| الأمان | يتحقق من أصالة المرسل | يمكن لأي شخص تزوير الطلبات |
| الحماية من إعادة التشغيل | ممكنة مع الطوابع الزمنية | لا شيء |
| جهد التنفيذ | متوسط (HMAC + فحص الطابع الزمني) | منخفض |
| موصى به لـ | جميع webhooks الإنتاج | الاختبار الداخلي فقط |
الأخطاء الشائعة وأفضل الممارسات
- لا تحلل قبل التحقق: تحقق دائمًا من التوقيع على الجسم الخام أولاً.
- استخدم HTTPS: تحمي التوقيعات السلامة، لكن HTTPS يحمي السرية.
- قم بتدوير الأسرار: دعم أسرار نشطة متعددة أثناء التدوير لتجنب التوقف.
- سجل حالات الفشل: سجل عدم تطابق التوقيعات وإعادة المحاولة لتصحيح الأخطاء.
- اختبر بحمولات حقيقية: استخدم أدوات مثل webhook.site أو CLI المزود لمحاكاة الأحداث.
الأسئلة الشائعة
ما هي حمولة webhook الموقعة؟
تتضمن الحمولة الموقعة توقيعًا تشفيريًا (عادة HMAC) يسمح للمستقبل بالتحقق من أن الطلب جاء من مصدر موثوق ولم يتم العبث به.
كيف أتعامل مع إعادة محاولة webhook دون تكرار الإجراءات؟
اجعل معالجك idempotent من خلال تتبع معرفات الأحداث. قبل المعالجة، تحقق مما إذا كان المعرف قد تمت معالجته بالفعل؛ إذا كان كذلك، فأعد النجاح دون تكرار العمل.
هل يمكنني استخدام webhooks بدون توقيعات؟
لا يُنصح بذلك للإنتاج. بدون توقيعات، لا يمكنك التمييز بين الطلبات المشروعة والمزورة. تحقق دائمًا من التوقيعات عند توفرها.
عند تصحيح حمولات webhook أو تحليل سجلات الخادم، يمكن أن تساعدك أداة تنسيق JSON في فحص والتحقق من هياكل JSON بسرعة.