Webhooks
Webhooks الصادرة
ترسل وجهة أحداث JSON إلى عنوانك عند تغيّر بيانات المتجر (POST أو PUT/PATCH/GET). السر الاختياري يوقّع كل تسليم للتحقق من الأصالة.
إدارة نقاط الاستقبال
افتح بوابة التاجر → إعدادات المتجر → المطورون → Webhooks. لكل نقطة:
- اللقب — تسمية اختيارية تظهر في البوابة.
- URL — وجهة HTTPS للتسليم.
- Method — POST (افتراضي)، PUT، PATCH، أو GET.
- Event — نوع حدث واحد أو أكثر (أو * للكل).
- Secret — سر مشترك اختياري (٨ أحرف على الأقل) لتوقيع HMAC.
يمكنك أيضًا إنشاء النقاط عبر POST /api/v1/webhooks/endpoints بنطاق webhooks.write.
سلوك التسليم
عند وقوع حدث مشترك، تُدرَج عملية تسليم وتُرسل فورًا:
- حتى ٦ محاولات إجمالًا (محاولة أولى + ٥ إعادة).
- تأخيرات الإعادة: ٣٠ث ← ٢د ← ١٠د ← ١س ← ٦س.
- فشل متواصل لنحو أسبوع يوقف النقطة ويُشعر المتجر.
حدّث الرابط أو أعد التفعيل من المطورون → Webhooks بعد إصلاح المستقبِل.
ترويسات الطلب
كل تسليم يتضمن:
- Content-Type: application/json
- User-Agent: Wijha-Webhooks/1.0
- X-Wijha-Timestamp — مللي ثانية Unix وقت الإرسال
- X-Wijha-Signature — عند وجود سر فقط: HMAC-SHA256 سداسي لـ `${timestamp}.${rawBody}`
const crypto = require("crypto");
function verifyWijhaSignature({ secret, timestamp, rawBody, signature }) {
const expected = crypto
.createHmac("sha256", secret)
.update(`${timestamp}.${rawBody}`)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(expected, "utf8"),
Buffer.from(signature, "utf8"),
);
}
// Express example — keep the raw body for HMAC verification
app.post("/webhooks/wijha", express.raw({ type: "application/json" }), (req, res) => {
const timestamp = req.get("X-Wijha-Timestamp");
const signature = req.get("X-Wijha-Signature");
const rawBody = req.body.toString("utf8");
if (!verifyWijhaSignature({
secret: process.env.WIJHA_WEBHOOK_SECRET,
timestamp,
rawBody,
signature,
})) {
return res.status(401).send("invalid signature");
}
const event = JSON.parse(rawBody);
// handle event.eventType + event.data / event.changes
res.status(200).send("ok");
});JSON التسليم
كل محاولة ترسل هذا الغلاف. data يحوي لقطات الكيانات؛ changes يحوي حقول الحدث المقتضبة.
{
"id": "del_01JABCXYZ",
"event": "OrderCreated",
"eventType": "OrderCreated",
"storeId": "str_01JSTORE",
"occurredAt": "2026-10-08T17:12:04.128Z",
"data": {
"order": {
"id": "ord_01JABC",
"storeId": "str_01JSTORE",
"number": "10042",
"channel": "portal",
"status": "pending",
"customerId": "cus_01JDEF",
"customerName": "Ahmed Hassan",
"customerPhone": "+201012345678",
"shippingAddress": {
"city": "Cairo",
"line1": "12 Nile St",
"phone": "+201012345678"
},
"lineItems": [
{
"productId": "prd_01J",
"variantId": "var_01J",
"sku": "TEE-BLK-M",
"title": "Cotton tee",
"qty": 2,
"price": 225
}
],
"payment": { "method": "cod", "codAmount": 495, "currency": "EGP" },
"totals": {
"subtotal": 450,
"shipping": 45,
"total": 495,
"currency": "EGP"
},
"createdAt": "2026-10-08T17:12:04.000Z"
},
"customer": {
"id": "cus_01JDEF",
"name": "Ahmed Hassan",
"phone": "+201012345678",
"email": "ahmed@example.com"
}
},
"changes": {
"aggregateType": "order",
"aggregateId": "ord_01JABC"
}
}تظهر فقط المفاتيح ذات الصلة في data لكل حدث. أرجع HTTP 2xx للتأكيد.
الإثراء
قبل الإدراج، توسّع وجهة حدث النطاق المقتضب:
- id / event / eventType / storeId / occurredAt — بيانات التسليم.
- data — لقطات كاملة (طلب، شحنة، منتج، عميل، مجموعة، مستوى مخزون حسب الحدث).
- changes — الحقائق الأصلية (aggregateType، aggregateId، تحولات الحالة، الإجراءات، الأخطاء).
إن تعذّر تحميل كيان مرتبط يُحذف مفتاحه من data؛ changes تصل دائمًا.
كتالوج الأحداث
اشترك في أي من أنواع الأحداث التالية:
- OrderCreated — طلب جديد
- OrderUpdated — تغيّرت حقول الطلب
- OrderStatusChanged — انتقال حالة الطلب
- ProductUpdated — إنشاء/تحديث/حذف منتج
- CollectionUpdated — إنشاء/تحديث/حذف مجموعة
- CustomerCreated — عميل جديد
- CustomerUpdated — تغيّرت حقول العميل
- ShipmentCreated — حُجزت شحنة
- ShipmentDelivered — وُسِمت الشحنة كمُسلَّمة
- ShipmentCreateFailed — فشل الحجز
- InventoryAdjusted — تعديل مستوى المخزون
- StockReceived — استلام مخزون وارد
مثال OrderCreated
لقطة كاملة للطلب مع العميل المرتبط عند التوفر:
{
"id": "del_01JABCXYZ",
"event": "OrderCreated",
"eventType": "OrderCreated",
"storeId": "str_01JSTORE",
"occurredAt": "2026-10-08T17:12:04.128Z",
"data": {
"order": {
"id": "ord_01JABC",
"storeId": "str_01JSTORE",
"number": "10042",
"channel": "portal",
"status": "pending",
"customerId": "cus_01JDEF",
"customerName": "Ahmed Hassan",
"customerPhone": "+201012345678",
"shippingAddress": {
"city": "Cairo",
"line1": "12 Nile St",
"phone": "+201012345678"
},
"lineItems": [
{
"productId": "prd_01J",
"variantId": "var_01J",
"sku": "TEE-BLK-M",
"title": "Cotton tee",
"qty": 2,
"price": 225
}
],
"payment": { "method": "cod", "codAmount": 495, "currency": "EGP" },
"totals": {
"subtotal": 450,
"shipping": 45,
"total": 495,
"currency": "EGP"
},
"createdAt": "2026-10-08T17:12:04.000Z"
},
"customer": {
"id": "cus_01JDEF",
"name": "Ahmed Hassan",
"phone": "+201012345678",
"email": "ahmed@example.com"
}
},
"changes": {
"aggregateType": "order",
"aggregateId": "ord_01JABC"
}
}مثال ProductUpdated
{
"id": "del_01JPROD",
"event": "ProductUpdated",
"eventType": "ProductUpdated",
"storeId": "str_01JSTORE",
"occurredAt": "2026-10-08T16:40:00.000Z",
"data": {
"product": {
"id": "prd_01J",
"title": "Cotton tee",
"status": "active",
"price": 225,
"currency": "EGP",
"sku": "TEE-BLK-M"
}
},
"changes": {
"aggregateType": "product",
"aggregateId": "prd_01J",
"action": "updated"
}
}مثال CustomerCreated
{
"id": "del_01JCUS",
"event": "CustomerCreated",
"eventType": "CustomerCreated",
"storeId": "str_01JSTORE",
"occurredAt": "2026-10-08T15:05:22.000Z",
"data": {
"customer": {
"id": "cus_01JDEF",
"name": "Ahmed Hassan",
"phone": "+201012345678",
"email": "ahmed@example.com",
"createdAt": "2026-10-08T15:05:22.000Z"
}
},
"changes": {
"aggregateType": "customer",
"aggregateId": "cus_01JDEF"
}
}مثال ShipmentCreated
{
"id": "del_01JSHIP",
"event": "ShipmentCreated",
"eventType": "ShipmentCreated",
"storeId": "str_01JSTORE",
"occurredAt": "2026-10-08T18:20:11.000Z",
"data": {
"shipment": {
"id": "shp_01J",
"orderId": "ord_01JABC",
"trackingNumber": "WJH123456789",
"status": "booked",
"carrier": "mega"
},
"order": {
"id": "ord_01JABC",
"number": "10042",
"status": "waiting_for_pickup"
}
},
"changes": {
"aggregateType": "shipment",
"aggregateId": "shp_01J",
"orderId": "ord_01JABC"
}
}الاستجابات المتوقعة
يجب أن يؤكد مستقبلك بسرعة:
- 2xx — يُعلَّم التسليم ناجحًا؛ تُصفَّر نافذة الفشل المتواصل.
- حالة أخرى أو مهلة — تُحسب فشلًا؛ تُعاد المحاولة حسب الجدول أعلاه.
- تسليمات GET ترسل نفس الترويسات؛ يُحذف الجسم في GET.
HTTP/1.1 200 OK
Content-Type: text/plain
ok