Webhooks
Webhooks إشعارات دفع لحظية من DZBuild إلى خادمك عند حدوث شيء على متجرك. استخدمها بدلًا من الاستعلام المتكرر (polling) — حمل أقل، تأخير أقل، والأهم: تسليمات webhook لا تُحسب من حصة طلباتك الشهرية.
لماذا Webhooks
قارن:
الاستعلام المتكرر — تنادي شيفرتك GET /v1/orders?since=... كل دقيقة. 1440 نداءً يوميًا، 1440 رحلة، تستهلك حصة API بالتساوي، والتأخير من "إنشاء الطلب" حتى "علم كودك" 60 ثانية.
Webhooks — تسجّل https://yourapp/webhooks مرة واحدة. ننادي عليه كلما أُنشئ طلب. التأخير ~2-5 ثوانٍ. صفر استعلام. صفر إهدار للحصة.
الاستعلام يتفوّق فقط حين:
- نقطتك غير قابلة للوصول من الإنترنت (استعلم من داخل شبكتك).
- ليس لديك خادم (استعلم من Lambda مجدوَلة / cron).
كيف يعمل التسليم
حدث/طلب يقع على الخادم الأصلي
│
▼
INSERT صف في api_v1_webhook_deliveries
│
▼
كرون يستنزف كل دقيقة (next_retry_at <= NOW())
│
▼
توقيع HMAC للجسم ─────► POST لرابطك مع timeout 10 ث
│ │
│ ├─ 2xx → علّم مسلَّمًا، انتهى
│ ├─ 5xx → أعد: 1د، 5د، 30د، 2س، 12س
│ ├─ 4xx → اعتبره سامًّا؛ لا تُعد
│ └─ بدون رد (timeout/DNS) → أعد
│
└─ 5 إخفاقات → انقل إلى DLQ
جدول إعادة المحاولة
بعد كل محاولة فاشلة، يُضبط next_retry_at إلى now() + delay[attempt]:
| المحاولة | التأخير |
|---|---|
| 1 | دقيقة |
| 2 | 5 دقائق |
| 3 | 30 دقيقة |
| 4 | ساعتان |
| 5 | 12 ساعة |
| السادسة | DLQ — تدخل يدوي |
نافذة إعادة المحاولة الإجمالية ~14.5 ساعة. بعدها يُرسَل التسليم إلى DLQ ويزداد failure_count للـ webhook. خمس رسائل ميتة متتالية تُعطّل الـ webhook تلقائيًا (status → dead)؛ راسل الدعم لإعادة التفعيل.
ما يُعتبر "نجاحًا"
- HTTP 200, 201, 202, 204 (أي 2xx) — نجاح.
- HTTP 4xx (400, 401, 403, 404, 422…) — لا تُعاد المحاولة. نعتبر 4xx "نقطتك ترفض هذه الحمولة بشكل قاطع"؛ المزيد لن يُجدي. أصلح نقطتك وأعد الاختبار عبر
POST /v1/webhooks/{id}/test. - HTTP 5xx، timeout، DNS، TLS — تُعاد.
إن أردت أن نعيد على 4xx محدد (نادر)، أعد 503 Service Unavailable بدلًا من 4xx.
نموذج الأمان
ما نُرسله
Content-Type: application/json
User-Agent: dzbuild-webhook/1
X-DZ-Timestamp: <unix seconds>
X-DZ-Signature: <hex hmac-sha256>
X-DZ-Delivery-Id: <id>
التوقيع
X-DZ-Signature = hex( hmac_sha256( WEBHOOK_SECRET, X-DZ-Timestamp + "." + sha256(body) ) )
WEBHOOK_SECRET هو ما حصلت عليه عند تسجيل webhook (POST /v1/webhooks). لا نكشفه ثانية — احفظه.
ما يجب أن تفعله
- تحقق من التوقيع على كل طلب (انظر التحقق من التواقيع).
- تأكد أن الطابع الزمني خلال 5 دقائق من ساعة خادمك — يرفض الإعادة.
- استخدم بايتات الجسم الخام للتجزئة — لا تُعد تسلسل JSON.
- كن idempotent —
X-DZ-Delivery-Idفريد لكل محاولة، لكن الحدث المنطقي ذاته قد يُسلَّم أكثر من مرة (مشاكل شبكة، إعادة بعد نجاح). أزل التكرار من جانبك بـdelivery_idأو معرّفات الحدث ذاتها.
ما لا نفعله
- لا نوثّق صادراتنا بـ mTLS. إن تطلّبت نقطتك ذلك، اعتمد reverse proxy يُجرّد/يضيف mTLS أمام معالجك.
- لا نقفل بحسب IP. نُرسل من CF Workers — الـ IP المصدر ضمن نطاقات Cloudflare. إن أردت whitelist استخدم قائمة Cloudflare.
مظروف الحمولة
كل جسم webhook له نفس الشكل الخارجي:
{
"event": "order.confirmed",
"store_id": 13,
"occurred_at": "2026-04-30T21:18:21+00:00",
"data": { "order_id": 6894, "old_status": "pending", "new_status": "confirmed" },
"delivery_id": "abc123def456"
}
| الحقل | ملاحظات |
|---|---|
event | نوع الحدث (القائمة الكاملة في كتالوج الأحداث). |
store_id | معرّف متجرك — مفيد إن كان لديك عدة webhooks لنفس المعالج. |
occurred_at | متى حدث الأمر في نظامنا، ISO 8601 مع المنطقة. |
data | حمولة خاصة بالحدث. انظر كتالوج الأحداث لشكل كل حدث. |
delivery_id | فريد لكل محاولة. استخدمه لإزالة التكرار من جانبك. |
الحصة
كل محاولة تسليم — نجاح، 4xx، إعادة — تُحسب 1 ضمن webhooks_per_month. حدث واحد يُعاد 5 مرات يكلّف 5. إذًا:
- اجعل نقطتك موثوقة (تردّ 2xx بسرعة) → محاولة واحدة لكل حدث.
- الإخفاقات تكلّف أكثر → أصلح الأخطاء بسرعة لتفادي حرق الحصة.
التالي
- التسجيل —
POST /v1/webhooksبكامل الجسم والاستجابة والأمثلة. - كتالوج الأحداث — كل حدث بحمولة بيانات نموذجية.
- التحقق من التواقيع — كود بأربع لغات.