الأحداث
| الحدث | متى يُرسل |
|---|---|
| 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-signature | sha256=<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 لا يُتبع، بل يُعدّ فشلاً.
- استجب ضمن مهلة الإرسال.