سلوك التسليم

حمولة البيانات القياسية، والمهلة المحددة بـ5 ثوانٍ، وسبب عدم إعادة المحاولة، ومعنى كل حالة تسليم.

صُمِّمت آلية تسليم إشعارات webhook لتكون بسيطة، ولهذه البساطة تبعات على الجهة المستقبِلة: محاولة HTTP واحدة، وخمس ثوانٍ، ومن دون إعادة محاولة. اقرأ هذه الصفحة قبل تحديد مقدار العمل الذي ينفذه المعالج مباشرةً أثناء معالجة الطلب.

بنية الحمولة القياسية

يصل كل حدث باستثناء listing.submission بالبنية نفسها على المستوى الأعلى، مع وضع الحقول الخاصة بالحدث داخل data.

JSON
{  "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"  }}
الحقلالنوعملاحظات
eventstringاسم الحدث الذي تفصل النقاط بين أجزائه. استخدمه لتحديد مسار التنفيذ.
timestampstringوقت بتنسيق ISO-8601 وبتوقيت UTC، يُسجَّل عند إعداد عملية التسليم، وليس عند حدوث التغيير الأصلي.
account_idintegerالحساب الذي ينتمي إليه الحدث.
location_idstringسلسلة نصية رقمية: "279381"، وليست 279381. لا يُحوَّل النوع في interaction.*؛ راجع الاستثناء أدناه.
dataobjectالحقول الخاصة بالحدث. موثَّقة لكل حدث في مرجع الأحداث.
agency_account_idintegerلا يظهر إلا عندما يُدار الحساب بواسطة حساب وكالة رئيسي.

تُحذَف الحقول الاختيارية داخل data عندما تكون فارغة بدلًا من تضمينها وإرسالها بالقيمة null. اكتب الشيفرة بحيث تراعي هذه الحالات: تعامل مع المفتاح الغائب والقيمة الصريحة null بالطريقة نفسها، ولا تفترض أبدًا وجود مفتاح موثَّق على أنه اختياري.

استثناءان

المهلة البالغة 5 ثوانٍ

تتكوَّن كل عملية تسليم من طلب POST واحد، مع مهلة اتصال تبلغ 5 ثوانٍ ومهلة قراءة تبلغ 5 ثوانٍ. إذا لم تستجب نقطة النهاية في الوقت المحدد، تُوقَف المحاولة وتُسجَّل بالحالة timeout.

تشمل هذه المهلة تنفيذ المعالج بأكمله. التصميم الآمن الوحيد هو:

  1. تحقَّق من التوقيع.
  2. احفظ الحمولة في وسيط تخزين دائم: قائمة انتظار أو جدول أو سجل.
  3. أعِد 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 ثانية على الأقل من الحدث، وطابق بيانات المواقع دوريًا أيضًا، لأن الإشعار نفسه قد يُفقَد: محاولة واحدة، دون إعادة محاولة. لا يُطبَّق كبح التكرار على أي شيء آخر.

التعافي من فقدان حدث

لا توجد إعادة محاولة ولا إعادة إرسال للأحداث السابقة، لذا تحتاج كل مجموعة أحداث إلى قراءة مرجعية يمكنك الرجوع إليها. طابق البيانات وفق جدول زمني، وليس فقط عندما يبدو أن هناك خللًا.

مجموعة الأحداثإعادة القراءة باستخدامالفاصل الزمني المقترحما لا يمكن استعادته بالقراءة
listing.submissionGET /locations/{locationId}/listings/premium، مرة باستخدام listingType=premium ومرة باستخدام additional: قيمتا syncStatus وlistingUrl لكل موقع اللتان يُعلِن عنهما الحدث.يوميًا لكل موقع، وبعد تعديل المواقع دفعة واحدة.الحمولة القديمة لا تحتوي على timestamp ولا على account_id، لذا لا يمكن تحديد توقيت الحدث المفقود أو نسبته إلى حساب. لا تتيح أي قراءة الوصول إلى error_message في عملية إرسال incomplete.
profile.*GET /locations، أو GET /locations/search، أو GET /locations-by-ids. تخبرك قيمة updatedAt لكل موقع بالمواقع التي تغيّرت.كل ساعة، بالإضافة إلى إعادة القراءة بعد انتهاء النافذة الموضحة أعلاه.source (user / api / bulk_edit) وchanged_fields: تُظهر القراءة الحالة الحالية، ولا تُظهر أبدًا من غيّر ماذا. لا تترك التعديلات المُسقَطة بفعل كبح التكرار خلال 60 ثانية أي أثر على الإطلاق، وتُفقَد حالة الموقع المحذوف قبل حذفه.
connection.*GET /connected-accounts وGET /connected-accounts/{connectedAccountId}/details للاتصال وقطع الاتصال وسلامة المصادقة. بالنسبة إلى زوج أحداث التحقّق لدى Google، اقرأ googleVerificationStatus { status, message } من أي قراءة لموقع.كل ساعة؛ وكل بضع دقائق أثناء إجراء التحقّق.failure_type (rejected مقابل transient) والسلسلة النصية reason. لا يظهر تذبذب الاتصال: فالقراءة تُظهر الاتصال الذي انقطع ثم عاد على أنه متصل.
interaction.*GET /locations/{locationId}/reviews لكل موقع، وGET /rollup_interactions عبر المواقع، وGET /reviewDetails حسب id.كل 15 دقيقة للتقييمات الجديدة، ويوميًا لفحص أوسع نطاقًا.وقوع تغيير ما. لا تتضمن حقول التفاعل الموثَّقة وقت آخر تحديث، لذا يبدو التقييم المعدَّل مطابقًا تمامًا لتقييم سبق أن خزّنته: قارن الحقول التي تحتفظ بها.
local_post.*GET /locations/{locationId}/posts، وGET /locations/{locationId}/bulk-posts، وGET /posts/{postId} للحصول على حالة النشر الحالية لكل موقع.كل ساعة أثناء تنفيذ عمليات نشر المنشورات.rejection_reason كما ورد عند التسليم، وتسلسل الحالات التي مرّ بها عنصر المنشور. تمنحك القراءات الحالة الحالية، لا الانتقالات بين الحالات.
review_analytics.*GET /locations/{locationId}/review-analytics-overview باستخدام startDate وendDate الخاصين بالفترة.يوميًا بعد نافذة التجميع، وأسبوعيًا لمجموعة الأحداث الأسبوعية.الأرقام كما كانت وقت اللقطة. يُعاد حساب الملخص عند القراءة، لذا يؤدي تقييم يصل متأخرًا أو يُحذَف إلى اختلافه عما احتوته اللقطة.

نتائج التسليم

تُسجَّل كل محاولة مع حالتها، حتى إن لم يُرسَل فيها أي طلب. هذه هي السلاسل النصية كما تُسجَّل تمامًا.

النتائج الأربع والسلاسل النصية للحالة التي تسجّلها. تعني حالتا «محظور» و«تجاوز حد المعدل» أنه لم يُرسَل أي طلب HTTP على الإطلاق، ولذلك من المفيد تمييزهما عن الفشل الفعلي.
الحالةالنتيجةالمعنىالإجراء المطلوب
successتم التسليمأعادت نقطة النهاية لديك 2xx.لا يلزم أي إجراء.
رمز HTTP كسلسلة نصية، e.g. "404"، "500"فشلأعادت نقطة النهاية لديك ذلك الرمز الذي يقع خارج نطاق 2xx. يندرج 3xx هنا أيضًا، لأن عمليات إعادة التوجيه لا تُتَّبع.تحقّق من المسار وسلسلة عمليات إعادة التوجيه ومعدل الأخطاء في معالج الطلبات لديك.
999فشلوصلت استجابة لا تحتوي على رمز حالة صالح للاستخدام.يكون السبب عادةً خادمًا وسيطًا أو موازن أحمال أمام نقطة النهاية لديك.
timeoutفشللم تصل استجابة ضمن المهلة المحددة البالغة 5 ثوانٍ.انقل العمل إلى خارج مسار معالجة الطلب وأكّد الاستلام في وقت أقصر.
network_errorفشلتعذّر تحليل اسم المضيف أو الوصول إليه.تحقّق من DNS ومن إمكانية تحليل اسم المضيف عبر الإنترنت العام.
errorفشلنتج عن المحاولة خطأ غير المذكور أعلاه، مثل فشل مصافحة TLS.تحقّق من سلسلة الشهادات وسجلات الخادم لديك.
blocked_urlمحظورلم يجتز عنوان URL فحص الأمان عند محاولة الاتصال: لا يستخدم HTTPS، أو لا يحتوي على مضيف، أو جرى تحليله إلى عنوان خاص أو عنوان استرجاع محلي أو عنوان محلي للرابط أو عنوان بيانات وصفية.وجّه عنوان URL إلى مضيف HTTPS متاح عبر الإنترنت العام. لاحظ أن هذا الفحص يُعاد عند كل عملية تسليم، لذا قد يؤدي تغيير DNS إلى بدء حظر عنوان URL كان يعمل سابقًا.
endpoint_unverifiedمحظوريحتوي الحساب على سر توقيع، لكن نقطة النهاية لم تجتز التحقّق، أو أُعيد تعيين حالة التحقّق.أعِد تشغيل التحقّق من نقطة النهاية من لوحة التحكم.
rate_limitedتجاوز حد المعدلتم تجاوز الحد الأقصى لكل دقيقة أو لكل يوم. أُسقِط الحدث.قلّل حجم الإرسال أو انتقل إلى خطة ذات حد أقصى أعلى. راجع الخطط والحدود.

قائمة تحقّق للطرف المستقبِل

  • تحقّق من التوقيع في كل طلب؛ وأعِد 401 ولا تعالج أي شيء عند عدم التطابق.
  • احسب التجزئة من بايتات جسم الطلب الخام، ولا تحسبها أبدًا من كائن أُعيد تسلسله.
  • أعِد 2xx خلال مدة تقل بكثير عن 5 ثوانٍ؛ وأضِف العمل إلى قائمة الانتظار.
  • استخدم المعرّفات داخل data مفاتيح لكل عملية كتابة ينفّذها المعالج حتى ينتج عن التكرار النتيجة نفسها، لكن لا تعتبر تكرار المعرّف أبدًا دليلاً على عدم وجود عمل يجب تنفيذه.
  • لا تستنتج الترتيب من ترتيب الوصول، ولا ترتّب حسب timestamp.
  • وحّد صيغة location_id عند الاستلام بتحويله إلى سلسلة نصية.
  • تجاهل قيم event التي لا تتعرّف عليها، وأعِد 200 رغم ذلك.
  • اجعل عنوان URL الخاص بالـ webhooks عنوانًا نهائيًا بلا عمليات إعادة توجيه.
  • طابِق البيانات دوريًا مع REST API؛ فلا توجد إعادة محاولة لتدارك ما فات.