ويب هوك

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

الأحداث

الحدثمتى يُرسل
order.placedأكمل الزبون عملية الشراء. أول لحظة يصبح فيها الطلب موجوداً.
order.state_changedانتقل الطلب بين الحالات (مثل الشحن أو الإلغاء). يحمل الحالة السابقة والحالية.
product.createdتمت إضافة منتج.
product.updatedتم تعديل منتج.
product.deletedتم حذف منتج.
customer.createdتم إنشاء حساب زبون في هذا المتجر.
customer.updatedتم تعديل بيانات زبون.
return.requestedفتح الزبون طلب إرجاع. رقم الهاتف غير مُرسل عمداً.
return.status_changedقام التاجر بتحديث حالة الإرجاع (قبول، استرجاع، إغلاق…).
ticket.repliedتم الرد على تذكرة دعم. يخبرك أن رداً حدث، لا بمحتواه.
shop.plan_changedتغيّرت خطة اشتراك المتجر. تحمل الخطة السابقة والجديدة.
shipment.createdتم تسليم طلب لجهة توصيل. رقم هاتف الزبون غير مُرسل عمداً.
shipment.status_changedتغيّرت حالة الشحنة (قيد التوصيل، تم التسليم، مرفوضة…). تحمل الحالة السابقة والحالية.

شكل الحمولة

كل عملية إرسال لها نفس الشكل الخارجي. الحقل id فريد لكل عملية — استخدمه كمفتاح لمنع التكرار، لأن إعادة المحاولة ترسل نفس المعرّف.

{
  "id": "4821",
  "event": "order.placed",
  "createdAt": "2026-08-09T18:20:11.004Z",
  "shop": { "slug": "aleppo-textiles", "token": "aleppo-textiles-token" },
  "data": { "code": "MS2408-0042", "totalWithTax": 185000, "currencyCode": "SYP" }
}

التحقق من التوقيع

التوقيع هو sha256=<hmac بصيغة hex> على النص "<الطابع الزمني>.<الجسم الخام>"، بمفتاح سر الاشتراك. وقّع الجسم الخام — لا كائناً أعدت تسلسله، إذ سيختلف ترتيب المفاتيح والمسافات.

الترويسةالمعنى
x-ms-signaturesha256=<hmac بصيغة hex>
x-ms-timestampميلي ثانية منذ الحقبة؛ جزء من النص الموقّع
x-ms-eventاسم الحدث، لتوجيهه قبل تحليل الجسم
x-ms-delivery-idنفس معرّف الحمولة — مفتاح منع التكرار
// Node — verify an eMatjarak webhook
import crypto from 'node:crypto';

function verify(rawBody, headers, secret) {
  const signature = headers['x-ms-signature'];
  const timestamp = headers['x-ms-timestamp'];
  if (!signature || !timestamp) return false;

  // Reject anything older than five minutes: a signature over the body alone
  // could be replayed forever, which is why the timestamp is signed too.
  if (Math.abs(Date.now() - Number(timestamp)) > 5 * 60 * 1000) return false;

  const expected =
    'sha256=' +
    crypto.createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest('hex');

  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}
<?php
// PHP — verify an eMatjarak webhook
function ms_verify(string $rawBody, array $headers, string $secret): bool {
    $signature = $headers['x-ms-signature'] ?? '';
    $timestamp = $headers['x-ms-timestamp'] ?? '';
    if ($signature === '' || $timestamp === '') return false;

    if (abs(time() * 1000 - (int) $timestamp) > 5 * 60 * 1000) return false;

    $expected = 'sha256=' . hash_hmac('sha256', $timestamp . '.' . $rawBody, $secret);
    return hash_equals($expected, $signature);
}
قارن بزمن ثابت (timingSafeEqual أو hash_equals)، وارفض الطوابع الزمنية القديمة. التحقق الذي يسرّب فروق التوقيت أو يتجاهل الطابع الزمني ليس تحققاً حقيقياً.

إعادة المحاولة والفشل

  • أي استجابة خارج نطاق 2xx أو انتهاء المهلة أو خطأ اتصال يُعدّ فشلاً وتُعاد المحاولة بتباعد متزايد.
  • أعد 2xx فور تخزين الحدث، ثم نفّذ عملك بعد ذلك — المعالج البطيء يبدو كفشل ويجلب لك نسخاً مكررة.
  • الرابط الذي يستمر بالفشل يُعطَّل تلقائياً، ويُعلَم التاجر بذلك.

متطلبات المستقبِل

  • HTTPS وعنوان عام. العناوين الخاصة والمحلية مرفوضة عند حفظ الرابط وعند كل إرسال.
  • لا إعادة توجيه. رمز 302 لا يُتبع، بل يُعدّ فشلاً.
  • استجب ضمن مهلة الإرسال.