Webhooks
تدفع الـ Webhooks إشعارات فورية إلى خادمك عند حدوث أحداث في متجرك.
أنواع الأحداث
| الحدث | المحفّز |
|---|---|
order.created | طلب جديد (واجهة العميل أو نقطة البيع) |
order.status_changed | تحديث حالة الطلب |
refund.created | استرداد طلب من نقطة البيع — جزئي أو كامل |
manager_override.approved | الموافقة على إجراء حساس برمز المدير (استرداد كبير، إلغاء طلب، خصم ≥ ٢٠٪، تبديل كاشير…) |
product.created | إضافة منتج جديد |
product.updated | تغيير تفاصيل المنتج |
customer.created | تسجيل عميل جديد |
inventory.changed | تغيير مستوى المخزون |
صيغة الحمولة
{ "id": "delivery-uuid", "event": "order.created", "entity_type": "order", "entity_id": "order-uuid-123", "data": { "id": "order-uuid-123", "status": "pending", "total": 80.0 }, "timestamp": "2026-04-20T14:30:00Z"}حمولة order.created من نقطة البيع
عندما يصدر الطلب من نقطة البيع تحتوي data على حقول إضافية تفيد أنظمة الرواتب والمحاسبة:
{ "source": "pos", "order_id": "order-uuid", "total": 120.0, "payment_method": "cash", "status": "completed", "customer_name": "أحمد محمد", "customer_phone": "+218910000123", "created_by": "session-owner-profile-uuid", "actual_cashier_id": "pin-switched-profile-uuid", "item_count": 3, "discount": 10.0}actual_cashier_id غير فارغ فقط عندما يستخدم الكاشير ميزة تبديل PIN — وفي هذه الحالة يجب اعتماده على created_by لاحتساب العمولات.
حمولة refund.created
{ "refund_id": "refund-uuid", "order_id": "order-uuid", "amount": 25.0, "refund_type": "partial", "reason": "العلبة مكسورة", "reason_code": "damaged", "items": [{ "product_id": "...", "variant_id": null, "quantity": 1, "amount": 25.0 }], "created_by": "cashier-profile-uuid"}reason_code قيم ثابتة: damaged · wrong_item · customer_changed_mind · price_match · expired · other (أو null إذا لم يحدّد الكاشير سبباً).
حمولة manager_override.approved
{ "action": "refund", "cashier_id": "cashier-profile-uuid", "manager_id": "manager-profile-uuid", "manager_name": "مدير الفرع", "context": { "orderId": "order-uuid", "amount": 150.0, "refundType": "full" }}قيم action المحتملة: refund · partial_refund · void · discount · negative_stock · price_override · cashier_switch · other. context يختلف حسب الإجراء — استخدم action للتفريع.
الرؤوس
| الرأس | الوصف |
|---|---|
X-durj-Signature | توقيع HMAC-SHA256 |
X-durj-Event | نوع الحدث |
X-durj-Delivery | معرف التسليم |
التحقق من التوقيع (Python)
import hmac, hashlib
def verify_signature(payload: bytes, secret: str, signature: str) -> bool: expected = hmac.new(secret.encode(), payload, hashlib.sha256).hexdigest() return hmac.compare_digest(expected, signature)سياسة إعادة المحاولة
| المحاولة | التأخير |
|---|---|
| الأولى | 1 دقيقة |
| الثانية | 5 دقائق |
| الثالثة | 30 دقيقة |
| الرابعة | ساعتان |
| الخامسة | 12 ساعة |
بعد 5 محاولات فاشلة، يُنقل إلى قائمة الرسائل الميتة (DLQ).
التكرار الآمن (مطلوب)
كل تسليم — بما فيه إعادات المحاولة لنفس الحدث — يحمل ترويسة X-durj-Delivery. قيمتها ثابتة عبر المحاولات: إذا أعاد المرسل التسليم بعد فشل أو مهلة، يُرسل نفس الحمولة بنفس X-durj-Delivery تماماً.
يجب على المتلقي إلغاء التكرار بناءً على هذه الترويسة — لا الاعتماد على تشابه المحتوى:
async function handleWebhook(req, res) { const deliveryId = req.headers['x-durj-delivery']
if (await alreadyProcessed(deliveryId)) { return res.status(200).json({ deduplicated: true }) }
await processEvent(req.body) await markProcessed(deliveryId) return res.status(200).json({ ok: true })}السبب: أي خطأ مؤقت (5xx) من طرفك يُعيد المحاولة حتى 5 مرات. بدون إلغاء التكرار، ستحاسب الزبون 5 مرات أو ترسل 5 رسائل لنفس الحدث.