# التحقّق من توقيع webhook: HMAC-SHA256 والحماية من إعادة التشغيل

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

- الرابط الأساسي: https://www.broadnet.me/ar/reference/webhook-signature-verification
- اللغة: ar
- آخر تحديث: 2026-07-19
- الناشر: BroadNet (https://www.broadnet.me)

كيف تتحقّق من أن webhook أصيل وغير معبوث به: HMAC-SHA256 على الجسم الخام، وترويستا التوقيع والطابع الزمني، والحماية من إعادة التشغيل بنافذة تسامح زمني وقيمة nonce، والمقارنة ثابتة الزمن لهزم هجمات التوقيت، والتدوير الآمن للسرّ بمفاتيح متداخلة.

## لماذا التواقيع لا قوائم السماح بعناوين IP

وجهة webhook عنوان HTTPS عام، فبإمكان أي أحد أن يُرسل إليها عبر POST. وTLS يُثبِت أن العميل بلغ خادمك؛ لكنه لا يُثبِت البتّة أن الطلب أتى فعلًا من منصّة المراسلة. وقائمة السماح بعناوين IP هشّة (عناوين المرسِلين تتغيّر، وتفشل خلف مخرج مشترك). والجواب القياسي هو رمز مصادقة رسالة مُفتاح: يتشارك المرسِل والمستقبِل سرًّا، فيوقّع المرسِل كل حمولة به، ويتحقّق المستقبِل. وHMAC (‏RFC 2104) مع SHA-256 (‏RFC 6234) هو البناء الشائع لأنه سريع ومُعيَّر — وخلافًا لتجزئة مجرّدة لسرّ مُلحَق بالرسالة — مُثبَتُ المقاومة لهجمات تمديد الطول. والتوقيع الصحيح يُثبِت أمرين معًا: أن الحمولة أنتجها حائزٌ للسرّ، وأنها لم تُبدَّل أثناء النقل.

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

يُضمِّن المرسِل ترويستين على الأقل: طابعًا زمنيًا (وقت توقيع الطلب) وتوقيعًا. والمحتوى الموقَّع ليس الـ JSON وحده بل سلسلة قانونية تربط الطابع الزمني بالجسم — عادةً الطابع الزمني وفاصلًا والجسم الخام للطلب — بحيث لا يستطيع مهاجم نقل توقيع صالح إلى حمولة أخرى أو إعادته في وقت مختلف. وتتباين التسمية بحسب المورّد (‏Webhook-Signature / Webhook-Timestamp، أو X-Signature / X-Timestamp)، لكن البناء واحد. والتوقيع عادةً بصيغة hex أو base64 وكثيرًا ما يحمل بادئة مخطّط (مثلًا ‎v1,‎) بحيث يمكن تدوير السرّ إلى مخطّط جديد دون كسر المتحقّقين القدامى.

*المحتوى الموقَّع وتخطيط الترويسة*

```text
signed_content = timestamp + '.' + raw_body
signature      = HMAC_SHA256(secret, signed_content)

Webhook-Timestamp: 1752916522
Webhook-Signature: v1,3f2b8c9a1d...   # hex or base64, scheme-prefixed
```

## التحقّق مقابل الجسم الخام

أشيع خطأ في التحقّق هو توقيع جسم أُعيد تسلسله. فـ HMAC يُحسَب على البايتات بالضبط، لذا على المستقبِل أن يتحقّق مقابل الجسم الخام للطلب تمامًا كما ورد — قبل أي تحليل وإعادة ترميز لـ JSON. فالتحليل إلى كائن ثم إعادة التسلسل يعيد ترتيب المفاتيح، ويغيّر الفراغات، ويُطبِّع الأرقام وهروب Unicode، فينتج بايتات مختلفة وعدم تطابق في التوقيع حتى لو لم يُعبَث بشيء. وعمليًا يعني هذا التقاط الجسم الخام في إطار الويب قبل أن تستهلكه وسائط تحليل الجسم، والتحقّق من HMAC على تلك البايتات، ثم فقط تحليل الـ JSON للاستخدام. والمحرَف مهمّ أيضًا: وقّع وتحقّق من تسلسل الثمانيات نفسه (‏UTF-8)، لا من سلسلة مُفكَّكة الترميز.

*التحقّق (شفرة توضيحية) — الجسم الخام، زمن ثابت*

```text
raw   = request.raw_body            # exact bytes, pre-parse
ts    = header('Webhook-Timestamp')
sig   = header('Webhook-Signature')

if abs(now() - ts) > TOLERANCE: reject   # replay window
expected = hmac_sha256(secret, ts + '.' + raw)
if not constant_time_equal(expected, sig): reject
accept(parse_json(raw))
```

## الحماية من إعادة التشغيل: تسامح الطابع الزمني وقيمة nonce

التوقيع وحده لا يوقف إعادة التشغيل — فطلبٌ ملتقَط لكنه صالح يمكن إعادة إرساله لاحقًا. وآليّتان تسدّان تلك الثغرة. أولًا، لأن الطابع الزمني داخل المحتوى الموقَّع، يقارنه المستقبِل بساعته الخاصة ويرفض أي شيء أقدم من تسامح (بضع دقائق عادةً، واسع بما يكفي لاستيعاب انحراف الساعة وتأخّر إعادة الإرسال، وضيّق بما يكفي لتقييد نافذة إعادة التشغيل). ثانيًا، للضمانات الأصرم يُسجّل المستقبِل قيمة nonce — معرّف الحدث الفريد — لمدّة التسامح ويرفض طلبًا ثانيًا يحمل معرّفًا رآه من قبل. فتسامح الطابع الزمني يحدّ النافذة؛ ومخزن الـ nonce يجعل إعادة التشغيل داخل تلك النافذة عديمة الأثر. ولاحظ أن هذا مكمّل لتكافؤ المستهلِك الذي يعالج التكرار المشروع لا إعادات التشغيل الخبيثة.

*ضوابط الحماية من إعادة التشغيل*

| التحكم | يدافع ضد | الضبط |
|---|---|---|
| الطابع الزمني في المحتوى الموقّع | نقل توقيع إلى توقيت جديد | يجب أن يكون موقّعًا، لا مجرد ترويسة |
| نافذة التسامح مع الطابع الزمني | إعادة تشغيل متأخرة لطلب تم التقاطه | بضع دقائق؛ موازنة بين انحراف الساعة والتعرّض |
| ذاكرة تخزين مؤقت لـ Nonce / event-id | إعادة إرسال (replay) ضمن نافذة التسامح | خزّن المعرّفات مؤقتًا لمدة لا تقل عن هامش التسامح |
| مقارنة ثابتة الوقت | قناة جانبية توقيتية على التوقيع | دائماً، بغض النظر عما سبق |

## المقارنة ثابتة الزمن

مقارنة التوقيع المحسوب والمستلَم بفحص مساواة اعتيادي يُسرِّب معلومات: فالمقارنة بايتًا بايتًا تعود أسرع عند اختلاف مبكّر، والمهاجم القادر على قياس ذلك التوقيت يستطيع استرداد التوقيع الصحيح بايتًا في المرة. لذا يجب أن يستخدم التحقّق مقارنة ثابتة الزمن (مستقلّة عن الطول) — دالّة المقارنة الآمنة زمنيًا في كل منصّة — تفحص دائمًا الطول الكامل قبل أن تعود. وهذا رخيص وغير قابل للتفاوض؛ وهو الفرق بين مخطّط توقيع سليم وآخر يمكن هزمه بعدد كافٍ من الطلبات المُوقَّتة.

## تدوير السرّ

يجب أن تكون أسرار التوقيع قابلة للتدوير دون إسقاط webhooks مشروعة في منتصف الطريق. والنمط الآمن هو مفاتيح متداخلة: يُزوَّد المرسِل بسرّ جديد بينما يبقى القديم صالحًا، وأثناء التداخل إمّا يوقّع المرسِل بكليهما (فيُصدِر توقيعين) أو يقبل المستقبِل توقيعًا يطابق أيًّا من السرّين. وبمجرّد تأكّد كل منتِج على السرّ الجديد، يُتقاعَد القديم. وبادئة المخطّط على التوقيع (‎v1, v2,‎) تجعل هذا مرئيًا وتتيح لتغيير خوارزمي مُصدَّر أن يرافقها. وأداة التحقّق من توقيع webhook المستضافة لدى BroadNet تتيح لك فحص حمولة وطابع زمني وسرّ مقابل HMAC متوقَّع دون كتابة شفرة؛ وميكانيكا توقيع DLR بـ HMAC-SHA256 مغطّاة في مكتبة الأجوبة.

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

### كيف يُحسَب توقيع webhook؟

يبني المرسِل سلسلة قانونية تربط الطابع الزمني بالجسم الخام (عادةً الطابع الزمني + '.' + الجسم)، ويحسب HMAC-SHA256 عليها بسرّ مشترك، ويضع الناتج في ترويسة توقيع إلى جانب الطابع الزمني. ويعيد المستقبِل حساب HMAC نفسه ويقارن. والتطابق يُثبِت أن الحمولة أتت من حائز للسرّ ولم تُبدَّل.

### لماذا يجب أن أتحقّق مقابل الجسم الخام للطلب؟

لأن HMAC يُحسَب على البايتات بالضبط. فإن حلّلت الـ JSON وأعدت تسلسله، تغيّر ترتيب المفاتيح والفراغات وتنسيق الأرقام وهروب Unicode، فتختلف البايتات ويفشل تطابق التوقيع حتى لو لم يُعبَث بشيء. التقط الجسم الخام قبل وسائط تحليل الجسم، ثم تحقّق، ثم حلّل.

### كيف يوقف تسامح الطابع الزمني هجمات إعادة التشغيل؟

الطابع الزمني جزء من المحتوى الموقَّع، فلا يمكن تغييره دون كسر التوقيع. ويقارنه المستقبِل بساعته الخاصة ويرفض أي شيء أقدم من تسامح (بضع دقائق عادةً). وهذا يحدّ مدّة إعادة تشغيل طلب صالح ملتقَط؛ ومخزن الـ nonce يسدّ إعادات التشغيل داخل تلك النافذة.

### لماذا تُشترَط المقارنة ثابتة الزمن؟

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

### كيف أُدوّر سرّ توقيع webhook بأمان؟

استخدم مفاتيح متداخلة. زوّد السرّ الجديد بينما يبقى القديم صالحًا؛ وأثناء التداخل يوقّع المرسِل بكليهما أو يقبل المستقبِل أيًّا منهما، فلا يُسقَط أي webhook مشروع. وبمجرّد تأكّد كل المنتِجين على السرّ الجديد، تقاعَد القديم. وبادئة المخطّط على التوقيع تجعل الانتقال مرئيًا.

### أليس HTTPS كافيًا للوثوق بـ webhook؟

لا. TLS يُثبِت أن النقل مُشفَّر وأن العميل بلغ خادمك، لكن ليس أن الطلب أتى من منصّة المراسلة — فأي أحد يستطيع الإرسال عبر POST إلى عنوان عام. ووحده توقيع HMAC بسرّ مشترك يصادق المرسِل ويُثبِت أن الحمولة لم تُعدَّل. وقائمة السماح بعناوين IP بديل ضعيف لأن عناوين المخرج تتغيّر.

## المصادر

- [IETF RFC 2104 — HMAC: Keyed-Hashing for Message Authentication](https://datatracker.ietf.org/doc/html/rfc2104)
- [IETF RFC 6234 — US Secure Hash Algorithms (SHA and HMAC-SHA)](https://datatracker.ietf.org/doc/html/rfc6234)

## ذو صلة

- [تسليم webhook وموثوقيته](https://www.broadnet.me/ar/reference/webhook-delivery-and-reliability)
- [حالة الرسائل والـ webhooks الواردة](https://www.broadnet.me/ar/reference/message-status-and-inbound-webhooks)
- [مصادقة الواجهة وتوقيعها](https://www.broadnet.me/ar/reference/api-authentication-and-signing)
- [أداة التحقّق من توقيع webhook](https://www.broadnet.me/ar/tools/webhook-signature-validator)
- [توقيع DLR بـ HMAC-SHA256](https://www.broadnet.me/ar/answers/hmac-sha256-dlr-signing)
