الـ webhook ردّ HTTP من خادم إلى خادم: تُرسِل منصّة المراسلة حدثًا عبر POST إلى عنوان المستهلِك. والتسليم الموثوق يكون مرة واحدة على الأقل، لذا يُعيد المنتِجون إرسال ما فشل بتباطؤ أسّي وتشويش زمني (jitter)، ويُهملونه بعد سقف معيّن، وعلى المستهلِكين أن يكونوا متكافئين لأن التكرار والأحداث غير المرتّبة أمر متوقّع.
- تطبيقكيتم الإرسال عبر REST API أو SMPP
- منصة BroadNetتتحقق من كل رسالة وتوجّهها وتحتسب تكلفتها
- SMSC الخاص بالمشغّلاتصال مباشر مع المشغّل
- هاتف المشتركيتم تسليم الرسالة
بنية الـ webhook
يعكس الـ webhook أدوار العميل/الخادم المعتادة: فبدل أن يستقصي التطبيقُ منصّةَ المراسلة عن التحديثات، تُرسِل المنصّةُ حدثًا عبر POST إلى عنوان سجّله التطبيق — تغيّر حالة تسليم أو رسالة واردة أو حدث حساب. وهذا هو نصف الدفع من النموذج غير المتزامن الذي نصفه الآخر هو استقصاء GET /messages/{id}. ووجهة المستهلِك خادم HTTPS اعتيادي عليه أن يقبل POST، وأن يفعل الحد الأدنى اللازم لتسجيل الحدث بشكل دائم، وأن يعيد 2xx بسرعة. ومن الأنماط الشائعة والمهمّة الإقرارُ عند الاستلام والمعالجةُ لاحقًا بشكل غير متزامن: تكتب الوجهةُ الحدثَ الخام إلى طابور أو سجلّ، وتعيد 200، ويقوم عاملٌ منفصل بالعمل الفعلي — بحيث لا يتسبّب منطق الأعمال البطيء أبدًا في انتهاء مهلة المنتِج وإعادة إرساله.
POST /webhooks/dlr HTTP/1.1
Host: app.example
Content-Type: application/json
Webhook-Id: 8b1a...
Webhook-Timestamp: 1752916522
Webhook-Signature: v1,3f2b...
{
"event": "message.delivered",
"message_id": "01HZY8Q3M6",
"status": "delivered",
"occurred_at": "2026-07-19T09:15:25Z"
}ضمانات التسليم: مرة واحدة على الأقل
لا يمكن للتسليم الموزّع أن يكون مرةً واحدة بالضبط ومتاحًا في آنٍ، لذا يختار كل نظام webhook إنتاجي تقريبًا التسليمَ مرةً واحدة على الأقل: يظل المنتِج يعيد الإرسال حتى يرى إقرار نجاح، متقبّلًا أن المستهلِك قد يستقبل الحدث نفسه أحيانًا أكثر من مرة. والطرفان النقيضان أسوأ — فالتسليم مرةً واحدة على الأكثر يُسقِط الأحداث عند أول فشل، والتسليم مرةً واحدة بالضبط فعلًا يتطلّب تنسيقًا موزّعًا لا يوفّره نقل HTTP. والتسليم مرةً واحدة على الأقل يُلقي التزامًا واحدًا على المستهلِك: أن يكون متكافئًا، فيزيل التكرار بالاعتماد على معرّف حدث ثابت بحيث يكون الحدثُ المُعاد إرساله غير ضارّ. وعقد الإقرار بسيط: أيّ 2xx يعني «توقّف عن إعادة الإرسال»؛ وأيّ شيء آخر (أو انتهاء مهلة) يعني «أعِد الإرسال لاحقًا».
إعادات الإرسال بالتباطؤ الأسّي والتشويش الزمني (jitter)
حين يفشل التسليم أو تنتهي مهلته، يُعيد المنتِج الإرسال وفق جدول ينمو أسّيًا — تأخير أساسي يتضاعف في كل محاولة حتى سقف — بحيث لا تُنهَك وجهةٌ غير متاحة مؤقتًا. والأهم أن التأخير يتضمّن تشويشًا زمنيًا عشوائيًا (jitter): فبدونه، يُعيد أسطولٌ من الأحداث التي فشلت كلّها أثناء العطل نفسه الإرسالَ في تزامن تامّ فينتج زحامٌ جارف متزامن لحظةَ تعافي الوجهة. والتشويش الزمني الكامل (قيمة عشوائية بين الصفر وحدّ التباطؤ الحالي) يوزّع ذلك الحِمل بسلاسة. وتستمر إعادات الإرسال حتى ينجح الحدث أو يبلغ سقف عدد المحاولات أو الزمن الإجمالي، وبعدها يُهمَل الحدث بدل إعادة إرساله إلى الأبد.
base = 2s ; factor = 2 ; cap = 3600s
attempt n bound = min(cap, base * factor^(n-1))
delay(n) = random(0, bound) # full jitter
n=1 bound 2s delay in [0, 2s]
n=2 bound 4s delay in [0, 4s]
n=3 bound 8s delay in [0, 8s]
n=4 bound 16s delay in [0, 16s]
... until success or attempt/time cap -> dead-letterالمُهَل والترتيب
يضبط المنتِج مهلةً قصيرة لكل تسليم — بضع ثوانٍ عادةً — ويعامل انتهاء المهلة معاملة الفشل: يُهجَر الاتصال ويُجدوَل الحدث لإعادة الإرسال. وهذا هو السبب الميكانيكي لوجوب أن يُقرّ المستهلِكون بسرعة ويعالجوا لاحقًا بشكل غير متزامن. والترتيب هو الضمان الأصعب. فلأن إعادات الإرسال والتسليم المتوازي يتداخلان، يكون تدفّق webhook الساذج مرتّبًا على أساس بذل الجهد فحسب: قد يصل حدثٌ سابق أُعيد إرساله بعد حدث لاحق. والأنظمة التي تحتاج الترتيب إمّا تُسلّم حسب المفتاح بالتسلسل (كل أحداث معرّف رسالة واحد على قسم مرتّب واحد، مع الحجب عند الفشل)، أو الأشيع أن تجعل الحمولةَ ذاتيةَ الترتيب — يحمل كل حدث طابعًا زمنيًا وتسلسلًا رتيبًا بحيث يستطيع المستهلِك تجاهل تحديث قديم بدل الاعتماد على ترتيب الوصول.
| الاستراتيجية | كيفية العمل | التكلفة |
|---|---|---|
| بذل أقصى جهد | قم بالتوصيل حسب التوافر، وحاول مرة أخرى بشكل مستقل | قد تصل الأحداث بترتيب غير متسلسل |
| مرتب حسب المفتاح | معالجة جميع أحداث المعرّف الواحد تسلسليًا؛ والإيقاف عند الفشل | حجب رأس الصف (head-of-line)، إنتاجية أقل |
| حمولة ذاتية الترتيب | يحمل الطابع الزمني والتسلسل؛ ويتجاهل المستهلك البيانات القديمة | منطق المستهلك، ولكن بدون حظر |
الرسائل المهملة وصحة الوجهة
الوجهة التي تبقى معطّلة يجب ألّا تستهلك سعة إعادة الإرسال إلى الأبد. فبعد استنفاد سياسة إعادة الإرسال، يُنقَل الحدث إلى طابور رسائل مهملة — مخزنٍ دائم للأحداث غير القابلة للتسليم يستطيع المشغّل فحصه وإعادة تشغيله بعد إصلاح الوجهة — بدل إسقاطه بصمت. ولحماية الطرفين أثناء اعتلال الوجهة، يتتبّع المنتِجون صحة التسليم لكل وجهة ويطبّقون قطع الدائرة: بعد سلسلة فشل مستمرة تُفتَح الدائرة وتُوقَف الأحداث الجديدة أو يُسرَّع فشلها بدل محاولتها، ثم يختبر مسبارٌ نصف مفتوح التعافي دوريًا قبل استئناف التسليم الطبيعي. وهذا يمنع مستهلِكًا معطّلًا من توليد تراكمٍ من المحاولات المحكوم عليها بالفشل، ويتيح لمستهلِك متعافٍ الارتقاء تدريجيًا. وسلوك webhook وإعادة الإرسال الفعلي لدى BroadNet موثّق للمطوّرين.
بناء مستهلِك متكافئ
لأن التسليم مرةً واحدة على الأقل يضمن التكرار، فإن المستهلِك — لا المنتِج — هو من يملك آثار «مرة واحدة بالضبط». والنمط هو مخزن إزالة تكرار يعتمد على معرّف حدث ثابت يوفّره المنتِج (Webhook-Id أو معرّف حدث، لا تجزئة الحمولة وحدها أبدًا). فعند الاستلام يتحقّق المستهلِك مما إذا كان قد عالج ذلك المعرّف؛ فإن كان كذلك أقرّ بـ 2xx ولم يفعل شيئًا آخر؛ وإن لم يكن سجّل المعرّف وطبّق الأثر في المعاملة نفسها بحيث لا يستطيع تعطّلٌ تطبيق الأثر دون تسجيل المعرّف. وبالاقتران مع الحمولات ذاتية الترتيب، يجعل هذا الحدثَ المُعاد إرساله أو غير المرتّب عمليةً لا أثر لها، وهو ما يحوّل النقلَ مرةً واحدة على الأقل إلى نتائج أعمال مرةً واحدة بالضبط.
on POST(event):
verify_signature(event) # reject if invalid
id = event.headers['Webhook-Id']
begin transaction:
if seen(id): return 200 # duplicate -> no-op
if event.sequence <= last_seq(event.message_id):
mark_seen(id); return 200 # stale -> discard
apply_effect(event)
mark_seen(id); set_last_seq(...)
commit
return 200الأسئلة المتكررة
ما ضمان التسليم الذي توفّره الـ webhooks؟
مرة واحدة على الأقل. يُعيد المنتِج الإرسال حتى يرى إقرار 2xx، لذا قد يستقبل المستهلِك الحدث نفسه أكثر من مرة. والتسليم مرةً واحدة بالضبط عبر HTTP المجرّد يتطلّب تنسيقًا موزّعًا لا يوفّره النقل، لذا يُتوقّع من المستهلِك أن يكون متكافئًا ويزيل التكرار بالاعتماد على معرّف حدث ثابت.
لماذا يُضاف التشويش الزمني (jitter) إلى تباطؤ إعادة إرسال webhook؟
بدون التشويش الزمني، يُعيد كل حدث فشل أثناء عطل واحد الإرسالَ في تزامن تامّ عند تعافي الوجهة، فينشأ زحامٌ جارف متزامن قد يُسقِطها من جديد. والتشويش الزمني الكامل — تأخير عشوائي بين الصفر وحدّ التباطؤ الحالي — يوزّع إعادات الإرسال بسلاسة بحيث ترى الوجهة المتعافية ارتقاءً تدريجيًا.
كيف ينبغي أن تستجيب وجهة webhook؟
بسرعة وبـ 2xx. والنمط الموصى به هو تسجيل الحدث الخام بشكل دائم، وإعادة 200 فورًا، والمعالجة لاحقًا في عامل منفصل. وبهذا لا يتسبّب منطق الأعمال البطيء أبدًا في إطلاق مهلة تسليم المنتِج وإثارة إعادة إرسال لا لزوم لها.
هل تُسلَّم أحداث webhook مرتّبة؟
افتراضيًا على أساس بذل الجهد فحسب — فإعادات الإرسال والتسليم المتوازي قد يتداخلان، فيصل حدثٌ سابق بعد حدث لاحق. وللحصول على الترتيب، إمّا سلسِل حسب المفتاح (على حساب الحجب عند رأس الطابور) أو اجعل الحمولةَ ذاتيةَ الترتيب بطابع زمني ورقم تسلسل بحيث يتجاهل المستهلِك التحديثات القديمة.
ما طابور الرسائل المهملة في الـ webhooks؟
مخزنٌ دائم للأحداث التي تعذّر تسليمها بعد استنفاد سياسة إعادة الإرسال. فبدل إسقاطها بصمت، يوقفها المنتِج ليستطيع المشغّل فحص الإخفاقات وإعادة تشغيل الأحداث بعد إصلاح وجهة المستهلِك.
لماذا يُطبَّق قطع الدائرة على وجهات webhook؟
لولا ذلك لاستهلكت وجهةٌ فاشلة باستمرار سعةَ إعادة الإرسال بمحاولات محكوم عليها بالفشل وبنَت تراكمًا. ويُفتَح قطع الدائرة بعد فشل مستمر، فيوقف الأحداث الجديدة أو يُسرّع فشلها، ثم يستخدم مسبارًا نصف مفتوح لاختبار التعافي قبل استئناف التسليم الطبيعي — حمايةً للمنتِج وللمستهلِك المتعافي معًا.
مواصفات المصدر
تستند هذه الصفحة إلى المعايير المنشورة. يفتح في علامة تبويب جديدة.