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

تسجيل webhook

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

بقلم: Support

لا يفرض 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 مكرّر.

الجسم

الحقل

النوع

إلزامي

ملاحظات

url

string (https URL)

✅

يجب أن يكون https://؛ ويُرفض الرابط http://. الطول الأقصى 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": "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

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

شكل رابط خاطئ

400

bad_request "url must be https"

رابط يبدأ بـ http://

400

bad_request "url host must be a hostname, not an IP literal"

عنوان IP بدل اسم مضيف

400

bad_request "url host is missing or carries credentials"

جزء user:password@ في الرابط

400

bad_request "url host must be a public hostname"

مضيف ليس اسم نطاق كاملًا، مثل localhost

400

bad_request "url host does not resolve"

لا يملك اسم المضيف سجل A أو AAAA

400

bad_request "url must resolve to a public address"

يشير المضيف إلى عنوان خاص أو محجوز واحد على الأقل

400

bad_request "url port must be 80 or 443"

منفذ مخصّص مثل :8443

400

bad_request "Body must be valid JSON"

جسم الطلب ليس JSON صالحًا

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"

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

403

forbidden "API access requires an active Enterprise plan"

المتجر ليس على خطة Enterprise سارية

403

forbidden "Apps cannot use this endpoint"

الرمز تابع لتطبيق مثبّت

403

forbidden "Missing scope: webhooks:write"

المفتاح لا يحمل webhooks:write

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 بعد ذلك بقليل، خلال دقيقة عادةً.

  • يختبر شيفرة التحقق لديك. يُوقَّع بسر 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').

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

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