Webhook أم API؟ كيف تبني تكاملاً موثوقاً لمتجرك

أنبوبان مضيئان يتبادلان حزم البيانات يرمزان إلى الفرق بين Webhook و API في تكامل المتاجر

أول سؤال بيتسأل في أي مشروع ربط متجر بنظام محاسبة أو شحن: «نستخدم API ولا Webhook؟». والحقيقة إن السؤال نفسه غلط شوية. الاتنين مش بدائل لبعض، كل واحد بيغطي نص المشكلة، والتكامل اللي بيعيش في الإنتاج غالباً بيستخدم الاتنين مع بعض.

نصفان لمشكلة واحدة

  • واجهة برمجة التطبيقات (API): طريقة نظامك ليطلب بيانات من نظام آخر أو يأمره بتنفيذ شيء. أنت من يقرر متى تسأل.
  • الويب هوك (Webhook): طريقة النظام الآخر ليخبرك أن شيئاً حدث للتو. المنصة هي من تقرر متى تخبرك.
API (سحب)Webhook (دفع)
من يبدأ؟نظامكالمنصة الأخرى
———
متى تصل البيانات؟عندما تسأل، يدوياً أو بجدولعادةً خلال ثوانٍ من الحدث
ماذا يفعل؟قراءة وكتابةإشعار فقط، والتنفيذ عبر الـ API
يتفوق فيالإنشاء والتعديل والبحث والتعويض بعد الانقطاعالتفاعل مع الأحداث: دفع، طلب جديد، مخزون
عطله المعتاداستطلاع كثير يستهلك الحد، أو قليل فتتقادم البياناتإشعارات مفقودة أو متأخرة أو مكررة أو مزورة
ما تحتاج لبنائهمصادقة، تقسيم الصفحات، التعامل مع الحدودرابط HTTPS عام، تحقق من التوقيع، منع تكرار، طابور

يسمي البعض الـ Webhook «API معكوساً»: نفس المكونات (HTTP وJSON) في الاتجاه المعاكس. لكنه نادراً ما يغني عن الـ API، فهو يخبرك أن شيئاً حدث، أما قراءة السجل كاملاً أو تعديل أي شيء فيمر غالباً عبر الـ API.

مثال حقيقي: طلب مدفوع ← برنامج المحاسبة

لنفترض أن كل طلب مدفوع في Shopify يجب أن يظهر كعملية بيع في برنامج المحاسبة. التصميم الذي يصمد في الإنتاج يمر بمسارين:

المسار الفوري:

  1. يصل Webhook «تم دفع الطلب» إلى رابطك، فتتحقق من التوقيع على النص الخام، وترد بـ 401 إن كان غير صالح.
  2. تسجّل معرّف الإرسال، وإن كان مكرراً ترد بـ 200 ولا تفعل شيئاً.
  3. تضع مهمة في الطابور وترد بـ 200 فوراً.
  4. يقرأ العامل (Worker) الطلب كاملاً عبر Admin API إن لم تكفِ بيانات الإشعار.
  5. يتحقق: هل أُنشئت عملية بيع لهذا الطلب؟ إن نعم يتخطاه، وإلا ينشئها عبر API المحاسبة.
  6. عند خطأ مؤقت يعيد المحاولة بتأخير متزايد، وعند خطأ دائم يتوقف وينبّه.

شبكة الأمان (كل ساعة أو يومياً):

  1. مهمة مجدولة تسأل الـ API عن «الطلبات المحدَّثة منذ آخر تشغيل ناقص فترة تداخل»، وتسلّم كل طلب للعامل نفسه، فيتخطى فحص الخطوة 5 ما عالجه الـ Webhook سابقاً.

السرعة تأتي من الـ Webhook، والتحكم والتعافي يأتيان من الـ API. التكاملات تنكسر عادةً حيث اعتمد الفريق على واحد فقط.

خمس قواعد للـ Webhook

1. تحقق من كل إشعار قبل أن تثق به

رابطك عام، وأي شخص يستطيع إرسال طلب إليه. Shopify توقّع كل إشعار بـ رمز التحقق من الرسالة (HMAC): الترويسة X-Shopify-Hmac-Sha256 تحمل قيمة HMAC-SHA256 بترميز base64 للنص الخام للطلب، مفتاحها هو الـ client secret لتطبيقك. أعد حسابها وارفض ما لا يطابق:

const crypto = require('node:crypto');

function isValidShopifyWebhook(rawBody, hmacHeader, secret) {
  if (typeof hmacHeader !== 'string' || hmacHeader.length === 0) return false;
  const expected = Buffer.from(
    crypto.createHmac('sha256', secret).update(rawBody).digest('base64'),
  );
  const received = Buffer.from(hmacHeader);
  // timingSafeEqual throws when lengths differ, so check that first
  return expected.length === received.length && crypto.timingSafeEqual(expected, received);
}

خطآن يسببان معظم مشكلات «التوقيع لا يطابق أبداً»:

  • حساب التوقيع على JSON أعيد تحويله: إن حلّل إطار العمل (Framework) الـ JSON قبل وصوله إلى دالتك، تتغير المسافات وترتيب المفاتيح فلا يطابق التوقيع. التقط البايتات الخام.
  • المقارنة بـ ===: استخدم مقارنة ثابتة الزمن، crypto.timingSafeEqual في Node أو hmac.compare_digest في Python.

في ووردبريس إلى متجر إلكتروني كامل: منتجات وسلة ودفع وشحن ومخزون وطلبات. تتوسع بإضافات لبوابات الدفع المحلية والشحن، وتمنحك ملكية كاملة لمتجرك…">ووكومرس: الفكرة نفسها تماماً. ووكومرس (WooCommerce) يرسل التوقيع في الترويسة X-WC-Webhook-Signature كقيمة HMAC-SHA256 بترميز base64، مفتاحها هو الـ Secret الذي تحدده عند إنشاء الـ Webhook من ووكومرس ← الإعدادات ← متقدم ← Webhooks. وبوابات الدفع المحلية مثل Paymob ترسل كذلك قيمة HMAC مع إشعارات العمليات، فلا تحدّث حالة أي طلب قبل التحقق منها وفق توثيق البوابة.

2. رد بسرعة وعالج لاحقاً

المنصات تمنحك نافذة قصيرة. Shopify مثلاً توثّق مهلة اتصال ثانية واحدة وخمس ثوانٍ للطلب كاملاً، وتعتبر أي رد خارج نطاق 2xx فشلاً، وتعيد المحاولة حتى 8 مرات خلال 4 ساعات، وقد تحذف الاشتراك إذا استمر الفشل.

لذلك يجب أن يفعل رابطك الحد الأدنى فقط: تحقق، سجّل، ضع في الطابور، ثم رد بـ 2xx. أما استدعاء برنامج المحاسبة أو شركة الشحن مثل Bosta فمكانه العامل.

3. توقّع التكرار والفوضى: اجعل المعالجة غير مكررة

الإشعارات تُسلَّم «مرة على الأقل». قد يصل الحدث نفسه مرتين، وقد تصل الأحداث بترتيب مختلف عن ترتيب وقوعها. الحل المعتاد لتحقيق عدم التكرار (Idempotency) سجل تحجز فيه مفتاحاً قبل أي عمل:

# Pseudocode for the webhook handler
def on_delivery(delivery):
    key = delivery.id  # fallback: topic + resource id + updated_at
    if not ledger.claim(key):   # atomic insert against a unique constraint
        return 200              # duplicate: acknowledge, do nothing
    try:
        enqueue(job)
    except QueueError:
        ledger.release(key)
        return 503
    return 200

مع Shopify، الترويسة الموثقة لمنع تكرار الإشعارات هي X-Shopify-Webhook-Id. تفاصيل تهمك أكثر مما تبدو:

  • السجل مشترك بين كل النسخ: جدول بقيد تفرد (Unique Constraint) أو Redis بالأمر SET key value NX EX <ttl>. المجموعة في الذاكرة تعمل فقط مع عملية واحدة.
  • احتفظ بالمفاتيح مدة تغطي نافذة إعادة الإرسال.
  • لا تفترض الترتيب: قارن updated_at بآخر قيمة خزّنتها وتجاهل التحديثات الأقدم، أو أعد قراءة الحالة الحالية من الـ API.
  • عدم التكرار على مستوى العمل: سجل الإشعارات يمنع تكرار الإرسال فقط. خطوة إنشاء الفاتورة تحتاج حارسها الخاص على مستوى الطلب، مثل قيد تفرد على رقم الطلب في سجلات المزامنة، حتى لا ينجح عاملان في الفحص في اللحظة نفسها.

4. أعد المحاولة بتأخير متزايد، واعرف متى تتوقف

  • خطأ مؤقت (انقطاع شبكة، مهلة، 429، 5xx): أعد المحاولة بتأخير أُسّي مع عشوائية، واحترم الترويسة Retry-After إن وُجدت.
  • خطأ دائم (400، 404، قاعدة عمل): توقف، سجّل، ونبّه شخصاً. إعادة المحاولة لن تفيد.
// "Full jitter": a random delay between 0 and the exponential cap
const delayMs = Math.random() * Math.min(60_000, 500 * 2 ** (attempt - 1));

العشوائية مهمة: بعد انقطاع، آلاف المهام التي تعيد المحاولة بالجدول نفسه تضرب الـ API المتعافي في اللحظة نفسها.

5. لا تعتمد على الـ Webhook وحده: طابق دورياً

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

المطابقة الدورية (Reconciliation) مهمة مجدولة تسأل الـ API عما تغيّر منذ آخر تشغيل مع فترة تداخل، وتسلّم كل نتيجة للعامل نفسه. مع Shopify يعني ذلك استعلام GraphQL Admin API على الطلبات مفلترة بـ updated_at ومقسّمة بالمؤشرات (Cursors).

انتبه: الطلب القادم من الـ API لا يحمل معرّف إشعار، فسجل الإشعارات لن يمنع تكراره. ما يمنع إنشاء عملية بيع ثانية هو فحص مستوى الطلب داخل العامل.

ثلاث قواعد للـ API

  • ابقَ داخل حد الطلبات (Rate Limit): GraphQL Admin API في Shopify يحسب كلفة لكل استعلام من رصيد يتجدد باستمرار حسب خطة المتجر. اطلب الحقول التي تحتاجها فقط، وتراجع عند الرد بـ 429 أو خطأ THROTTLED.
  • المفاتيح على السيرفر فقط: لا تضع رموز الوصول في القالب في Elementor تصميم محفوظ يُعاد استخدامه، مثل رأس الموقع أو التذييل أو صفحة المقالة أو صفحة المنتج. تصممه مرة وتحدد شروط ظهوره، فيُطبَّق على كل…">القالب أو JavaScript الواجهة أو مستودع Git. واطلب أقل الصلاحيات: مهمة المطابقة تحتاج read_orders لا صلاحية كتابة شاملة.
  • ثبّت الإصدار وخطط للترقية: الـ API والإشعارات لها إصدارات، وShopify ترسل الإصدار في X-Shopify-API-Version. ثبّت إصداراً صريحاً واختبر الجديد قبل التحويل.

جدول الأعطال وما يحميك منها

ما الذي يحدثما يحميك
طلب مزور إلى رابطكالتحقق من HMAC على النص الخام ثم 401
——
الحدث نفسه مرتينسجل عدم تكرار بمفتاح معرّف الإرسال
أحداث بترتيب خاطئمقارنة updated_at أو إعادة القراءة من الـ API
معالجة بطيئة فتعيد المنصة الإرسالرد سريع ومعالجة في طابور
API خارجي متذبذب أو محدودإعادة محاولة بتأخير أُسّي وعشوائية
النظام الخارجي يرفض البياناتمسار الخطأ الدائم: توقف وسجّل ونبّه
رابطك متوقف أطول من نافذة الإعادةالمطابقة الدورية عبر الـ API
العامل تعطل بعد الرد بـ 2xxطابور دائم في قاعدة بيانات، لا في الذاكرة

أيهما تختار؟

  • حدث يجب التفاعل معه خلال ثوانٍ (دفع مؤكد، طلب جديد): Webhook لتسمع به، وAPI لتنفذ.
  • كتابة في نظام آخر: API.
  • عمل دفعي (تصدير ليلي، تقرير، تعويض بيانات): استدعاءات API مجدولة.
  • لا يوجد Webhook للحدث: الاستطلاع الدوري (Polling) للـ API بفاصل معقول داخل الحدود.
  • أموال أو مخزون على المحك: الاثنان معاً، مع مطابقة دورية فوقهما.

الأسئلة الشائعة

س: هل الاستطلاع الدوري للـ API ممارسة سيئة؟
ج: لا. الاستطلاع بجدول معقول داخل حد الطلبات هو طريقة التعويض بعد أي انقطاع، والخيار الوحيد حين لا توفر المنصة Webhooks. قصوره فقط في التفاعل الفوري.

س: هل يمكنني الاستغناء عن الطابور في متجر صغير؟
ج: إن كان رابطك يكتب في قاعدة بياناتك فقط وينتهي قبل المهلة بوقت كافٍ فابدأ بدونه. لكن بمجرد أن يدخل استدعاء API خارجي في مسار الطلب، يصبح ردك رهينة سرعة طرف آخر، وهنا يبدأ التكرار والفقد.

س: لماذا لا يطابق توقيع HMAC أبداً رغم صحة المفتاح؟
ج: غالباً لأنك تحسبه على JSON بعد تحليله وإعادة تحويله. احسبه على النص الخام كما وصل.

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

الخلاصة

الـ Webhook بيقولك إن فيه حاجة حصلت، والـ API هو اللي بتعمل بيه الشغل وبتصلّح بيه اللي فاتك. لو فيه فلوس أو مخزون في النص، استخدم الاتنين، وما تطلعش رابط Webhook للإنتاج قبل ما تراجع الجدول اللي فوق بند بند.

مرجع المقال (بالإنجليزية): Webhook vs API: Choosing the Right Integration Pattern
المقال ده مش ترجمة حرفية: متكيّف للقارئ العربي ومضاف عليه سياق ووردبريس وElementor وJetEngine.

اترك تعليقاً