لا يفرض API v1 حدًّا لعدد webhooks لكل متجر — سجّل ما يحتاجه تكاملك فعليًا من روابط، ونظّف ما توقفت عن استعماله. (أما إضافة Webhooks للتاجر بدون كود فتفرض حدًّا: نقطة واحدة في Unlimited، و3 في Enterprise.)
كل webhook يمكن أن يشترك بقائمة مختلفة من الأحداث؛ اختر النموذج المناسب لك:
نقطة واحدة، كل الأحداث — أسهل للتطبيقات الصغيرة. تفرّع على
eventفي معالجك.عدة نقاط، حدث واحد لكل — أنظف في إعدادات microservices، لكن روابط أكثر للإدارة.
⚠️ تنبيه — الوصول التجريبي (Pilot)
تحتاج webhooks الخاصة بـ API v1 إلى مفتاح API خاص بالتاجر من متجر على خطة Enterprise سارية؛ وأي خطة أخرى، أو اشتراك منتهي الصلاحية، يتلقى 403 forbidden "API access requires an active Enterprise plan". المفاتيح المُنشأة من /dashboard/api مُسجَّلة مسبقًا في البرنامج التجريبي؛ والمفتاح غير المُسجَّل يتلقى 403 forbidden "API is in pilot mode; key not enrolled". أما رموز التطبيقات المثبّتة فتتلقى 403 forbidden "Apps cannot use this endpoint" على كل نداء تحت /v1/webhooks.
POST /v1/webhooks — تسجيل
المصادقة: مفتاح منصة بصلاحية webhooks:write. يتطلب Idempotency-Key. على خلاف أغلب عمليات الكتابة، لا تُخزَّن هذه الاستجابة لإعادة تشغيلها لأنها تحمل السر: إعادة المحاولة بالمفتاح نفسه تسجّل webhook ثانيًا بسر جديد. إن انتهت مهلة النداء، راجع GET /v1/webhooks قبل إعادة المحاولة واحذف أي webhook مكرّر.
الجسم
الحقل | النوع | إلزامي | ملاحظات |
| string (https URL) | ✅ | يجب أن يكون |
| string[] | ✅ | قائمة أسماء أحداث. انظر كتالوج الأحداث للقيم المسموح بها. مصفوفة فارغة = خطأ. |
الطلب
curl -X POST 'https://api.dzbuild.app/v1/webhooks' \
-H "Authorization: Bearer $DZ_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"url": "https://yourapp.example/webhooks/dzbuild",
"events": ["order.created", "order.confirmed", "order.shipped",
"order.cancelled", "signup.counted"]
}'
الاستجابة 200
{
"data": {
"id": 17,
"secret": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"note": "Save the secret now — it is not retrievable after this response."
},
"meta": { "request_id": "...", "api_version": "v1" }
}
💡 نصيحة — لا تتفرّع أبدًا على == 201
نقاط الإنشاء /v1/webhooks و /v1/products و /v1/orders و /v1/landing-pages و /v1/keys تُجيب بـ 200 عند النجاح، بينما تُجيب نقاط أخرى، مثل /v1/categories، بـ 201. تفرّع على أي 2xx بدلًا من ذلك.
secret يظهر مرة واحدة. احفظه بجانب معرّف الـ webhook في مدير الأسرار، فكل تسليم إلى هذا الـ webhook يُوقَّع به (انظر التوقيع). إن فقدته، احذف الـ webhook وأنشئ غيره.
الأخطاء
HTTP | الكود / الرسالة | السبب |
400 |
| شكل رابط خاطئ |
400 |
| رابط يبدأ بـ |
400 |
| عنوان IP بدل اسم مضيف |
400 |
| جزء |
400 |
| مضيف ليس اسم نطاق كاملًا، مثل |
400 |
| لا يملك اسم المضيف سجل A أو AAAA |
400 |
| يشير المضيف إلى عنوان خاص أو محجوز واحد على الأقل |
400 |
| منفذ مخصّص مثل |
400 |
| جسم الطلب ليس JSON صالحًا |
400 |
| مصفوفة فارغة |
400 |
| اسم حدث غير موجود في الكتالوج |
400 |
| نسيان |
400 |
| المفتاح أطول من اللازم، أو يحوي محارف خارج تلك المجموعة (محارف base64 وهي |
403 |
| المفتاح موجود لكنه غير مُسجَّل في البرنامج التجريبي |
403 |
| المتجر ليس على خطة Enterprise سارية |
403 |
| الرمز تابع لتطبيق مثبّت |
403 |
| المفتاح لا يحمل |
500 |
| غالبًا |
GET /v1/webhooks — قائمة
المصادقة: مفتاح منصة بصلاحية webhooks:read.
curl https://api.dzbuild.app/v1/webhooks \ -H "Authorization: Bearer $DZ_KEY"
{
"data": {
"items": [
{
"id": 17,
"url": "https://yourapp.example/webhooks/dzbuild",
"events": ["order.created", "order.confirmed"],
"status": "active",
"last_success_at": "2026-04-30 21:18:23",
"last_failure_at": null,
"failure_count": 0,
"created_at": "2026-04-30 19:00:00"
}
],
"allowed_events": [
"order.created", "order.confirmed", "order.shipped", "order.delivered",
"order.cancelled", "order.returned", "payment.received",
"signup.counted", "event.recorded", "product.stock_low"
]
}
}
allowed_events هي القائمة التي يقبلها الـ API عند التسجيل — لكنها أوسع مما يُطلق فعليًا. فـ payment.received و event.recorded و product.stock_low تُقبل ثم لا تُصدَر أبدًا. راجع كتالوج الأحداث قبل أن تبني على أي منها.
الحالة | المعنى |
| يستلم التسليمات. عمليًا هذه هي القيمة الوحيدة التي ستراها. |
| محجوزة للاستعمال مستقبلًا، غير مستعملة حاليًا — ولا توجد صفحة في لوحة التحكم لـ webhooks الخاصة بـ API v1. |
| محجوزة للاستعمال مستقبلًا، غير مستعملة حاليًا. لا يوجد تعطيل تلقائي؛ يواصل |
لإيقاف التسليمات، احذف الـ webhook.
POST /v1/webhooks/{id}/test
أطلق تسليم webhook.test لتتحقق من أن نقطتك قابلة للوصول ولترى شكل المظروف.
لا يمكن الاشتراك في webhook.test: وضعه في مصفوفة events عند التسجيل يُرجع 400 bad_request "unknown event: webhook.test. Allowed: …". ويُرسَل تسليم الاختبار إلى الـ webhook المستهدف بغض النظر عمّا يشترك فيه ذلك الـ webhook.
المصادقة: مفتاح منصة بصلاحية webhooks:write. يتطلب Idempotency-Key.
curl -X POST 'https://api.dzbuild.app/v1/webhooks/17/test' \ -H "Authorization: Bearer $DZ_KEY" \ -H "Idempotency-Key: test-17-$(date +%s)"
{ "data": { "tested": true, "note": "a webhook.test delivery was enqueued; check your endpoint" } }
تسليم الاختبار يبدو هكذا:
{
"event": "webhook.test",
"store_id": 13,
"occurred_at": "2026-04-30T21:24:17+00:00",
"data": { "ts": 1717112657 },
"delivery_id": "9f2c41ab77e05d18"
}
أمران يجب معرفتهما عنه:
يُوضع في طابور، وليس متزامنًا. الاستجابة تؤكد فقط أن التسليم دخل الطابور؛ ويصل الـ POST بعد ذلك بقليل، خلال دقيقة عادةً.
يختبر شيفرة التحقق لديك. يُوقَّع بسر
secretالخاص بـ webhook لديك مثل أي تسليم آخر، فإذا اجتاز فحصك فهو يثبت إمكانية الوصول وشكل الحمولة وصحة التحقق من التوقيع. انظر التوقيع.
DELETE /v1/webhooks/{id}
المصادقة: مفتاح منصة بصلاحية webhooks:write. يتطلب Idempotency-Key.
curl -X DELETE 'https://api.dzbuild.app/v1/webhooks/17' \ -H "Authorization: Bearer $DZ_KEY" \ -H "Idempotency-Key: del-17"
{ "data": { "deleted": true, "id": 17 } }
بعد الحذف: - لا تُجدوَل تسليمات جديدة. - كل تسليم في الطابور لذلك الـ webhook يُلغى فورًا. لا شيء "يُحاوَل مرة أخيرة". إن كان المتراكم يهمك، أوقف كتاباتك ودع الطابور يفرغ (أقل من دقيقة عادةً) قبل الحذف. - سر الـ webhook صار عديم الفائدة.
إعادة تشغيل Idempotency-Key في test و DELETE
يُخزّن الـ API استجابة كل Idempotency-Key لمدة 24 ساعة ويعيد تشغيلها مع ترويسة Idempotency-Replay: 1. لذلك نتيجتان:
إعادة استعمال مفتاح حرفي مثل
del-17خلال 24 ساعة تعيد الاستجابة المخزّنة بدل تنفيذ نداء جديد. استعمل مفتاحًا جديدًا (أو مفتاحًا يتضمّن رقم المحاولة) كلما أردت فعلًا أن تُنفَّذ العملية.استجابات الخطأ تُخزَّن أيضًا. الكتابة الفاشلة تعيد نفس الـ 4xx لمدة 24 ساعة تحت نفس المفتاح — غيّر المفتاح بعد إصلاح الطلب.
إعادة التشغيل مضمونة لمدة 24 ساعة أيًّا كان المضيف الذي تناديه، وتُجيب دائمًا بترويسة Idempotency-Replay: 1. ومع ذلك يُفضَّل استعمال https://api.dzbuild.app/v1: فالمسار dzbuild.com/api/v1 مجرد اسم بديل، وقد تُحجب عليه بعض المسارات.
متطلبات النقطة
رابط webhook يجب أن:
يردّ بـ 2xx عند النجاح. يُعاد إرسال 5xx و
408و429بعد دقيقة، ثم 5 دقائق، ثم 30 دقيقة، ثم ساعتين، ثم يُسقط؛ أما أي 4xx آخر، وكل 3xx، فينقل التسليم فورًا إلى قائمة الرسائل الميتة (انظر سلوك إعادة المحاولة).يجيب خلال 10 ثوانٍ إجمالًا (و5 ثوانٍ للاتصال). الأبطأ يُحسب timeout، ويُعاد كما يُعاد 5xx.
يبدأ بـ
https://ويقدّم TLS صالحًا. الشهادات تُتحقَّق بصرامة، فالشهادة الموقّعة ذاتيًا تفشل في كل محاولة.لا يُعيد التوجيه. لا نتبع
Location؛ و301/302إخفاق.يقبل
POSTمعContent-Type: application/json.يقرأ الجسم الخام للتحقق من التوقيع (لا تُعد التسلسل).
يكون idempotent — نفس
delivery_idقد يصل أكثر من مرة.
فخ شائع في بعض الأطر: middleware يُعيد ترميز جسم JSON قبل أن يراه معالجك، فلا يطابق HMAC. الحلول:
Express: استعمل
express.raw({ type: 'application/json' })لمسار webhook ثمJSON.parse(req.body)في المعالج.Django:
request.bodyهي البايتات الخام — هذا ما تريده.Laravel:
$request->getContent()يُعيد الجسم الخام.PHP خام:
file_get_contents('php://input').
انظر التحقق من التواقيع لشيفرة كاملة بأربع لغات.