كيف يمكنني منع تنفيذ سير العمل المكررة عند تفعيل webhook عدة مرات؟

كيف يمكنني منع عمليات سير العمل المكررة عند تشغيل webhook عدة مرات؟

ما رسالة الخطأ (إن وجدت)؟

Webhook Set HTTP Request Airtable أنا على علم بأن Stripe يعيد محاولة الطلبات الفاشلة، لكن حتى الطلبات الناجحة تبدو أحيانًا أنها تصل مرتين. كيف يمكنني التأكد من معالجة كل حدث مرة واحدة فقط؟

يرجى مشاركة سير عملك

(حدد العقد على لوحتك الرسومية واستخدم اختصارات لوحة المفاتيح CMD+C/CTRL+C و CMD+V/CTRL+V لنسخ ولصق سير العمل.)

شارك الإخراج الذي تعيده العقدة الأخيرة

أستخدم عقدة Webhook تستقبل أحداثًا من Stripe. في بعض الأحيان، يتم تسليم نفس الحدث أكثر من مرة، مما يؤدي إلى معالجة سير عملي لطلبات مكررة

معلومات حول إعداد n8n الخاص بك

  • إصدار n8n:1.123
  • قاعدة البيانات (الافتراضي: SQLite): Postgresql
  • إعداد n8n EXECUTIONS_PROCESS (الافتراضي: own, main):
  • تشغيل n8n عبر (Docker, npm, n8n cloud, تطبيق سطح المكتب):
  • نظام التشغيل:

مرحباً @Fatoki_Alfred

كل حدث في Stripe يحتوي على معرّف فريد (مثل evt_1Nabc...). لضمان معالجة كل حدث مرة واحدة فقط، يجب عليك تتبع هذه المعرّفات في جدول مخصص يسمى “الأحداث المعالجة” في قاعدة بيانات Postgres الخاصة بك.

مرحبًا @Fatoki_Alfred
لدى n8n عقدة Remove Duplicates مدمجة تقوم بهذا دون الحاجة إلى جدول مخصص أو استعلام. ضعها مباشرة بعد عقدة Webhook وعيّن Operation إلى “Remove Items Processed in Previous Executions”، و Keep Items Where إلى “Value Is New”، و Value to Dedupe On إلى معرّف حدث Stripe:

{{ $json.body.id }}

يحتفظ n8n بسجل المعرّفات المرئية في قاعدة بيانات Postgres الخاصة به، لذا يستمر عبر عمليات إعادة التشغيل، وأي إعادة تسليم مكررة يتم حذفها قبل أن تصل إلى خطوات الطلب الخاصة بك.

تكملة للإجابات أعلاه: النقطة التي عادة ما تفوت هي حالة التنافس (race condition). إذا قام Stripe بتسليم نفس evt_id بشكل متوازٍ، قد يمر سير عملين عبر فحص إزالة التكرار قبل أن يقوم أي منهما بتسجيل المعرّف — وكلاهما يستمر. عقدة Remove Duplicates تحل الحالة المسلسلة، لكنها ليست ذرية تحت التزامن.

للحماية الحقيقية، قم بإزالة التكرار في قاعدة البيانات نفسها: أنشئ العمود بقيد UNIQUE (مثل stripe_event_id TEXT UNIQUE) وبعد الـ Webhook مباشرة، عقدة Postgres بـ INSERT … ON CONFLICT (stripe_event_id) DO NOTHING RETURNING id. إذا كانت النتيجة المرجعة فارغة، فهي إعادة تسليم مكررة → توقف التدفق هناك (استخدم IF للتحقق مما إذا كان هناك صف مُرجع). قاعدة البيانات تضمن الذرية، لذلك حتى مع تسليمين متزامنين: واحد فقط يفوز بـ INSERT.

شيئان إضافيان يقللان المشكلة كثيراً من المصدر: (1) رد 200 إلى Stripe في أسرع وقت ممكن (استخدم Webhook في وضع Respond Immediately أو عقدة Respond to Webhook في البداية) — انتهاء المهلة الزمنية يجعل Stripe يحاول مرة أخرى وهنا حيث تنشأ معظم “التكرارات”; و(2) معالجة الطلب فقط بعد نجاح INSERT التحكم. بهذه الطريقة معرّف الحدث هو مفتاح عدم تكرار العملية الحقيقي، وليس منطق الطلب أبداً يعمل مرتين.

مرحباً @Fatoki_Alfred

تعيد Stripe محاولة تسليم webhooks بشكل مقصود، لذا الأحداث المكررة متوقعة. الطريقة الموصى بها هي جعل سير العمل الخاص بك idempotent بدلاً من افتراض أن كل webhook فريد.
الحل الأبسط هو تخزين معرّف الحدث event.id قبل المعالجة. في بداية سير العمل:

  • استخرج event.id.
  • ابحث في قاعدة البيانات الخاصة بك (أو Data Store) عن هذا المعرّف.
    إذا كان موجوداً بالفعل، أوقف سير العمل.
    وإلا احفظ المعرّف وتابع المعالجة.
    عادة ما يكون استخدام عقدة IF قبل منطق عملك كافياً.
    إذا كنت تكتب إلى Airtable أو قاعدة بيانات أخرى، فكر في جعل event.id حقلاً فريداً بحيث يتم رفض المكررات تلقائياً.

تعامل مع خطافات Stripe على أنها توصيل “مرة واحدة على الأقل” واجعل سير العمل idempotent. استخدم معرّف حدث Stripe كمفتاح idempotency. في بداية سير العمل، تحقق من توقيع الخطاف وحاول إدراج معرّف الحدث هذا في جدول PostgreSQL بقيد فريد. تابع فقط إذا نجح الإدراج. إذا كان المعرّف موجوداً بالفعل، فأرجع استجابة ناجحة وتوقف دون إنشاء الطلب مرة أخرى.

يمكن لجدول بسيط أن يحتوي على event_id كمفتاح أساسي بالإضافة إلى status و received_at و completed_at. في عقدة Postgres استخدم إدراجاً مع ON CONFLICT DO NOTHING وأرجع ما إذا تم إدراج صف. أرسل هذه النتيجة إلى عقدة IF. يعالج الفرع الصحيح الطلب ويخرج الفرع الخاطئ. يهم قيد قاعدة البيانات لأن نسختين يمكن أن تصلا قريبة بما يكفي بحيث يسمح فحص البحث-ثم-الإدراج المنفصل لكليهما بالمرور.

حدد السجل كـ"قيد المعالجة" عند المطالبة به واكتمل فقط بعد نجاح الطلب. قرر كيفية إعادة محاولة السجلات الفاشلة بحيث لا يؤدي الخطأ المؤقت إلى قمع الحدث بشكل دائم. كما قم بتمرير معرّف حدث Stripe إلى الأنظمة المتنقلة باعتباره مفتاح idempotency الخاص بهم حيث يكون مدعوماً. لا تقم بإلغاء التكرار حسب العميل أو المبلغ أو الطابع الزمني لأن عمليات دفع منفصلة شرعية يمكن أن تشترك في تلك القيم.

أنشئ مفتاح إدراجة (idempotency key) من معرّف حدث مستقر وخزّنه قبل تشغيل العقد المكلفة. إذا وصل المفتاح نفسه مرة أخرى، فارجع في وقت مبكر وعيّن صلاحية انتهاء تتطابق مع المدة التي قد يحاول فيها المُرسل إعادة الإرسال.