تخط وانتقل إلى المحتوى الرئيسي

تسجيل webhook

أنشئ، اسرد، اختبر، واحذف اشتراكات webhook لمتجرك.

بقلم: Support

لا يفرض API v1 حدًّا لعدد webhooks لكل متجر — سجّل ما يحتاجه تكاملك فعليًا من روابط، ونظّف ما توقفت عن استعماله. (أما إضافة Webhooks للتاجر بدون كود فتفرض حدًّا: نقطة واحدة في Unlimited، و3 في Enterprise.)

كل webhook يمكن أن يشترك بقائمة مختلفة من الأحداث؛ اختر النموذج المناسب لك:

  • نقطة واحدة، كل الأحداث — أسهل للتطبيقات الصغيرة. تفرّع على event في معالجك.

  • عدة نقاط، حدث واحد لكل — أنظف في إعدادات microservices، لكن روابط أكثر للإدارة.

⚠️ تنبيه — الوصول التجريبي (Pilot)

API v1 مُقيَّد بالبرنامج التجريبي في الإنتاج. لا يكفي أن يكون المفتاح صالحًا — يجب أن تُسجّله DZBuild في البرنامج التجريبي، وإلا فكل نداء يُرجع 403 forbidden "API is in pilot mode; key not enrolled".

POST /v1/webhooks — تسجيل

المصادقة: مفتاح منصة بصلاحية webhooks:write. يتطلب Idempotency-Key.

الجسم

الحقل

النوع

إلزامي

ملاحظات

url

string (https URL)

يجب أن يكون http:// أو https://. للإنتاج: دائمًا https://. الطول الأقصى 500 حرف — والرابط الأطول يُرفض بـ 500 server_error لا كخطأ تحقق.

events

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": "fc9b5f0b51b4a93c1d6f8e29b6a2e30c7c2c44a4f2a6c8d8e0e1b9d4f6c1a8b3",
    "note":   "Save the secret now — it is not retrievable after this response."
  },
  "meta": { "request_id": "...", "api_version": "v1" }
}

💡 نصيحة — لا تتفرّع أبدًا على == 201

لا توجد نقطة إنشاء في v1 تُرجع 201 اليوم — /v1/webhooks و /v1/products و /v1/orders و /v1/landing-pages و /v1/keys كلها تُجيب بـ 200 عند النجاح. تفرّع على أي 2xx بدلًا من ذلك.

secret يظهر مرة واحدة. احفظه بجانب معرّف الـ webhook في مدير الأسرار. لاحظ أنه ليس حاليًا المفتاح الذي تُوقَّع به تسليمات API v1 — انظر التوقيع — فهو اليوم قيمة تحتفظ بها للمستقبل، لا قيمة تتحقق بها. إن فقدته، احذف الـ webhook وأنشئ غيره.

الأخطاء

HTTP

الكود / الرسالة

السبب

400

bad_request "url must be a valid http(s) URL"

شكل رابط خاطئ

400

bad_request "events must be a non-empty list"

مصفوفة فارغة

400

bad_request "unknown event: foo. Allowed: …"

اسم حدث غير موجود في الكتالوج

400

bad_request "Idempotency-Key header is required for write requests"

نسيان Idempotency-Key في POST/DELETE

400

bad_request "Idempotency-Key must be <=64 chars, [A-Za-z0-9_-:.]"

المفتاح أطول من اللازم، أو يحوي محارف خارج تلك المجموعة (محارف base64 وهي + و / و = كلها مرفوضة)

403

forbidden "API is in pilot mode; key not enrolled"

المفتاح موجود لكنه غير مُسجَّل في البرنامج التجريبي

500

server_error "Could not register webhook"

غالبًا url أطول من 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 تُقبل ثم لا تُصدَر أبدًا. راجع كتالوج الأحداث قبل أن تبني على أي منها.

الحالة

المعنى

active

يستلم التسليمات. عمليًا هذه هي القيمة الوحيدة التي ستراها.

paused

محجوزة للاستعمال مستقبلًا، غير مستعملة حاليًا — ولا توجد صفحة في لوحة التحكم لـ webhooks الخاصة بـ API v1.

dead

محجوزة للاستعمال مستقبلًا، غير مستعملة حاليًا. لا يوجد تعطيل تلقائي؛ يواصل failure_count عدّ المحاولات الفاشلة فقط ويعود إلى 0 عند النجاح التالي.

لإيقاف التسليمات، احذف الـ 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 بعد ذلك بقليل، خلال دقيقة عادةً.

  • لا يمكنه التحقق من صحة شيفرة التحقق لديك. كأي تسليم في API v1، توقيعه ليس مشتقًّا من 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 مجرد اسم بديل، ولا يحصل على ذاكرة القراءة المؤقتة لمدة 30 ثانية، وقد تُحجب عليه بعض المسارات.

متطلبات النقطة

رابط webhook يجب أن:

  • يردّ بـ 2xx عند النجاح. الأخطاء 4xx و 3xx إخفاقات بمحاولة واحدة؛ أما 5xx فيُعاد إرسال POST كل دقيقة حتى يتوقف (انظر سلوك إعادة المحاولة).

  • يجيب خلال 10 ثوانٍ إجمالًا (و5 ثوانٍ للاتصال). الأبطأ يُحسب timeout، والـ timeout لا يُعاد — يُهجر التسليم.

  • يقدّم TLS صالحًا. الشهادات تُتحقَّق بصرامة، فالشهادات الموقّعة ذاتيًا تفشل بلا إعادة.

  • لا يُعيد التوجيه. لا نتبع Location؛ و301/302 إخفاق.

  • يقبل POST مع Content-Type: application/json.

  • يقرأ الجسم الخام إن كنت تُجزّئ أي شيء (لا تُعد التسلسل).

  • يكون idempotent — نفس delivery_id قد يصل أكثر من مرة.

فخ شائع في بعض الأطر: middleware يُعيد ترميز جسم JSON قبل أن يراه معالجك، فيُخفق sha256(body) بالمطابقة. الحلول:

  • Express: استعمل express.raw({ type: 'application/json' }) لمسار webhook ثم JSON.parse(req.body) في المعالج.

  • Django: request.body هي البايتات الخام — هذا ما تريده.

  • Laravel: $request->getContent() يُعيد الجسم الخام.

  • PHP خام: file_get_contents('php://input').

انظر التحقق من التواقيع لشيفرة كاملة بأربع لغات.

هل أجاب هذا عن سؤالك؟