يا جماعة الخير، السلام عليكم ورحمة الله.
خلوني أحكيلكم قصة صارت معي قبل كم سنة. كنا شغالين على نظام متجر إلكتروني كبير، وكان مربوط مع بوابة دفع عالمية. كل ما زبون يعمل طلبية، كنا ننتظر تأكيد الدفع من بوابة الدفع عشان نغير حالة الطلبية من “قيد الانتظار” إلى “مؤكدة” ونبدأ عملية الشحن. في البداية، شو عملنا؟ عملنا أبسط إشي ممكن يخطر على بال مبرمج: الـ Polling.
كتبنا سكربت بسيط، كل 5 ثواني يروح يسأل بوابة الدفع: “يا بوابة الدفع، شو صار بالطلبية رقم 123؟ اندفعت؟”، “طيب والطلبية 124؟”، “طيب و125؟”. في الأيام الأولى والأسبوع الأول، كان الوضع “عال العال” والنظام شغال زي الحلاوة. بس لما كبر المشروع وزادت الطلبات، بلشت المصايب. السيرفر صار يصيح من كثرة الطلبات اللي طالعة منه، واستهلاك الموارد صار بالسما. وفوق كل هاد، بوابة الدفع بلشت تبعتلنا تحذيرات إنه رح توقف حسابنا بسبب تجاوزنا للـ Rate Limit (الحد المسموح للطلبات في الدقيقة). كنا قاعدين بنشرب قهوة والمدير التقني دخل علينا وجهه أصفر، وقال: “يا جماعة، السيرفرات رح توقع، شو القصة؟!”.
هون كانت لحظة الحقيقة. أدركنا إنه طريقتنا في “السؤال كل شوي” مش بس غبية، بل هي مدمرة للنظام. ومن هنا بدأت رحلتنا مع الحل السحري اللي أنقذنا: الـ Webhooks.
ما هو الـ Polling؟ أو “طريقة السؤال كل شوي”
قبل ما نغوص في الحل، خلينا نفهم أصل المشكلة. الـ Polling (الاستعلام المستمر) هو ببساطة إنك كـ “عميل” (Client)، زي السيرفر تبعنا، تضلك تسأل “الخادم” (Server)، زي بوابة الدفع، بشكل متكرر: “هل في إشي جديد؟”، “هل تغيرت الحالة؟”، “هل وصلت البيانات؟”.
تخيل حالك بتستنى مكالمة مهمة، وبدل ما تستنى التلفون يرن، بتمسك التلفون كل 10 ثواني وبتتصل على حالك عشان تشوف إذا في حدا حاول يرن عليك! منطق غريب، صح؟ هاد هو الـ Polling بالضبط.
في عالم الكود، كان شكله إشي زي هيك (مثال بسيط باستخدام JavaScript):
// Polling Example in JavaScript
setInterval(async () => {
try {
// كل 5 ثواني، بنبعت طلب لنشوف حالة الطلبية
const response = await fetch('https://api.payment-gateway.com/orders/123');
const data = await response.json();
if (data.status === 'paid') {
console.log('الدفع تم بنجاح! تحديث حالة الطلبية...');
// هنا نوقف الـ Polling ونحدث قاعدة البيانات
// clearInterval(...)
} else {
console.log('لسه الحالة قيد الانتظار...');
}
} catch (error) {
console.error('حدث خطأ أثناء الاستعلام:', error);
}
}, 5000); // 5000 milliseconds = 5 seconds
جحيم الـ Polling: ليش هو سيء؟
طريقتنا هاي كانت بتسبب ثلاث مشاكل رئيسية:
- استهلاك فظيع للموارد: كل طلب API يعني استهلاك للمعالج (CPU)، للذاكرة (Memory)، وللشبكة (Network)، سواء على سيرفرك أو على سيرفر الخدمة اللي بتسألها. تخيل آلاف الطلبات بتتسأل عن حالتها كل كم ثانية… كارثة حقيقية.
- تأخير في الحصول على البيانات: حتى لو بتسأل كل 5 ثواني، لو الدفع تم في الثانية الأولى، أنت ما رح تعرف إلا بعد 4 ثواني. بياناتك دايماً متأخرة بمقدار الفترة الزمنية اللي حددتها.
- تجاوز حدود الاستخدام (Rate Limiting): معظم الـ APIs المحترمة بتفرض حد على عدد الطلبات اللي بتقدر تبعتها في الدقيقة أو الساعة. مع الـ Polling، رح توصل لهاد الحد بسرعة البرق، والنتيجة؟ حظر مؤقت أو حتى دائم لحسابك.
الحل السحري: Webhooks أو “لما يصير إشي، خبرني!”
الـ Webhook هو عكس الـ Polling تماماً. بدل ما أنت تروح تسأل، بتعطي للخدمة الثانية (بوابة الدفع في مثالنا) عنوانك (URL)، وبتحكيلها: “اسمعي، بس يصير حدث معين عندي (مثلاً الدفع ينجح)، لا تغلب حالك، بس ابعتيلي رسالة على هاد العنوان مع كل التفاصيل”.
الفكرة هاي معروفة في عالم البرمجة بـ “الهندسة القائمة على الأحداث” (Event-Driven Architecture). أنت ما بتسأل، أنت بتتفاعل مع الأحداث لما تصير. وهيك، بدل ما تتصل على مطعم البيتزا كل دقيقة تسألهم “خلصت طلبيتي؟”، المطعم هو اللي برن عليك لما الطلبية تجهز. منطقي وأكثر كفاءة، أليس كذلك؟
كيف تعمل الـ Webhooks؟
الآلية بسيطة جداً ومكونة من ثلاث خطوات:
- أنت (تطبيقك): بتجهز عنوان URL خاص في تطبيقك (مثلاً:
https://my-store.com/webhooks/payment-updates). هذا العنوان يسمى “Webhook Endpoint”. - أنت (في لوحة تحكم الخدمة الأخرى): بتروح على لوحة تحكم بوابة الدفع، وبتضيف الـ URL تبعك في قسم الـ Webhooks، وبتحدد الأحداث اللي بدك ياهم يخبروك عنها (مثلاً:
payment.succeeded). - الخدمة الأخرى (بوابة الدفع): لما يتم الدفع بنجاح، السيرفر تبعهم تلقائياً بيبعت طلب HTTP من نوع
POSTعلى الـ URL تبعك. هذا الطلب بيحتوي على جسم (Body) فيه كل تفاصيل الحدث بصيغة JSON.
تطبيقك بكون بستنى على هاد العنوان، وبس يستقبل الطلب، بقرأ البيانات وبعمل الإجراء اللازم (مثل تحديث حالة الطلبية في قاعدة البيانات).
بناء أول Webhook Endpoint لك: مثال عملي
خلينا نشوف مثال عملي وبسيط لبناء Webhook Endpoint باستخدام Node.js و Express.js، لأنه إطار عمل خفيف وممتاز لهيك مهام.
الخطوة الأولى: تجهيز السيرفر
أولاً، تأكد إنك ثبتت Express. ثم أنشئ ملف سيرفر أساسي.
// server.js
const express = require('express');
const app = express();
const PORT = 3000;
// Middleware مهم جداً عشان Express يفهم الـ JSON اللي جاي في جسم الطلب
// ملاحظة: بعض خدمات الـ Webhook تتطلب قراءة الـ Raw Body للتحقق من التوقيع، سنتحدث عن هذا لاحقاً
app.use(express.json());
app.get('/', (req, res) => {
res.send('سيرفر الـ Webhooks شغال!');
});
app.listen(PORT, () => {
console.log(`السيرفر بستنى على البورت ${PORT}`);
});
الخطوة الثانية: إنشاء الـ Endpoint
الآن، لنضف المسار (Route) الذي سيستقبل طلبات الـ Webhook.
// ... باقي كود السيرفر
// هذا هو الـ Endpoint اللي رح تعطيه لبوابة الدفع
app.post('/webhooks/payment-updates', (req, res) => {
console.log('Webhook جديد وصل!');
// الـ payload أو البيانات اللي بعتتها بوابة الدفع بتكون في req.body
const payload = req.body;
console.log('البيانات:', payload);
// هنا تبدأ بمعالجة البيانات
// مثال: التحقق من نوع الحدث
if (payload.event === 'payment.succeeded') {
const orderId = payload.data.order_id;
const amount = payload.data.amount;
console.log(`تم دفع الطلبية رقم ${orderId} بنجاح بمبلغ ${amount}`);
// ... اكتب الكود لتحديث قاعدة بياناتك هنا
}
// **مهم جداً:** أرجع استجابة ناجحة (200 OK) بسرعة
// هذا يخبر الخدمة أنك استلمت الـ Webhook بنجاح
res.status(200).send('Webhook received');
});
الخطوة الثالثة: تأمين الـ Endpoint (نصيحة احترافية)
أي شخص بيعرف عنوان الـ Endpoint تبعك بيقدر يبعتلك طلبات مزيفة. لهذا السبب، معظم الخدمات المحترمة مثل Stripe وGitHub وShopify تستخدم “توقيعاً” (Signature) لتأكيد أن الطلب قادم منهم فعلاً.
الآلية كالتالي: الخدمة بتنشئ “Secret Key” خاص فيك. مع كل Webhook بتبعته، بتستخدم هاد المفتاح لتوقيع البيانات وبتبعتلك التوقيع في الـ Headers (مثلاً X-Stripe-Signature). مهمتك هي إنك تعيد حساب التوقيع عندك باستخدام نفس المفتاح وتقارنه بالتوقيع اللي وصلك. إذا تطابقوا، فالطلب أصلي.
نصيحة من أبو عمر: لا تتجاهل خطوة التحقق من التوقيع أبداً في بيئة الإنتاج (Production)! تجاهلها يعني ترك باب خلفي مفتوح للهجمات.
المثال التالي يوضح الفكرة (الكود يختلف قليلاً حسب الخدمة، لكن المبدأ واحد):
const crypto = require('crypto');
const webhookSecret = 'whsec_...'; // هذا المفتاح السري تأخذه من بوابة الدفع
// ...
// ملاحظة: للتحقق من التوقيع، تحتاج للـ Raw Body وليس الـ JSON
// لذلك، قد تحتاج لتعديل الـ Middleware قليلاً
app.post('/webhooks/secure-payment-updates', express.raw({type: 'application/json'}), (req, res) => {
const signature = req.headers['stripe-signature']; // اسم الـ header يختلف
try {
const event = crypto.webhooks.constructEvent(
req.body, // هنا نستخدم الـ Raw Body (Buffer)
signature,
webhookSecret
);
// إذا لم يحدث خطأ، فالتوقيع صحيح والطلب آمن
console.log('Webhook موثوق وصل:', event);
// الآن يمكنك معالجة الـ event.data بأمان
if (event.type === 'payment_intent.succeeded') {
// ...
}
res.status(200).send('Success');
} catch (err) {
console.log(`⚠️ فشل التحقق من توقيع الـ Webhook: ${err.message}`);
return res.status(400).send('Webhook Error: Invalid signature');
}
});
نصائح من خبرة أبو عمر
بعد سنوات من التعامل مع الـ Webhooks، تعلمت كم درس “على جلدي” كما يقال. إليكم أهمها:
- نصيحة 1: لا تثق بالـ Webhook دائماً: الشبكات غير موثوقة. ممكن الـ Webhook ما يوصلك أبداً (سيرفرك كان واقع، مشكلة شبكة، …إلخ). الحل؟ ابنِ نظام “مصالحة” (Reconciliation). مثلاً، شغل سكربت Polling خفيف جداً مرة كل يوم بالليل، يسأل عن حالة كل الطلبات اللي لسه “قيد الانتظار” من 24 ساعة. هذا يضمن لك التقاط أي تحديثات ضائعة.
- نصيحة 2: اجعل معالجة الـ Webhook سريعة جداً: الخدمة اللي بتبعت الـ Webhook بتتوقع منك رد
200 OKخلال ثوانٍ قليلة. إذا تأخرت، رح تعتبر إنك فشلت في الاستلام ورح تحاول تبعت الطلب مرة ثانية، مما يسبب تكراراً للمعلومات. القاعدة الذهبية: استلم الطلب، تحقق من توقيعه، ضعه في طابور مهام (Message Queue) مثل RabbitMQ أو AWS SQS، ثم أرجع استجابة200 OKفوراً. دع “عامل” (Worker) منفصل يأخذ المهمة من الطابور ويعالجها براحته. - نصيحة 3: استخدم أدوات لتجربة الـ Webhooks محلياً: كيف ممكن بوابة دفع على الإنترنت تبعت طلب لسيرفر شغال على جهازك (localhost)؟ مستحيل مباشرة. هنا تأتي روعة أدوات مثل
ngrok. هي أداة بسيطة بتعطيك رابط عام على الإنترنت، وأي طلب بيوصل على هاد الرابط بتحوله مباشرة إلى جهازك على بورت معين. هذا يسمح لك بتجربة وتصحيح أخطاء الـ Webhooks على جهازك المحلي قبل رفع الكود للسيرفر.
الخلاصة: ارتاح وخلي الخبر يجيك لعندك! 🏁
الانتقال من الـ Polling إلى الـ Webhooks كان نقلة نوعية في كل الأنظمة التي بنيناها. حولنا أنظمتنا من “متسول” يطرق الأبواب كل ثانية، إلى “سيد” يجلس في قصره وتأتيه الأخبار فور حدوثها.
باختصار:
- استخدم الـ Polling (بحذر شديد): فقط إذا كانت الخدمة لا تدعم الـ Webhooks، أو كآلية احتياطية ثانوية.
- استخدم الـ Webhooks: في 99% من الحالات. هي الحل الأمثل للأنظمة الحديثة التي تتطلب كفاءة عالية، استجابة لحظية، وقابلية للتوسع.
نصيحتي الأخيرة لكل مطور: في المرة القادمة التي تفكر فيها بكتابة setInterval لطلب بيانات من API، توقف لحظة واسأل نفسك: “هل هذه الخدمة تدعم الـ Webhooks؟”. إذا كان الجواب نعم، فأنت تعرف ما عليك فعله. ما تضلك تسأل كل شوي، خلي الخبر يجيك لعندك!