سلوك التسليم
حمولة البيانات القياسية، والمهلة المحددة بـ5 ثوانٍ، وسبب عدم إعادة المحاولة، ومعنى كل حالة تسليم.
صُمِّمت آلية تسليم إشعارات webhook لتكون بسيطة، ولهذه البساطة تبعات على الجهة المستقبِلة: محاولة HTTP واحدة، وخمس ثوانٍ، ومن دون إعادة محاولة. اقرأ هذه الصفحة قبل تحديد مقدار العمل الذي ينفذه المعالج مباشرةً أثناء معالجة الطلب.
بنية الحمولة القياسية
يصل كل حدث باستثناء listing.submission بالبنية نفسها على المستوى الأعلى، مع وضع الحقول الخاصة بالحدث داخل data.
{ "event": "connection.location_connected", "timestamp": "2026-08-25T14:32:10Z", "account_id": 11073, "location_id": "279381", "data": { "platform": "google", "connected_account_id": "ba03bc4b-9f8c-4d3b-8e93-9628034c63cc" }}تُحذَف الحقول الاختيارية داخل data عندما تكون فارغة بدلًا من تضمينها وإرسالها بالقيمة null. اكتب الشيفرة بحيث تراعي هذه الحالات: تعامل مع المفتاح الغائب والقيمة الصريحة null بالطريقة نفسها، ولا تفترض أبدًا وجود مفتاح موثَّق على أنه اختياري.
استثناءان
المهلة البالغة 5 ثوانٍ
تتكوَّن كل عملية تسليم من طلب POST واحد، مع مهلة اتصال تبلغ 5 ثوانٍ ومهلة قراءة تبلغ 5 ثوانٍ. إذا لم تستجب نقطة النهاية في الوقت المحدد، تُوقَف المحاولة وتُسجَّل بالحالة timeout.
تشمل هذه المهلة تنفيذ المعالج بأكمله. التصميم الآمن الوحيد هو:
- تحقَّق من التوقيع.
- احفظ الحمولة في وسيط تخزين دائم: قائمة انتظار أو جدول أو سجل.
- أعِد
200.
يجب تنفيذ كل ما عدا ذلك (إضافة بيانات تكميلية، أو توزيع المعالجة على جهات متعددة، أو استدعاء REST API، أو أي عمل يتعامل مع طرف ثالث) بعد سحب الحمولة من قائمة الانتظار.
لا توجد إعادة محاولات
تُجرى محاولة تسليم واحدة فقط. لا توجد آلية لزيادة فترة الانتظار بين المحاولات، ولا قائمة انتظار لإعادة المحاولة، ولا قائمة انتظار للرسائل المتعذَّر تسليمها. إذا تعطَّلت نقطة النهاية لمدة دقيقتين، فستُفقَد الأحداث التي وقعت خلال هاتين الدقيقتين. ولن تصل لاحقًا.
هناك نتيجتان يجب توضيحهما:
- لا تُتبَع عمليات إعادة التوجيه. عمليات إعادة التوجيه معطَّلة تمامًا، لذا تُسجَّل استجابة
301أو302من نقطة النهاية على أنها عملية تسليم فاشلة، ولا يُنتقل إلى عنوان URL ثانٍ. اضبط عنوان URL الخاص بإشعارات webhook ليكون عنوان URL النهائي. انتبه إلى عمليات إعادة التوجيه من النطاق المجرد إلىwwwوعمليات إعادة التوجيه لإضافة الشرطة المائلة الختامية، التي تضيفها خوادم ويب كثيرة افتراضيًا. - لا يُعَدّ التسليم ناجحًا إلا عند تلقي
2xx. تُسجَّل3xxو4xxو5xxجميعها على أنها حالات فشل.
التكرار والترتيب
- قد تتلقى الحدث نفسه مرتين، وقد تتلقى id نفسه لتغييرين مختلفين. تحدد المعرّفات داخل
data(معرّف تفاعل، أو معرّف عنصر منشور، أو معرّف حساب متصل) المورد، لا التغيير. يُطلَقinteraction.reviewمجددًا بمعرّف التفاعل نفسه كلما تغيّر التقييم، ولا تتضمن أي حمولة إصدارًا أو حقلًا لوقت آخر تحديث، لذا فإن معالجًا يتجاهل الأحداث نهائيًا بناءً على id +eventيُسقط تعديلات فعلية دون تنبيه. اكتشف بدلًا من ذلك النسخ المكررة المتطابقة تمامًا: احسب قيمة تجزئة لبايتات جسم الطلب الخام، وتجاهل تكرار قيمة التجزئة نفسها ضمن نافذة زمنية قصيرة ومحدودة (بالدقائق، لا إلى الأبد). جسم طلب مختلف للمعرّف نفسه يعني تغييرًا جديدًا، وليس نسخة مكررة. ثم أعد قراءة المورد من REST API واعتمد تلك القراءة مرجعًا؛ واستخدم معرّف المورد مفتاحًا لعملية الكتابة بدلًا من تخطيها. - الترتيب غير مضمون، ولن يعالج
timestampذلك. تأتي الأحداث من عدة مسارات مستقلة وتُسلَّم فور وصولها، لذا قد يصلlocal_post.publishedقبلlocal_post.createdالذي كنت تتوقع وصوله أولًا. لا ترتب تغييرين للمورد نفسه بناءً علىtimestamp: فهو يُسجَّل عند إعداد التسليم، لا عند حدوث التغيير، ولا يوجد إصدار أو رقم تسلسلي في أي موضع من الحمولة. عندما يكون الترتيب مهمًا، أعد قراءة المورد واستخدم ما تُرجعه API. - يُطبَّق كبح التكرار على
profile.updatedبحيث يُسلَّم مرة واحدة لكل موقع كل 60 ثانية، في بداية النافذة الزمنية. هذا إسقاط فعلي للأحداث، وليس تأخيرًا: يفتح التعديل الأول النافذة ويُسلَّم، ويُسقَط كل تعديل داخل النافذة ولا يصل أبدًا، ولا يصفchanged_fieldsإلا التعديل الذي فتح النافذة. لذلك يصلك الحدث في بداية النافذة، فلا يمكن لإعادة القراءة التي يبدأها وصوله أن ترى سوى ذلك التعديل الأول. جدوِل إعادة قراءة ثانية بعد إغلاق النافذة، بعد مرور 60 ثانية على الأقل من الحدث، وطابق بيانات المواقع دوريًا أيضًا، لأن الإشعار نفسه قد يُفقَد: محاولة واحدة، دون إعادة محاولة. لا يُطبَّق كبح التكرار على أي شيء آخر.
التعافي من فقدان حدث
لا توجد إعادة محاولة ولا إعادة إرسال للأحداث السابقة، لذا تحتاج كل مجموعة أحداث إلى قراءة مرجعية يمكنك الرجوع إليها. طابق البيانات وفق جدول زمني، وليس فقط عندما يبدو أن هناك خللًا.
نتائج التسليم
تُسجَّل كل محاولة مع حالتها، حتى إن لم يُرسَل فيها أي طلب. هذه هي السلاسل النصية كما تُسجَّل تمامًا.
قائمة تحقّق للطرف المستقبِل
- تحقّق من التوقيع في كل طلب؛ وأعِد
401ولا تعالج أي شيء عند عدم التطابق. - احسب التجزئة من بايتات جسم الطلب الخام، ولا تحسبها أبدًا من كائن أُعيد تسلسله.
- أعِد
2xxخلال مدة تقل بكثير عن 5 ثوانٍ؛ وأضِف العمل إلى قائمة الانتظار. - استخدم المعرّفات داخل
dataمفاتيح لكل عملية كتابة ينفّذها المعالج حتى ينتج عن التكرار النتيجة نفسها، لكن لا تعتبر تكرار المعرّف أبدًا دليلاً على عدم وجود عمل يجب تنفيذه. - لا تستنتج الترتيب من ترتيب الوصول، ولا ترتّب حسب
timestamp. - وحّد صيغة
location_idعند الاستلام بتحويله إلى سلسلة نصية. - تجاهل قيم
eventالتي لا تتعرّف عليها، وأعِد200رغم ذلك. - اجعل عنوان URL الخاص بالـ webhooks عنوانًا نهائيًا بلا عمليات إعادة توجيه.
- طابِق البيانات دوريًا مع REST API؛ فلا توجد إعادة محاولة لتدارك ما فات.