# `POST /v1/events`

متتبّع أحداث عام الغرض. استخدمه لأي شيء تحتاج تحليلات عليه ولا يكون اشتراكًا:

* مشاهدات الصفحة
* إرسال نماذج العملاء المحتملين (Lead)
* تتبع النقرات على صفحات الهبوط
* أحداث مخصصة يحددها التاجر

إن كانت بياناتك تحمل **هوية مستخدم** ذات معنى (عنوان بريد إلكتروني)، استخدم `/v1/signups` بدلًا منها: فهي تحتسب كل عنوان بريد مرة واحدة لكل متجر. أما أرقام الهاتف فلا يُزال تكرارها هناك. `/v1/events` للبيانات عالية الحجم بلا هوية.

## المصادقة[​](#المصادقة "رابط مباشر إلى المصادقة")

مفتاح عام + HMAC. نفس مخطط [`/v1/signups`](https://dzbuild.com/ar/ar/api-docs/public-key/signups.md). خادمك الخلفي يوقّع كل نداء.

أمران يعطّلان أي مفتاح جديد تمامًا

* **بوابة البرنامج التجريبي.** الـ API مُقيَّد بالبرنامج التجريبي في الإنتاج. المفتاح الذي لم تُسجّله DZBuild يُرجع `403 forbidden` `"API is in pilot mode; key not enrolled"` في كل نداء.
* **تأخير التفعيل.** المفتاح العام الجديد ليس صالحًا للاستعمال لحظة إنشائه. وإلى أن يُفعَّل لهذه النقاط ستحصل على `401 unauthorized` `"Invalid or revoked public key"`. راسل الدعم مباشرة بعد إنشائه لتفعيله.

إلغاء مفتاح عام لا يوقفه وحده: تظل النداءات الموقّعة مقبولة إلى أن يعطّله الدعم. راسل الدعم كلما ألغيت مفتاحًا عامًا.

## الجسم[​](#الجسم "رابط مباشر إلى الجسم")

```
{

  "name":       "lead_submitted",

  "properties": { "plan": "pro", "country": "DZ", "form": "footer" },

  "nonce":      "32-hex-single-use"

}
```

| الحقل        | النوع            | إلزامي | ملاحظات                                                                                                                                                                                                                                                                           |
| ------------ | ---------------- | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name`       | string ≤ 64 بايت | ✅     | اسم الحدث. `snake_case` مستحسن. الحد يُحسب بالبايت، فالحرف العربي يُحسب بايتين. والاسم المفقود أو الفارغ أو الأطول لا يُبلَّغ عنه: يُرجع النداء `202` رغم ذلك ويُسقَط الحدث.                                                                                                      |
| `properties` | object           |        | قيم قابلة لـ JSON. تُحفظ كما هي.                                                                                                                                                                                                                                                  |
| `nonce`      | 32-hex string    | ✅     | استعمل نفس قيمة ترويسة `X-DZ-Nonce` (الجسم الخالي من `nonce` يأخذ قيمة الترويسة). لاستخدام واحد، إلى الأبد. انظر [إزالة التكرار](#%D8%A5%D8%B2%D8%A7%D9%84%D8%A9-%D8%A7%D9%84%D8%AA%D9%83%D8%B1%D8%A7%D8%B1): نافذة الساعة هي الحارس الخارجي فقط؛ فالـ nonce لا يصلح مرتين أبدًا. |

كما يجب أن تحمل طلبات `POST` ترويسة **`Idempotency-Key`** (بحد أقصى 64 حرفًا، من مجموعة المحارف `[A-Za-z0-9_-:.]`). بدونها تحصل على `400 bad_request` ولا يدخل الحدث الطابور أصلًا. والـ nonce ذو الـ 32 خانة ست عشرية الذي تولّده أصلًا قيمة صالحة — أعد استعماله.

`name` يُخزَّن مع كل حدث، لكن لا توجد نقطة في الـ API ولا شاشة في لوحة التحكم تعرض الأحداث المخزَّنة: ما يمكنك قراءته، بمفتاح منصة، هو العدد الشهري في `GET /v1/usage` (`usage.event.total`). ومع ذلك التزم بمعجم صغير ثابت للأسماء، وتجنّب توليدها ديناميكيًا (مثل `viewed_product_42` سيء؛ استخدم `name="viewed_product"` مع `properties.product_id=42`).

## الاستجابة 202[​](#الاستجابة-202 "رابط مباشر إلى الاستجابة 202")

```
{

  "data": { "status": "queued", "kind": "event", "store_id": 13 },

  "meta": { "request_id": "...", "api_version": "v1", "edge": true }

}
```

نفس تدفّق الاشتراكات: يُقبَل في \~30 مللي ثانية، ويُخزَّن خلال 5 ثوانٍ تقريبًا.

## إزالة التكرار[​](#إزالة-التكرار "رابط مباشر إلى إزالة التكرار")

إزالة التكرار تعتمد على الـ **nonce وحده**، لكل متجر. لا توجد إزالة تكرار قائمة على البريد.

المكرَّر **يُسقَط بالكامل**: لا يُخزَّن شيء، ولا يتحرك أي عدّاد استهلاك، ولا شيء في الـ API يخبرك بحدوث ذلك. فالنداء أرجع `202` رغم ذلك، لأن إزالة التكرار تحدث بعد القبول. أي أن المكرَّر غير مرئي لك — ولّد دائمًا nonce جديدًا.

الـ nonce محروس على مرحلتين، بعمرين مختلفين:

* لمدة **ساعة واحدة** يُرفض الـ nonce المعاد استعماله رفضًا صريحًا بـ `401 unauthorized` `"Nonce reused"`.
* **وإلى الأبد** بعد ذلك، يُقبل الـ nonce المُعاد تدويره بـ `202` ثم يُهمَل بصمت — بلا خطأ، وبلا حدث.

## مثال عملي: تتبع مشاهدات الصفحة (Node.js، خادمي)[​](#مثال-عملي-تتبع-مشاهدات-الصفحة-nodejs-خادمي "رابط مباشر إلى مثال عملي: تتبع مشاهدات الصفحة (Node.js، خادمي)")

```
import crypto from 'node:crypto';



export async function trackEvent(name, properties = {}) {

  const KEY_ID = process.env.DZ_PUBLIC_KEY;

  const SECRET = process.env.DZ_SIGNING_SECRET;

  const nonce = crypto.randomBytes(16).toString('hex');

  const ts    = Math.floor(Date.now() / 1000).toString();

  const body  = JSON.stringify({ name, properties, nonce });

  const hash  = crypto.createHash('sha256').update(body).digest('hex');

  const sig   = crypto.createHmac('sha256', SECRET).update(`${KEY_ID}\n${nonce}\n${ts}\n${hash}`).digest('hex');



  await fetch('https://api.dzbuild.app/v1/events', {

    method: 'POST',

    headers: {

      'Authorization': `DZ-Public ${KEY_ID}`,

      'X-DZ-Timestamp': ts, 'X-DZ-Nonce': nonce, 'X-DZ-Signature': sig,

      'Idempotency-Key': nonce,          // إلزامية في كل POST

      'Content-Type': 'application/json',

    },

    body,

  });

}



// داخل Express middleware:

app.use((req, res, next) => {

  trackEvent('page_view', { path: req.path, ua: req.get('user-agent') }).catch(() => {});

  next();

});
```

لاحظ أننا **لا ننتظر** النداء من middleware مشاهدات الصفحة — fire-and-forget لكي لا يُحظر طلب المستخدم. الـ API يردّ في \~30 مللي ثانية على أي حال، لكن هذا يحمي من مشاكل شبكة عابرة.

## ما يُحسب من حصتك[​](#ما-يُحسب-من-حصتك "رابط مباشر إلى ما يُحسب من حصتك")

كل نداء `/v1/events` يصل فعلًا يزيد `usage.event.total` و `usage.event.billable`. لا يوجد "قراءات مجانية ثم كتابات قابلة للفوترة" هنا — الأحداث قابلة للفوترة من الطلب الأول. أما المكرَّرات (نفس الـ nonce) فلا تحرّك أي عدّاد، لأنها لا تُخزَّن أصلًا.

إن ارتفع حجم الأحداث، **اجمع من جانبك**: خزّنها في طابور خاص، واطلق نداء `/v1/events` لكل حدث منطقي على دفعات من 1 (الـ API لا يقبل الدُّفعات). وكل نداء يُحسب ضمن حد الدقيقة الخاص بالمتجر، المشترك بين كل مفاتيحه.

## الأخطاء[​](#الأخطاء "رابط مباشر إلى الأخطاء")

نفس `/v1/signups`. انظر [الأخطاء](https://dzbuild.com/ar/ar/api-docs/errors.md). والثلاثة التي توقع الناس على هذه النقطة تحديدًا:

| HTTP | الكود / الرسالة                                                         | السبب                                                                                       |
| ---- | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| 400  | `bad_request` "Idempotency-Key header is required for write requests"   | الترويسة مفقودة                                                                             |
| 400  | `bad_request` "Idempotency-Key must be `<=64` chars, `[A-Za-z0-9_-:.]`" | أطول من اللازم، أو يستعمل محارف خارج تلك المجموعة (محارف base64 وهي `+` و `/` و `=` مرفوضة) |
| 403  | `forbidden` "API is in pilot mode; key not enrolled"                    | المفتاح غير مُسجَّل في البرنامج التجريبي                                                    |

## متى **لا** تستعمل `/v1/events`[​](#متى-لا-تستعمل-v1events "رابط مباشر إلى متى-لا-تستعمل-v1events")

* لـ **طلبات الواجهة** — لوحة تحكم التاجر تسجّلها أصلًا، وتكرارها هنا يضخّم عدد أحداثك فقط. ولاحظ أن طلبات الواجهة **لا** تُطلق webhook `order.created` في API v1؛ الطلبات المُنشأة عبر `POST /v1/orders` وحدها تفعل ذلك (انظر [كتالوج الأحداث](https://dzbuild.com/ar/ar/api-docs/webhooks/events.md)).
* لـ **أحداث داخلية** لا تتعلق ببيانات التاجر (استهلاك CPU، نتائج cache). استعمل APM حقيقيًا.
* لـ **أحجام ضخمة** (أكثر من مليون حدث/يوم). ابنِ خط تحليلاتك واصدّر فقط التجميعات هنا.
