تدوير عنوان Webhook في سلة بأمان هو ترحيل حالة، وليس تعديل DNS فقط. احتفظ بجرد موثوق لاشتراكات كل متجر، وشغّل المستقبل الجديد قبل أي تغيير، وأدخل تسليمات المسارين القديم والجديد إلى صندوق وارد واحد يدعم Idempotency، وقارن صحة التسليم، ثم احذف الاشتراك القديم بواسطة معرّفه تحديدًا وشغّل مطابقة عبر API قبل إغلاق مسار الرجوع. يفيد هذا التصميم تطبيقات سلة التي تنقل النطاق أوالمنطقة أوالبوابة أوالبنية المستقبلة للأحداث من دون فقد صامت لأحداث الطلبات والمنتجات والعملاء.
هذا دليل هندسي عملي مبني على Merchant API ووثائق الشركاء الرسمية في سلة، وقد تم التحقق منها في 7 أكتوبر 2026. عُدلت صفحات Register وUpdate Webhook الحالية في 6 أكتوبر 2026. توثق سلة الـEndpoints والصلاحيات وسلوك النسخ والحذف، أما Desired-state Controller وبوابات التحويل ونافذة التعافي أدناه فهي توصيات هندسية، وليست ادعاءً بأن سلة تضمن Exactly-once Delivery.
عامل الاشتراكات كحالة مُدارة
يقبل Register Webhook الحدث والعنوان والاسم والنسخة وRule اختيارية وHeaders مخصصة تحت صلاحية `webhooks.read_write`. وتذكر سلة أن الاشتراكات الجديدة التي تستخدم URL نفسها قد تحدّث الأحداث أوتعيد Webhook قديمة إذا كانت موجودة. كما تبدأ التسجيلات الجديدة بالنسخة 2 افتراضيًا ما لم تُطلب النسخة 1 صراحة. لذلك لا تصلح POST عمياء ومتكررة كخطة ترحيل؛ فقد تغيّر عملية تبدو كإنشاء حالة قائمة أوتعيدها بدل إنشاء مسار مستقل ثانٍ.
خزّن سجل اشتراك محليًا لكل متجر وحدث مقصود. احفظ على الأقل معرّف الاشتراك في سلة ومعرّف المتجر والحدث وURL ونسخة Payload وConditional Rule ومرجع السر والجيل المطلوب والجيل المرصود وحالة التفعيل ووقت آخر تحقق. لا تحفظ سر Header مخصصة في Logs خام. المعرّف البعيد بيانات تشغيلية مهمة، وهو المقبض الأضيق لتحديث أوحذف محدد.
عرّف Desired State في الكود أوجدول إعدادات مضبوط. يستطيع Controller قراءة الحالة البعيدة ومقارنتها بالجيل المطلوب واقتراح التغييرات. اجعل الاكتشاف Read-only بصلاحية `webhooks.read`، واقصر `webhooks.read_write` على Reconciler يطبق تغييرات مراجعة. وإذا شاركت مئات المتاجر التطبيق نفسه فقسّم العمل لكل متجر واحترم ميزانية API نفسها التي يستخدمها باقي التكامل.
subscription_key = store_id + event + logical_consumer
desired = { url, version, rule, secret_ref, generation }
observed = { salla_id, url, version, rule, checked_at }
action = create | update | verify | retire | no_changeأنجز الجرد قبل تغيير المسار
استدعِ List Active Webhooks لكل متجر مثبت واحفظ Snapshot منزوعة الأسرار. تتضمن الاستجابة الموثقة ID والاسم والحدث والنسخة وRule والنوع وURL وHeaders مع Pagination. مرّ على الصفحات كلها؛ الصفحة الأولى ليست جردًا كاملًا عندما يملك التاجر اشتراكات عديدة. قارن بالمعرّف البعيد أولًا، ثم بالحدث وURL بعد التطبيع. قد يكون تكرار الحدث مقصودًا إذا خدم مستهلكان مساري عمل مختلفين.
صنّف كل صف إلى Managed أوForeign أوAmbiguous. الاشتراك المدار مملوك للتطبيق ويطابق سجلًا محليًا. وقد يخص الاشتراك الأجنبي تطبيقًا آخر أوWorkflow للتاجر فلا تعدله. أما الصف الملتبس فيشارك URL أوالاسم ولا يثبت الملكية؛ توقف وراجعه بدل التخمين. هذه النقطة مهمة لأن Endpoint الحذف في سلة تستطيع الحذف بواسطة URL، وقد يشترك في العنوان أكثر من حدث.
الجرد هو أيضًا Rollback Manifest. سجّل ID القديم وURL والنسخة وRule وجيل السر قبل التحويل. لا تنسخ قيم Headers المعادة إلى التذاكر أوTraces. يجب أن يعيد Restore مضبوط عقد المستقبل القديم دون كشف Credentials.
أنشئ حد قبول دائمًا واحدًا
انشر Endpoint الجديدة قبل تسجيلها. يجب أن تتحقق من Token أوSignature الخاصة بسلة على جسم الطلب غير المعدل، وترفض المرور غير الموثوق، وتضيف صفًا دائمًا إلى Inbox ثم ترد بسرعة. يشرح دليل Webhooks في سلة استراتيجيات Token وSignature، ويوجه التكامل إلى رفض الطلبات المشبوهة التي لا يمكن توثيقها. لا تنفذ استدعاء ERP أوحجز شحنة أوإشعار عميل داخل مسار الطلب.
يجب أن يكتب المستقبلان القديم والجديد في Logical Inbox نفسها، أوفي صندوقين متماثلين يستخدمان Idempotency Store مشتركة. يمكن أن يجمع Business Key ثابت المتجر الموثق والحدث وهوية المورد ونسخة المصدر أووقته. وإذا لم تحمل Payload معرّف تسليم فريدًا مناسبًا، فاحتفظ ببصمة محدودة للحمولة كإشارة ثانوية للتكرار واجعل كل Side Effect في Worker قابلة للتكرار المستقل. البصمة لا تثبت تطابق حدثين تجاريين؛ لذلك احتفظ ببيانات الحدث اللازمة للمراجعة.
const raw = await readRawBody(request);
const route = resolveReceiverGeneration(request.url);
verifySallaWebhook(raw, request.headers, route.secretRef);
const event = parseAfterVerification(raw);
const businessKey = deriveStableKey({
store: event.merchant,
type: event.event,
resource: sourceResourceId(event),
sourceVersion: sourceVersionOrTime(event)
});
await inbox.insertOrObserve({ businessKey, route, payload: encrypt(raw) });
return accepted();يعتمد المفتاح الدقيق على عقد الحدث. وإذا أمكن تحديث الطلب عدة مرات داخل دقة Timestamp واحدة، فأضف نسخة خاصة بالحدث أوبصمة مضبوطة واجعل Worker يجلب الحالة الحالية الموثوقة قبل تطبيق Transition. لا تعد بتنفيذ Exactly-once لمجرد وجود Unique Constraint في Receiver.
نفّذ التحويل عبر ست بوابات
أولًا، انشر العنوان الجديد وافحص Health وTLS وإعداد السر. ثانيًا، سجّل URL الجديدة لعينة Canary من المتاجر ولمجموعة الأحداث المطلوبة نفسها. استخدم عنوانًا مختلفًا إذا أردت التسليم المتوازي؛ فوثائق سلة تقول إن التسجيل بالعنوان نفسه قد يحدّث اشتراكًا قائمًا أويعيده. ثالثًا، أكد الصفوف الجديدة عبر List Active Webhooks بدل الثقة في Mutation Response وحدها.
رابعًا، راقب المسارين عبر Inbox الدائمة. قارن التسليمات الموثقة ومزيج الأحداث وزمن الرد ونسبة رفض التوقيع وعمر الطابور ونجاح المعالجة. توقع Duplicates وأثبت أنها غير مؤذية. لا تشترط تطابقًا مثاليًا للعدد في نافذة صغيرة؛ فقد تغير التوقيت وإعادة المحاولة وConditional Rules المشاهدات. قارن لكل متجر وحدث ضمن نافذة استقرت.
خامسًا، أوقف الاشتراك القديم عبر Deactivate Webhook باستخدام ID الدقيقة. تدعم سلة الحذف بواسطة URL أيضًا، وتحذر من أن استخدام URL يحذف كل Webhooks المسجلة عليها. يصلح حذف URL فقط عندما يثبت الجرد أن جميع الاشتراكات على العنوان ستتقاعد عمدًا. أما التدوير المعتاد فيحتاج حذفًا بالمعرّف لتقليص نطاق الضرر. الاستجابة الناجحة الموثقة HTTP 202؛ لذلك اتبعها بقراءة جديدة للقائمة النشطة، واعتبر الغياب المرصود هو Postcondition.
سادسًا، أبقِ المستقبل القديم حيًا لكن غير مسؤول عن الآثار الجانبية خلال Cooldown محدودة. يواصل Authentication وتسجيل الطلبات المتأخرة دون تكرار التأثيرات. شغّل Reconciliation على موارد سلة ذات الصلة من Checkpoint قبل التحويل حتى Safe Horizon بعده. بعد ذلك فقط أزل Route القديمة والسر والبنية.
افصل تدوير العنوان والسر ونسخة Payload
تغيير URL وسر توثيق Webhook وPayload Version في اللحظة نفسها يخلق ثلاثة مصادر محتملة للفشل. استخدم أجيالًا مستقلة. انقل المرور إلى العنوان الجديد مع قبول مرجعي السر الحالي والقادم لفترة قصيرة مضبوطة. وبعد ثبوت صحة المسارين دوّر السر. ولا ترق نسخة Payload قبل أن تقبل Parsers وFixtures المخططين معًا.
يحدّث Update Webhook اشتراكًا قائمًا بواسطة ID، ويدعم الاسم والنسخة وRule وHeaders. استخدمه لتغيير In-place منخفض المخاطر عندما لا تحتاج إلى تسليم متوازٍ ويكون الرجوع مباشرًا. واستخدم URL جديدة ومستقبلين متداخلين عندما تتغير البنية أوالمنطقة أوالحد الأمني وتحتاج دليلًا مرصودًا قبل التحويل.
احتفظ بأجيال الأسرار في Managed Secret Store. يحمل جدول الاشتراكات مرجعًا وبصمة لا القيمة. يمكن للتحقق قبول جيلين مؤقتًا، لكن الإعداد الصادر يجب أن يحدد جيلًا فعالًا واحدًا. أزل السر القديم بعد Cooldown وأثبت أن لا Process مستقبلًا ما زال يحمّله.
اجعل الرجوع صريحًا
Rollback ليس «أعد DNS». إنه انتقال مضبوط له Subscription ID وجيل Receiver معروفان. فعّل الرجوع إذا فشلت بوابات Authentication أوDurable Acceptance أوLatency أوEvent Mix أوReconciliation. أعد تفعيل Desired State القديمة أوتسجيلها من Manifest، وتحقق منها في List Active Webhooks، واترك المسار الجديد يجمع الأدلة دون تطبيق آثار حتى فهم الحادث.
إذا حُذف الاشتراك القديم بواسطة URL واختفت أحداث عدة، فاستعد من الجرد الكامل بدل التخمين من آخر تنبيه. وإذا كانت نتيجة Mutation مجهولة بسبب Timeout، فاعرض القائمة النشطة قبل إعادة المحاولة؛ فقد يخفي سلوك Restore أوUpdate للعنوان نفسه حقيقة الحالة البعيدة. يجب أن تتبع كل Control-plane Mutation قراءة للحالة المرصودة.
احفظ Audit Record لهوية المنفذ أوJob والمتجر والمعرّفات القديمة والجديدة والعقدين السابق والمطلوب واستجابة التغيير وPostcondition المرصودة والأوقات. احذف Tokens وSignatures وCustom Headers وبيانات العملاء من السجل. يساعد ذلك على فصل فجوة تسليم لدى المزود عن خطأ إعداد داخل التطبيق.
ألغِ تثبيت التطبيق لا العنوان فقط
تتضمن وثائق App Events في سلة حدث `app.uninstalled` ضمن Lifecycle. عامله كانتقال عالي الأولوية: ضع Installation في حالة Revoked، وأوقف Jobs الجديدة، وأبطل Credentials المخزنة، وامنع Refresh، وابدأ حذفًا يراعي Retention. يجب أن تعيد مهمة أُنشئت قبل الإلغاء فحص حالة التثبيت قبل استدعاء سلة أوتنفيذ أثر للتاجر.
لا تعتمد على نجاح تنظيف نهائي عبر Merchant API بعد الإلغاء؛ فقد لا تبقى Credentials صالحة. احتفظ محليًا ببيانات ملكية تكفي لإيقاف المعالجة دون Remote Call. يظل جرد الاشتراكات مفيدًا للتدقيق، لكن Local Authorization State هي حد الأمان الفوري. وافصل Uninstall عن فشل التسليم المؤقت حتى لا يسبب Outage حذفًا غير مقصود.
هجرة النطاق ليست Uninstall. حافظ على هوية تثبيت التاجر بينما تتغير أجيال المستقبل. لا تنشئ Tenant ثانية لأن Callback Hostname تغيرت. يملك التثبيت الاشتراكات، أما Endpoint فهي بنية تحتية ذات نسخ تحته.
أفضل الممارسات والأنماط المضادة
استخدم Desired-state Controller عندما تشترك متاجر كثيرة في عقد الأحداث نفسه، أوتعبر الهجرة مناطق وبوابات، أوتحتاج Audit وRollback. واستخدم In-place Update لتغيير Rule أوMetadata منخفض المخاطر فقط عندما تثبت ID الحالية وتقبل غياب Overlap قصيرًا وتملك رجوعًا مختبرًا. واستخدم Reverse Proxy أوStable Ingress Hostname عندما تتغير البنية كثيرًا؛ فقد يجنب تبديل Upstream داخلي تغيير الاشتراك البعيد كله.
تجنب Blind Create Loops، وحذف URL دون إثبات الملكية، والأسرار في Logs، والآثار داخل Receiver، والمقارنة بالعدد فقط، وإغلاق المسار القديم فور Response 202. ولا تسمح لوكيل AI باستدعاء أدوات عامة لتغيير Webhooks مع Merchant Tokens. اعرض عمليات ضيقة مثل `planRotation` و`registerCanary` و`verifyInventory` و`retireById`، وأبق Approval حول الخطوات المدمرة.
القاعدة التنفيذية بسيطة: اجرد قبل Mutation، وأنشئ Overlap بعنوان مختلف، ووثق قبل Parsing، واحفظ قبل Acknowledgement، وامنع التكرار عند الحد التجاري، وتحقق من Postconditions البعيدة، وطابق بعد التحويل. للتصاميم القريبة راجع دليل تعافي Webhooks سلة وزد وConditional Webhooks في سلة وخدمات تكاملات API الخلفية.
المراجع الرسمية
تدعم هذه المراجع سلوك الأدوات المذكورة. الأمثلة وقرارات التصميم توضيحية، ويجب تكييفها مع متطلبات المشروع وإصداراته.
إعداد: Noor Yasser
تعمل على تحدٍ تقني مشابه؟
أساعد الفرق على تحويل القرار المعماري إلى نطاق واضح وتنفيذ يمكن تشغيله ومراجعته بثقة.




