بناء تكامل Webhook موثوق لا يفقد الأحداث
صمم تكامل Webhook موثوقاً يتحقق من التوقيع ويحفظ الحدث قبل الرد ويمنع التكرار ويعالج الأحداث داخل طابور مع إعادة تشغيل آمنة.
افصل استقبال الحدث عن تنفيذه: تحقق من الطلب، واحفظه تخزيناً دائماً، ثم أرسل المعالجة إلى طابور آمن عند التكرار. لا تعتبر إعادة الإرسال خطأً نادراً؛ إنها جزء طبيعي من البروتوكول.
يبدو Webhook في الرسم مثل سهم بين نظامين: ترسل خدمة الدفع أو المتجر طلب HTTP، فيحدث تطبيقك البيانات. لكن الإنتاج يضيف انقطاع الشبكة، وتأخر الاستجابة، ووصول الحدث نفسه مرتين، وترتيباً مختلفاً للأحداث، وخدمة داخلية قد تتوقف مؤقتاً.
لذلك لا تُقاس موثوقية التكامل بنجاح أول تجربة في لوحة المزود، بل بقدرته على الوصول إلى الحالة الصحيحة بعد التكرار والفشل وإعادة التشغيل.
ارسم رحلة الحدث كاملة قبل كتابة المتحكم
اكتب المراحل التي يمر بها الحدث:
Provider
-> HTTPS endpoint
-> signature verification
-> durable inbox
-> queue
-> business handler
-> observable result
حدد في كل مرحلة: ما البيانات الداخلة؟ ما الدليل على النجاح؟ ماذا يحدث إذا توقفت المرحلة التالية؟ ومن يستطيع إعادة التنفيذ؟
هذا الرسم يكشف مبكراً خطأ شائعاً: تنفيذ تحديثات قاعدة البيانات وإرسال البريد والاتصال بخدمة أخرى قبل الرد على المزود.
اجعل نقطة الاستقبال محدودة المسؤولية
ينبغي لنقطة Webhook العامة أن تنفذ أربع خطوات قصيرة:
- قراءة جسم الطلب الخام والعناوين المطلوبة.
- التحقق من مصدر الطلب وسلامة محتواه.
- تخزين الحدث تخزيناً دائماً خلف قيد يمنع التكرار.
- إرسال مهمة إلى الطابور ثم إعادة استجابة ناجحة بسرعة.
مثال مبسط في Laravel:
public function __invoke(Request $request): Response
{
$rawBody = $request->getContent();
$this->verifier->verify(
rawBody: $rawBody,
signature: (string) $request->header('Webhook-Signature'),
timestamp: (string) $request->header('Webhook-Timestamp'),
);
$event = $this->inbox->storeOnce(
providerId: (string) $request->header('Webhook-Id'),
rawBody: $rawBody,
);
ProcessWebhook::dispatch($event->id)->afterCommit();
return response()->json(['status' => 'accepted'], 202);
}
لا تُعد النجاح قبل التخزين الدائم؛ قد تضيع العملية بعد انقطاع التطبيق. وفي المقابل لا تنتظر إرسال فاتورة أو بريد داخل الطلب؛ أي بطء سيزيد إعادة الإرسال من المزود.
تحقق من البايتات التي وصلت فعلاً
تعتمد خدمات كثيرة توقيع HMAC على جسم الطلب كما أُرسل. تحويل JSON إلى مصفوفة ثم تكوينه من جديد قد يغير المسافات أو ترتيب المفاتيح أو طريقة كتابة Unicode.
تحقق من النص الخام أولاً، واستخدم مقارنة ثابتة الزمن، وافحص الطابع الزمني عندما يتضمنه البروتوكول لتقليل إعادة استخدام طلب قديم. ولا تحاول تخمين أكثر من خوارزمية حتى تعمل؛ نفذ صيغة المزود كما وثقها.
يشرح دليل التحقق من توقيع Webhook في PHP وLaravel تكوين الرسالة ومقارنة HMAC وتدوير الأسرار بالتفصيل.
احفظ الحدث داخل صندوق وارد دائم
لا تعتمد على السجلات وحدها. أنشئ جدولاً للأحداث الواردة يتضمن أقل مجموعة تساعدك على الاسترداد:
| الحقل | فائدته |
|---|---|
| المزود والحساب | فصل البيئات أو العملاء |
| معرف الحدث عند المزود | منع التكرار والبحث أثناء الدعم |
| نوع الحدث | توجيه المعالجة وبناء المقاييس |
| المحتوى الخام أو النسخة الضرورية منه | إعادة التشغيل والتحقيق |
| وقت الاستقبال | قياس تأخر التسليم |
| الحالة وعدد المحاولات | معرفة موضع التعطل |
| آخر خطأ ووقت النجاح | التشخيص والمراجعة |
ضع قيد UNIQUE في قاعدة البيانات على مفتاح المزود ومعرف الحدث. فحص وجود السجل ثم إدخاله في خطوتين منفصلتين لا يمنع طلبين متزامنين من المرور معاً.
قد يحتوي الحدث على بيانات شخصية. خزن ما تحتاجه فقط، وحدد مدة الاحتفاظ والتشفير والصلاحيات، ولا تعرض المحتوى الكامل في لوحة دعم عامة.
افترض أن الحدث سيتكرر
يستخدم كثير من المزودين تسليماً من نوع at-least-once: قد يصل الحدث أكثر من مرة حتى بعد نجاح المعالجة، خصوصاً إذا ضاعت استجابتك في الشبكة.
يمنع سجل الوارد تكرار الاستقبال، لكن منطق العمل يحتاج حماية مستقلة. إنشاء اشتراك مثلاً يجب أن يعتمد على معرف الاشتراك الخارجي، وإرسال طلب إلى مزود آخر يجب أن يحمل Idempotency Key ثابتاً إذا كان يدعم ذلك.
نجاح المهمة مرتين يجب أن ينتج الحالة التجارية نفسها، لا فاتورتين أو رسالتين أو خصمين.
راجع منع تكرار Webhooks في Laravel لبناء القيد والمعاملة والآثار الجانبية الآمنة.
انقل المعالجة إلى طابور مراقب
تقرأ مهمة الطابور معرف سجل الوارد، ثم تقفل السجل أو تحجزه بطريقة ذرية، وتخرج فوراً إذا كانت الحالة processed.
اجعل المهمة صغيرة وقابلة لإعادة المحاولة، واضبط مهلة اتصالات HTTP وقاعدة البيانات داخلها. لا تجعل مهلة عامل Laravel أطول من retry_after حتى لا تعمل نسختان من المهمة نفسها في الوقت نفسه.
إذا كانت الطوابير لا تتحرك أو يفشل Supervisor في إبقاء العامل حياً، استخدم دليل تشخيص Laravel Queue وSupervisor.
صمم إعادة المحاولة وفق نوع الخطأ
أعد المحاولة عند الأخطاء المؤقتة مثل:
- انقطاع الاتصال.
- مهلة خدمة خارجية.
- رد
429 Too Many Requests. - رد خادمي مؤقت يمكن أن ينجح لاحقاً.
لا تكرر بلا نهاية عند توقيع غير صالح أو نوع حدث غير مدعوم أو انتقال حالة مستحيل. استخدم تأخيراً تصاعدياً مع قدر عشوائي صغير حتى لا تضرب آلاف المهام الخدمة المتعافية في اللحظة نفسها.
بعد آخر محاولة انقل السجل إلى حالة فشل واضحة. نبّه على معدل أو مدة تراكم الأحداث، لا على كل خطأ منفرد قد ينجح في المحاولة التالية.
لا تفترض وصول الأحداث بالترتيب
قد يصل حدث subscription.updated قبل subscription.created أو يصل حدث قديم بعد أحدث منه. منع التكرار لا يحل ترتيباً مختلفاً.
استخدم رقم إصدار أو تسلسل من المزود إن توفر. وفي بعض الأنظمة يكون الأأمن أن تتعامل مع Webhook كإشارة ثم تجلب الحالة الحالية من API المزود. ضع انتقالات حالة مسموحة حتى لا يعيد حدث قديم الطلب من paid إلى pending.
وفر إعادة تشغيل يمكن تدقيقها
تحتاج العمليات إلى البحث بمعرف المزود أو الكيان، ورؤية المحاولات، ثم إعادة معالجة السجل نفسه. يجب أن تستخدم الإعادة المعالج الآمن نفسه، وتسجل من بدأها وسببها ونتيجتها.
لا تنشئ Event جديدة يدوياً لمجرد إعادة المحاولة؛ بذلك تفقد تاريخ التسليم وقد تتجاوز قيد التكرار.
اختبر الفشل قبل الإنتاج
اختبر على الأقل:
- توقيعاً خاطئاً مع جسم صحيح.
- الحدث نفسه مرتين بالتتابع.
- الحدث نفسه في طلبين متزامنين.
- توقف العامل بعد أول كتابة في قاعدة البيانات.
- مهلة خدمة خارجية ثم نجاحها.
- حدثين للكيان نفسه بترتيب عكسي.
- تدوير السر مع قبول المفتاحين خلال فترة الانتقال.
- إعادة تشغيل يدوية لحدث مكتمل.
احتفظ بعينات اختبار منزوعة البيانات الحساسة داخل المستودع. لوحة Sandbox مفيدة، لكنها لا تستبدل اختباراً حتمياً يمكن تكراره في كل إصدار.
أخطاء شائعة
- وضع منطق العمل كله داخل طلب HTTP.
- تحليل JSON ثم استخدام النسخة المعاد تكوينها للتحقق من التوقيع.
- إعادة
200قبل حفظ الحدث. - استخدام Cache مؤقتة فقط لمنع التكرار.
- إعادة المحاولة لكل الأخطاء بالقواعد نفسها.
- تسجيل الأسرار أو كامل بيانات العميل.
- بناء زر إعادة تشغيل يتجاوز المعالج الأصلي.
أسئلة شائعة
أي حالة HTTP أعيد بعد قبول الحدث؟
اتبع توثيق المزود. عادة تكون 200 أو 202 أو 204 مناسبة بعد التحقق والتخزين. الأهم ألا تعيد النجاح قبل امتلاك نسخة دائمة يمكن معالجتها لاحقاً.
هل الطابور وحده يمنع فقدان الأحداث؟
لا. قد يفشل الإرسال إلى الطابور بعد الرد أو تُحذف المهمة. خزّن الحدث أولاً، وأرسل المهمة بعد تثبيت المعاملة، وراقب السجلات التي بقيت في حالة received دون معالجة.
كم مدة الاحتفاظ بالمحتوى الخام؟
حسب حساسية البيانات ومتطلبات الدعم والامتثال. حدد مدة مكتوبة، وقلل البيانات، واحذفها آلياً بعد انتهائها مع إبقاء حقول التدقيق الضرورية.
مراجع رسمية
لديك سؤال عن هذا الدليل أو فكرة لتعاون تقني؟ تواصل مع بكري عبر المركز التقني.
نهاية الملاحظة.