MailLandDocs
خطافات الويب

التحقق من التوقيع

التحقق من توقيعات webhook لضمان صحة حمولات webhook الواردة

تتضمن كل عملية تسليم webhook توقيعاً تشفيرياً حتى تتمكن من التحقق من أن الطلب صادر من MailLand ولم يتم التلاعب به أثناء النقل. احرص دائماً على التحقق من التوقيعات في بيئة الإنتاج.

آلية التوقيع

تقوم MailLand بتوقيع كل حمولة webhook باستخدام HMAC-SHA256 مع المفتاح السري لـ webhook الخاص بك. يُدرج التوقيع في رأس X-MLND-Signature.

تنسيق التوقيع:

X-MLND-Signature: t=1708171200,v1=b9f747ea899eafe911777060ca8f3c90828d37f1e16ee6e9ee98dacf8b316b67
المكوّنالوصف
tالطابع الزمني Unix (بالثواني) وقت إنشاء التوقيع
v1توقيع HMAC-SHA256 بصيغة سداسية عشرية (بالأحرف الصغيرة)

الحمولة الموقّعة:

{timestamp}.{raw_json_body}

يُحسب التوقيع على الطابع الزمني متصلاً بنقطة وجسم طلب JSON الخام.

مثال محلول

قارن تنفيذك بهذه القيم بالضبط قبل الانتقال إلى الإنتاج. كل ما يلي حقيقي: التوقيع هو ما ينتجه MailLand لهذا المفتاح السري وهذا الطابع الزمني وهذا الجسم.

المُدخلالقيمة
المفتاح السريwhsec_docs_example_secret_do_not_use
الطابع الزمني (t)1708171200
الجسم الخام{"event":"message.delivered","event_id":"evt_a1b2c3d4e5f6"}

النص الموقَّع: الطابع الزمني ثم نقطة ثم الجسم كما هو:

1708171200.{"event":"message.delivered","event_id":"evt_a1b2c3d4e5f6"}

قيمة HMAC-SHA256 لهذا النص باستخدام المفتاح السري، بصيغة ست عشرية صغيرة:

b9f747ea899eafe911777060ca8f3c90828d37f1e16ee6e9ee98dacf8b316b67

وبالتالي تكون ترويسة هذا التسليم:

X-MLND-Signature: t=1708171200,v1=b9f747ea899eafe911777060ca8f3c90828d37f1e16ee6e9ee98dacf8b316b67

أعد إنتاجها في سطر واحد:

printf '%s' '1708171200.{"event":"message.delivered","event_id":"evt_a1b2c3d4e5f6"}' \
  | openssl dgst -sha256 -hmac 'whsec_docs_example_secret_do_not_use'

الطابع الزمني في هذا المثال قديم، لذا سيرفضه أي مُتحقِّق حقيقي بسبب نافذة التسامح. اختبر HMAC أولًا مع تعطيل فحص الوقت ثم أعِد تفعيله.

الرؤوس المُرسَلة

تتضمن كل عملية تسليم webhook الرؤوس التالية:

الرأسمثالالوصف
X-MLND-Signaturet=1708171200,v1=abc123...الطابع الزمني وتوقيع HMAC-SHA256
X-MLND-Event-Idevt_a1b2c3d4e5f6...معرّف الحدث الفريد لإلغاء التكرار
X-MLND-Eventmessage.deliveredنوع الحدث
X-MLND-Event-Version1إصدار مخطط الحمولة
X-MLND-Webhook-Idwh_abc123def456المعرّف العام لـ webhook الخاص بك
X-MLND-Attempt1رقم محاولة التسليم (يبدأ من 1)
X-MLND-Delivery-Idp8r2kx7m9wq4rtمعرّف هذه المحاولة، جديد في كل إعادة؛ وهو publicId في سجل التسليمات. لا يُستخدم لإزالة التكرار: راجع الإصدارات
X-MLND-Delivery-Timestamp1708171200وقت إرسال هذه المحاولة (ثواني Unix)؛ يساوي دائمًا t في التوقيع

يوجد رأسان إضافيان مشروطان بنوع الحدث الذي اشتركت فيه:

الرأسمثاليُرسَل عندما
X-MLND-Sourceinboundموجود فقط في تسليمات inbound.received
X-MLND-Inbound-Email-Idinb_a1b2c3d4e5f6موجود فقط في تسليمات inbound.received

الرأس X-MLND-Message-Id غير مُرسَل حالياً في تسليمات message.*. استخدم حقل data.message_id في محتوى الحدث بدلاً منه.

يستخدم inbound.received أيضاً مسار إعادة محاولة مختلفاً بحد أقصى خاص به للمحاولات: راجع قسم تسليمات البريد الوارد في منطق إعادة المحاولة.

خطوات التحقق

استخراج الطابع الزمني والتوقيع

قم بتحليل رأس X-MLND-Signature. قسّمه على , للحصول على مكوّني t=... وv1=....

إعداد الحمولة الموقّعة

اربط الطابع الزمني بنقطة (.) وجسم الطلب الخام:

signed_payload = timestamp + "." + raw_body

حساب التوقيع المتوقع

أنشئ تجزئة HMAC-SHA256 باستخدام المفتاح السري لـ webhook كمفتاح والحمولة الموقّعة كرسالة. حوّلها إلى سداسية عشرية بأحرف صغيرة.

مقارنة التوقيعات

استخدم مقارنة ذات وقت ثابت للتحقق من تطابق التوقيع المحسوب مع قيمة v1 الواردة في الرأس.

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

تحقق من أن الطابع الزمني يقع ضمن 5 دقائق من الوقت الحالي لمنع هجمات إعادة التشغيل.

أمثلة البرمجة

verify-signature.sh
#!/usr/bin/env bash
# Compute and verify an HMAC-SHA256 webhook signature using openssl

SIGNATURE_HEADER="t=1708171200,v1=b9f747ea899eafe911777060ca8f3c90828d37f1e16ee6e9ee98dacf8b316b67"
RAW_BODY='{"event":"message.delivered","event_id":"evt_a1b2c3d4e5f6"}'
SECRET="whsec_docs_example_secret_do_not_use"
TOLERANCE=300

# Parse timestamp and signature from header
TIMESTAMP=$(echo "$SIGNATURE_HEADER" | grep -oP '(?<=t=)[^,]+')
RECEIVED_SIG=$(echo "$SIGNATURE_HEADER" | grep -oP '(?<=v1=).+')

# Check timestamp tolerance
NOW=$(date +%s)
DIFF=$(( NOW - TIMESTAMP ))
if [ "${DIFF#-}" -gt "$TOLERANCE" ]; then
  echo "Error: Timestamp outside tolerance window" >&2
  exit 1
fi

# Compute expected signature
SIGNED_PAYLOAD="${TIMESTAMP}.${RAW_BODY}"
EXPECTED=$(printf '%s' "$SIGNED_PAYLOAD" | openssl dgst -sha256 -hmac "$SECRET" | awk '{print $2}')

# Compare signatures
if [ "$EXPECTED" = "$RECEIVED_SIG" ]; then
  echo "Signature valid"
else
  echo "Signature invalid" >&2
  exit 1
fi

منع هجمات إعادة التشغيل

يمنع الطابع الزمني في رأس التوقيع هجمات إعادة التشغيل. إذا التقط مهاجم عملية تسليم webhook صالحة وأعاد إرسالها لاحقاً، فإن فحص الطابع الزمني سيرفضها.

الحد الافتراضي لـ MailLand هو 5 دقائق (300 ثانية). يمكنك ضبط هذا في كود التحقق الخاص بك، لكن احرص على إبقائه قصيراً بما يكفي لمنع هجمات إعادة التشغيل ذات المغزى.

علاوة على ذلك، يمتلك كل حدث event_id فريداً (في رأس X-MLND-Event-Id). خزّن معرّفات الأحداث المعالَجة وارفض المكررات للحصول على حماية إضافية.

يزيل MailLand تكرار نية الإرسال من جانبه: الحدث الذي يُولَّد مرتين يُرسَل مرة واحدة فقط لكل ويب هوك خلال نافذة 24 ساعة. وهذا ليس نفسه "التسليم مرة واحدة على الأكثر": فالتسليم الفاشل يُعاد، وكل محاولة تحمل نفس event_id ونفس الجسم، ويتغير فقط X-MLND-Attempt والطابع الزمني للتوقيع. أزل التكرار بالاعتماد على event_id واعتبر التسليمات المتكررة أمرًا متوقعًا.

كل تسليم موقَّع

لا يوجد مسار تسليم غير موقَّع. إذا لم يكن لدى webhook مفتاح سري توقيع صالح للاستخدام، تُفشل MailLand التسليم وتسجّل الفشل بدلاً من إرسال الحمولة بدون رأس X-MLND-Signature: لذا فإن رأس التوقيع المفقود ليس أبداً شيئاً يحتاج المُتحقِّق لديك إلى التسامح معه. ارفض أي طلب لا يحمل واحداً.

إعادة توليد الأسرار

إذا اشتبهت في اختراق المفتاح السري لـ webhook الخاص بك:

  1. استدعِ POST /v1/webhooks/{id}/regenerate-secret للحصول على مفتاح سري جديد
  2. حدّث كود التحقق في خادمك بالمفتاح السري الجديد
  3. يُلغى المفتاح السري القديم فوراً

لا توجد فترة سماح عند إعادة توليد الأسرار. حدّث كود التحقق قبل عملية تسليم webhook التالية لتجنب رفض الأحداث الصالحة.

الأخطاء الشائعة

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

الخطوات التالية

في هذه الصفحة