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

إشعارات Webhook

ترسل DZBuild طلب POST عبر HTTPS إلى رابط webhook الخاص بتطبيقك عندما يقع حدث في متجر ثبّت التطبيق. كل طلب موقّع بمفتاح التوقيع الخاص بتطبيقك، فيستطيع خادمك التأكد من أنه صادر عن DZBuild قبل أن يتصرّف بناءً عليه.

رابط Webhook​

تحدّد رابط webhook واحدًا والأحداث التي تريدها لتطبيقك في منصة المطورين. يستقبل الرابط نفسه أحداث كل المتاجر التي تثبّت التطبيق، ويبيّن الحقل store_id في كل حمولة المتجرَ المعني.

يجب أن يحترم الرابط هذه الشروط، وإلا رفضت منصة المطورين حفظه:

  • https فقط، على المنفذ 443.
  • اسم مضيف عام بنطاق من المستوى الأعلى. تُرفض عناوين IP وأسماء المستخدمين وكلمات المرور داخل الرابط.
  • 255 حرفًا على الأكثر.
  • ألا يكون على نطاق تابع لـ DZBuild (dzbuild.app، dzbuild.com، dzbuild.me، dzme.app، minacef.com، twarc.net) ولا على workers.dev.

القاعدة الأخيرة تعني أن Worker على رابط workers.dev مجاني لا يستطيع استقبال إشعارات webhook. ضعه على نطاق تملكه في حسابك على Cloudflare (النطاقات المخصصة متاحة في خطة Cloudflare المجانية)، وسجّل ذلك الرابط، ثم تحقق منه.

إن كنت تحتاج الطلبات الجديدة فقط، فاستعلم عنها بدل ذلك: طلب GET /v1/orders?since=<last created_at>&limit=50 واحد لكل متجر في الدقيقة يبقى ضمن 120 طلبًا في الدقيقة لرمز التثبيت، وهذا ما يفعله قالب cloudflare-orders افتراضيًا. أما تغيّرات الحالة فما زالت تحتاج إلى إشعارات webhook.

عند الضغط على «التحقق من الـ Webhook» في منصة المطورين، ترسل DZBuild طلب webhook.verify واحدًا إلى الرابط. أجب عنه بأي رمز حالة 2xx خلال 6 ثوانٍ. لا يستقبل رابطك أحداثًا حقيقية إلا بعد التحقق منه. والتحقق الناجح يعيد أيضًا تفعيل الإرسال لكل تثبيت عُطّلت نقطة استقباله بعد إخفاقات متكررة.

لا تصل أحداث الطلبات إلى تثبيت إلا إذا منح التاجر صلاحية orders:read، لأن الحمولة تتضمن اسم المشتري وهاتفه وعنوانه. من دون هذه الصلاحية لا يستقبل التثبيت أي حدث طلب، بل app.uninstalled فقط.

الأحداث​

الحدثمتى يُرسل
order.createdعند تسجيل طلب جديد في المتجر.
order.confirmedعند تغيّر حالة الطلب إلى مؤكَّد.
order.processingعند تغيّر حالة الطلب إلى قيد التجهيز.
order.shippedعند تغيّر حالة الطلب إلى مُرسَل.
order.deliveredعند تغيّر حالة الطلب إلى مُسلَّم.
order.cancelledعند تغيّر حالة الطلب إلى ملغى.
order.returnedعند تغيّر حالة الطلب إلى مُرتجَع.
app.uninstalledعندما يزيل التاجر تطبيقك من المتجر. يصلك حتى لو لم تختره.
webhook.verifyعند الضغط على «التحقق من الـ Webhook» في منصة المطورين.

تفحص DZBuild الطلبات الجديدة وتغيّرات الحالة مرة كل دقيقة. يُرسل order.created في أول فحص يرى الطلب الجديد، فتوقّع وصوله خلال دقيقة أو دقيقتين. يُرسل كل حدث حالة مرة واحدة على الأكثر لكل طلب.

الحمولة​

جسم كل طلب غلاف JSON واحد. مثال الطلب هذا مختصر:

{
"id": "evt_9b2f6c1d04e8a7b35c6d2e10",
"event": "order.confirmed",
"created_at": "2026-09-26T14:05:09+01:00",
"store_id": 1234,
"data": {
"order": { "id": 98765, "order_number": "ORD-1234-20260926-1a2b3c4d", "status": "confirmed" },
"items": [ { "product_id": 555, "product_name": "Sac en cuir", "quantity": 1 } ],
"previous_status": "pending"
}
}
الحقلالمعنى
idمعرّف الحدث. يبقى نفسه في كل إعادة محاولة للحدث نفسه، فاستعمله لإسقاط التكرارات.
eventاسم الحدث.
created_atوقت إنشاء الغلاف، بصيغة ISO 8601 مع فارق التوقيت عن UTC.
store_idالمتجر الذي يخصّه الحدث. قيمته 0 في webhook.verify.
dataبيانات الحدث.

في أحداث الطلبات يحمل data.order هذه الحقول: id، store_id، order_number، store_seq، status، payment_status، payment_method، delivery_type، desk_name، customer_name، customer_phone، customer_email، wilaya_id، wilaya_name (الاسم بالعربية)، commune، address، subtotal، shipping_cost، discount، total، promo_code، customer_notes، tracking_number، delivery_company، created_at، updated_at. وdata.items قائمة تضم لكل سطر product_id وvariant_id وproduct_name وvariant_name وsku وprice وquantity وtotal وsource. وتحمل أحداث الحالة أيضًا data.previous_status.

ترويسات الإرسال​

الترويسةالقيمة
Content-Typeapplication/json
X-DZ-Timestampتوقيت Unix بالثواني لحظة التوقيع.
X-DZ-Signaturet=<timestamp>,v1=<signature>
X-DZ-Eventاسم الحدث، وهو نفس event في الجسم.
X-DZ-Deliveryمعرّف الإرسال. يبقى نفسه بين محاولات الإرسال الواحد. لا يحمله طلب webhook.verify القادم من منصة المطورين.
User-AgentDZBuild-Webhooks/1.0

التوقيع​

تتكوّن X-DZ-Signature من جزأين تفصل بينهما فاصلة:

  • t هو الطابع الزمني Unix، وهو نفس قيمة X-DZ-Timestamp.
  • v1 هو HMAC-SHA256 بالنظام الست عشري بأحرف صغيرة للسلسلة t ثم . ثم جسم الطلب كما وصل، ومفتاحه مفتاح التوقيع الخاص بتطبيقك.

للتحقق من طلب:

  1. اقرأ بايتات الجسم الخام قبل أي تحليل لـ JSON. الجسم الذي حُلّل ثم أعيد تحويله إلى نص لن يطابق التوقيع.
  2. احسب HMAC-SHA256 بمفتاح التوقيع على الطابع الزمني ثم نقطة ثم الجسم الخام.
  3. قارن النتيجة بقيمة v1 مقارنةً ثابتة الزمن.
  4. ارفض الطلب إذا ابتعد الطابع الزمني عن ساعتك بأكثر من 5 دقائق. هذا يمنع إعادة إرسال طلب قديم.

يظهر مفتاح التوقيع في منصة المطورين. احفظه على خادمك فقط. عند تجديده، توقّع كل التثبيتات بالمفتاح الجديد ابتداءً من الإرسال الموالي، دون فترة تداخل، فانشر المفتاح الجديد على خادمك في اللحظة نفسها.

Node.js​

const crypto = require('crypto');
const express = require('express');

const app = express();
const SECRET = process.env.DZBUILD_SIGNING_SECRET;

function parseSignature(header) {
const parts = {};
for (const pair of (header || '').split(',')) {
const i = pair.indexOf('=');
if (i > 0) parts[pair.slice(0, i)] = pair.slice(i + 1);
}
return parts;
}

app.post('/dzbuild/webhooks', express.raw({ type: 'application/json' }), (req, res) => {
const { t, v1 } = parseSignature(req.get('X-DZ-Signature'));
if (!t || !v1 || !/^\d+$/.test(t)) return res.status(401).end();

const expected = crypto.createHmac('sha256', SECRET).update(t + '.').update(req.body).digest('hex');
const valid = v1.length === expected.length
&& crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected));
if (!valid) return res.status(401).end();
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return res.status(401).end();

const event = JSON.parse(req.body.toString('utf8'));
// Queue event for processing, then answer fast.
res.status(200).end();
});

PHP​

<?php
$secret = getenv('DZBUILD_SIGNING_SECRET');
$raw = file_get_contents('php://input');

$parts = [];
foreach (explode(',', $_SERVER['HTTP_X_DZ_SIGNATURE'] ?? '') as $pair) {
[$k, $v] = array_pad(explode('=', $pair, 2), 2, '');
$parts[$k] = $v;
}
$t = $parts['t'] ?? '';
$v1 = $parts['v1'] ?? '';

$expected = hash_hmac('sha256', $t . '.' . $raw, $secret);
if (!ctype_digit($t) || !hash_equals($expected, $v1) || abs(time() - (int) $t) > 300) {
http_response_code(401);
exit;
}

$event = json_decode($raw, true);
// Queue $event for processing, then answer fast.
http_response_code(200);

Python​

import hashlib
import hmac
import json
import os
import time

from flask import Flask, abort, request

app = Flask(__name__)
SECRET = os.environ["DZBUILD_SIGNING_SECRET"].encode()


@app.post("/dzbuild/webhooks")
def dzbuild_webhook():
raw = request.get_data()
parts = dict(
pair.split("=", 1)
for pair in request.headers.get("X-DZ-Signature", "").split(",")
if "=" in pair
)
t = parts.get("t", "")
v1 = parts.get("v1", "")

expected = hmac.new(SECRET, t.encode() + b"." + raw, hashlib.sha256).hexdigest()
if not t.isdigit() or not hmac.compare_digest(expected, v1):
abort(401)
if abs(time.time() - int(t)) > 300:
abort(401)

event = json.loads(raw)
# Queue event for processing, then answer fast.
return "", 200

إعادة المحاولة والإرسال المتروك​

ينجح الإرسال عندما يجيب خادمك برمز حالة 2xx خلال 10 ثوانٍ. أي رمز آخر، أو إعادة توجيه (3xx)، أو تجاوز للمهلة، أو خطأ في الاتصال يُحسب محاولةً فاشلة. لا تُتبع إعادات التوجيه.

لكل إرسال 5 محاولات على الأكثر. بعد كل محاولة فاشلة تنتظر المحاولة الموالية على الأقل:

المحاولة الفاشلةالانتظار قبل المحاولة الموالية
160 ثانية
25 دقائق
330 دقيقة
4ساعتان
5لا انتظار. يُعلَّم الإرسال متروكًا ولا يُرسل مرة أخرى أبدًا.

هذه المدد حدّ أدنى لأن عملية الإرسال تعمل مرة كل دقيقة. الإرسال مضمون مرة واحدة على الأقل: قد يصلك الطلب مرتين، مثلًا بعد عطل في الشبكة من جهة DZBuild، فأسقط التكرارات بواسطة id الغلاف.

بعد 10 محاولات فاشلة متتالية، مهما كان الإرسال، تُعطَّل نقطة استقبال ذلك التثبيت وتُترك إرسالاته المعلّقة. أصلح خادمك، ثم اضغط «التحقق من الـ Webhook» في منصة المطورين لإعادة تفعيل الإرسال. الإرسالات المتروكة من قبل لا يُعاد إرسالها.

تُترك الإرسالات المعلّقة أيضًا دون إرسال عندما توقف DZBuild تطبيقك، أو عندما تنزل خطة المتجر تحت الخطة الدنيا لتطبيقك، أو عندما يعمل تطبيق في وضع التجربة على متجر لا يملكه المطوّر. الاستثناء هو app.uninstalled الذي يُرسل رغم ذلك.

app.uninstalled​

عندما يزيل تاجر تطبيقك، تلغي DZBuild رمز التثبيت، وتترك كل الإرسالات المعلّقة لذلك التثبيت، وترسل حدث app.uninstalled واحدًا إذا كانت نقطة استقبال التثبيت مفعّلة ومتحقَّقًا منها:

{
"id": "evt_5a0c7e3b9d1f24681ace3579",
"event": "app.uninstalled",
"created_at": "2026-09-26T14:05:09+01:00",
"store_id": 1234,
"data": {
"install_id": 57,
"client_id": "dzapp_4e1b9c07d2a86f35e0b1",
"store_id": 1234,
"uninstalled_at": "2026-09-26T13:05:09+00:00"
}
}

الحقل uninstalled_at بتوقيت UTC. يتبع الإشعار جدول إعادة المحاولة نفسه، وتبقى نقطة الاستقبال مفعّلة يومًا واحدًا بعد الإزالة حتى تكتمل المحاولات. إذا أعاد التاجر تثبيت تطبيقك على ذلك المتجر قبل وصول الإشعار، يُترك الإشعار المعلّق حتى لا يلغي التثبيت الجديد.

بعد الإزالة، تجيب طلبات الواجهة البرمجية برمز التثبيت القديم بـ 401. تعامل مع بيانات المتجر كما تصفه قواعد الأمان.

لا X-DZ-Token للتطبيقات​

إشعارات webhook التي ينشئها التاجر في لوحة تحكم DZBuild تحمل أيضًا ترويسة X-DZ-Token، وهي نسخة صريحة من السرّ موجّهة لأدوات no-code. أما الإرسالات إلى التطبيقات فلا تحملها أبدًا: سرّ توقيع واحد يغطي كل المتاجر التي تثبّت تطبيقك، ونسخة منه في سجل الطلبات تسمح لأي شخص بتزوير أحداث لكل تلك المتاجر. تحقّق دائمًا من X-DZ-Signature.

هذه الصفحة لأدوات الذكاء الاصطناعيعرض بصيغة Markdownفتح في ChatGPTفتح في Claude