مستوى المقال: متوسط — مناسب لمن سبق له العمل مع Node.js وExpress أو Fastify ويعرف أساسيات الـ HTTP headers.
اعمل Webhook Receiver آمن بـ Node.js: تحقّق من HMAC Signature ورد في 50ms
لو endpoint الـ webhook بتاعك مفتوح من غير verification، أي حد عارف الرابط يقدر يبعتلك payloads مزيّفة ويغيّر حالة قاعدة البيانات. هتتعلّم هنا تبني receiver بيتحقق من توقيع HMAC-SHA256، يرفض الطلبات القديمة، ويرد في أقل من 50ms — في 30 سطر Node.js قابلة للنسخ والاختبار فورًا.
المشكلة باختصار
Stripe وGitHub وShopify كلهم بيبعتوا webhooks بـ POST request على endpoint عندك. لو ما تحققتش من إن الطلب جاي منهم فعلاً، اللي بيحصل في الإنتاج: مهاجم يبعت POST /webhooks/payment بـ {"amount": 0, "status": "paid"} ويفتح اشتراك ببلاش. API key في header مش حل — لأنها بتتسرّب في الـ logs والـ proxies. الحل توقيع HMAC على الـ payload نفسه، بحيث أي تعديل في byte واحد بيكسر التوقيع.
HMAC بمثال بسيط: ختم العجين قبل الفرن
تخيّل صاحب مخبز بيبعت طلبية للفرن المركزي. هو وصاحب الفرن متفقين على ختم سرّي شكله غريب. قبل ما الطلبية تطلع، صاحب المخبز بيغطّس الختم في عجين وبيدوسه فوق الكرتونة. وقت ما الكرتونة توصل، صاحب الفرن بيبص على الختم. لو الختم مش هو، الطلبية بتترمي. حتى لو حد عرف الكرتونة شكلها إيه، مش هيقدر يقلّد الختم لأن الشكل السرّي عند الاتنين بس.
HMAC هو نفس الفكرة بالظبط، بس بدل العجين فيه دالة hash، وبدل الختم فيه secret key. الـ webhook sender بيحسب hash للـ payload + secret ويبعت الناتج في header اسمه X-Signature. الـ receiver بيعيد نفس الحساب وبيقارن. لو ناتج اتنين متطابق، يبقى الطلب جاي من حد فعلًا معاه الـ secret. تعديل byte واحد في الـ body بيغيّر التوقيع تمامًا.
التعريف العلمي الدقيق
HMAC-SHA256 هو Keyed-Hash Message Authentication Code معرّف في RFC 2104 ومحدّث في FIPS 198-1. الصيغة الرياضية:
HMAC(K, m) = H((K' XOR opad) || H((K' XOR ipad) || m))حيث K هو الـ secret، m هو الـ message (الـ raw body)، H هي SHA-256، وopad وipad ثوابت padding. الناتج 32 بايت بنحوّلهم لـ hex string بطول 64 حرف. أهم خاصيتين: (1) لا يمكن استنتاج K من HMAC(K, m) حتى لو عندك آلاف العيّنات، و(2) أي تعديل ولو bit واحد في m بيغيّر ناتج HMAC بالكامل بسبب خاصية avalanche في SHA-256.
الخطوات: ابنِ الـ receiver في 6 خطوات
- ولّد secret قوي. 32 بايت عشوائي على الأقل: . خزّنه في environment variable. ممنوع نهائيًا تكتبه في الكود أو في git.