> ## Documentation Index
> Fetch the complete documentation index at: https://docs.hamla.io/llms.txt
> Use this file to discover all available pages before exploring further.

# واجهة الأحداث

> مرجع تفصيلي لكل حقل في مسارات إرسال الأحداث إلى حملة — الحدث المفرد، والدفعات، ومسار المتصفح — وما يفعله الحدث بعد تخزينه.

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

للشرح المبسّط اقرأ [أحداث حملة](/ar/how-to/hamla-events). هذه الصفحة هي العقد.

## المسارات

ثلاثة تحمل الأحداث، والرابع يحمل الشخص الذي وقعت له.

| المسار                      | يُستخدم لـ                 | المفتاح        | يبدأ حملة      |
| --------------------------- | -------------------------- | -------------- | -------------- |
| `POST /api/sdk/track`       | حدث حيّ واحد من خادمك      | **سري، مطلوب** | نعم            |
| `POST /api/sdk/track/batch` | حتى 500 حدث سابق (استيراد) | **سري، مطلوب** | **لا — أبداً** |
| `hamla.track()` في المتصفح  | فعل متعمَّد داخل صفحتك     | لا يحتاج       | نعم            |
| `POST /api/sdk/identify`    | من هو الشخص، وبماذا تناديه | اختياري        | —              |

الالتقاط التلقائي — مشاهدات الصفحات والتمرير ومؤقتات الخمول التي يجمعها السكربت وحده — مسار آخر. يغذّي التحليلات والمُطلِقات السلوكية، لكنه لا يفتح مُطلِق حدث أبداً. والتعمّد لا يُستنتج من اسم الحدث إطلاقاً.

## المصادقة

`track` و`track/batch` يتطلبان مفتاحاً **سرياً**:

```
Authorization: Bearer sk_live_xxx
```

أنشئه من **الإعدادات ← مفاتيح API**. والمفاتيح القديمة `hamla_live_…` تبقى صالحة.

المفتاح المنشور (`pk_live_…`) يجتاز المصادقة لكنه يُرفض بـ `403` على هذين المسارين: هو يُشحن داخل مصدر الصفحة، وقبوله هنا يعني أن أي زائر يستطيع اختلاق إيرادات. ولا يرسل أي من المسارين ترويسات CORS ولا يستجيب لـ `OPTIONS`، وهذا مقصود — الصفحة التي لا تستطيع اجتياز CORS لا تُغرى بتضمين مفتاح سري.

المفتاح يحدد نشاطاً تجارياً واحداً بالضبط. وحقل `businessId` في الجسم اختياري؛ فإن وُجد وخالف المفتاح رُفض الطلب بدل أن يُعاد كتابته بصمت.

## POST /api/sdk/track

يسجّل حدثاً واحداً وقع للتو.

```bash theme={null}
curl -X POST https://app.hamla.io/api/sdk/track \
  -H "Authorization: Bearer sk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "event": "subscription_started",
    "identity": { "email": "omar@raqmi.co", "platformCustomerId": "user_812" },
    "properties": { "plan": "pro", "seats": 3 },
    "value": 29,
    "currency": "JOD",
    "occurredAt": "2026-08-23T09:41:00Z",
    "idempotencyKey": "invoice_9911",
    "traits": { "plan": "pro", "subscription_status": "active" },
    "tags": ["paying"]
  }'
```

### الحقول

| الحقل            | النوع       |           | ملاحظات                                                                                                             |
| ---------------- | ----------- | --------- | ------------------------------------------------------------------------------------------------------------------- |
| `event`          | نص          | **مطلوب** | اللحظة، بمفرداتك أنت. تُقلَّم المسافات، والطول 1–64 حرفاً. تطابقها المُطلِقات والشرائح حرفياً.                      |
| `identity`       | كائن        | **مطلوب** | واحد على الأقل من `email` أو `phone` أو `platformCustomerId`. انظر أدناه.                                           |
| `occurredAt`     | نص \| رقم   | اختياري   | ISO 8601 أو ميلي ثانية منذ الحقبة. الافتراضي: الآن. الماضي مسموح، وأكثر من 5 دقائق في المستقبل مرفوض.               |
| `type`           | نص          | اختياري   | ماذا **يعني** الحدث، من الأنواع السبعة أدناه. إن تركته استنتجته حملة.                                               |
| `value`          | رقم         | اختياري   | رقم منتهٍ. يُحتسب ضمن إيراد جهة الاتصال — انظر **ماذا يعني الحدث**.                                                 |
| `currency`       | نص          | اختياري   | 1–8 أحرف، مثل `JOD` أو `SAR` أو `USD`.                                                                              |
| `properties`     | كائن        | اختياري   | أي تفاصيل JSON تستحق الحفظ مع الحدث. الحد 32,768 بايت بعد التحويل. تُخزَّن متداخلة ولا تُفرد في جذر الحمولة.        |
| `tags`           | مصفوفة نصوص | اختياري   | تُضاف دمجاً إلى جهة الاتصال ولا تستبدل وسومها القائمة. حتى 50 وسماً، كل واحد 1–64 حرفاً.                            |
| `traits`         | كائن        | اختياري   | قيم الحالة الراهنة تُكتب في الحقول المخصصة. القيم نص أو رقم أو منطقي أو null. المفاتيح غير المعروفة تُنشأ تلقائياً. |
| `idempotencyKey` | نص          | اختياري   | 1–200 حرف. إعادة الإرسال بالمفتاح نفسه عملية لاغية مسجَّلة.                                                         |
| `businessId`     | نص          | اختياري   | للتحقق المتقاطع فقط. يجب أن يطابق نشاط المفتاح.                                                                     |
| `visitorId`      | نص          | اختياري   | يربط الحدث بجلسة متصفح معروفة، حين يملك خادمك الكوكي. حتى 128 حرفاً.                                                |

### حقل `identity`

| الحقل                | النوع     | ملاحظات              |
| -------------------- | --------- | -------------------- |
| `email`              | نص        | 3–320 حرفاً.         |
| `phone`              | نص \| رقم | بأي صيغة تخزّنها.    |
| `platformCustomerId` | نص \| رقم | معرّف المستخدم لديك. |

**واحد يكفي**، وليس شرطاً أن يكون البريد — نشاط لا يجمع إلا أرقام الهواتف يرسل `phone` وحده، دائماً. أما الحدث بلا هوية أصلاً فلا يخصّ أحداً ويُرفض.

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

والهوية تعرُّف لا إذن: رقم الهاتف يتيح لحملة أن تعرف صاحبه، ولا يكون أبداً موافقةً على إرسال رسالة نصية أو واتساب إليه.

### الرد

```json theme={null}
{
  "success": true,
  "message": "Event recorded",
  "data": {
    "contactId": "cm4x...",
    "event": "subscription_started",
    "isNew": false,
    "deduped": false,
    "interpreted": { "type": "purchase", "countedRevenue": 29 },
    "warnings": ["custom field \"businesses_count\" needs a number — skipped"]
  }
}
```

| الحقل         | المعنى                                                                                                                          |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `contactId`   | جهة الاتصال التي نزل عليها الحدث.                                                                                               |
| `isNew`       | `true` حين ينشئ هذا النداء جهة الاتصال.                                                                                         |
| `interpreted` | ما قررت حملة أن الحدث يعنيه، وكم احتسبت من `value`.                                                                             |
| `deduped`     | `true` حين يطابق `idempotencyKey` حدثاً مسجَّلاً سابقاً. والوسوم والسمات تُطبَّق رغم ذلك، فتصل إعادة المحاولة إلى الحالة نفسها. |
| `warnings`    | لا تظهر إلا عند أمر إرشادي، غالباً سمة لم تطابق نوع حقلها.                                                                      |

### الأخطاء

| الحالة | متى                                                                                                  |
| ------ | ---------------------------------------------------------------------------------------------------- |
| `400`  | JSON غير سليم، أو مخالفة للبنية، أو غياب الهوية، أو `occurredAt` خاطئ، أو `properties` أكبر من الحد. |
| `401`  | لا توجد ترويسة `Authorization`، أو المفتاح غير صالح أو ملغى. والحالتان تُجابان بنصّين مختلفين.       |
| `403`  | مفتاح منشور، أو `businessId` مخالف، أو النشاط غير مفعّل.                                             |
| `404`  | النشاط التجاري غير موجود.                                                                            |
| `429`  | تجاوز حد المعدل. أعد المحاولة بعد النافذة المذكورة في ترويسات الرد.                                  |

## POST /api/sdk/track/batch

يسجّل حتى 500 حدث وقعت سابقاً. المفردات نفسها، وقواعد الحقول نفسها، والمفتاح السري نفسه.

```bash theme={null}
curl -X POST https://app.hamla.io/api/sdk/track/batch \
  -H "Authorization: Bearer sk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "events": [
      { "event": "purchase", "identity": { "email": "omar@raqmi.co" },
        "value": 120, "currency": "JOD",
        "occurredAt": "2025-11-02T10:00:00Z", "idempotencyKey": "order_5512" },
      { "event": "purchase", "identity": { "email": "lina@example.com" },
        "value": 80, "currency": "JOD",
        "occurredAt": "2025-12-14T18:20:00Z", "idempotencyKey": "order_5610" }
    ]
  }'
```

مصفوفة `events` تقبل من 1 إلى 500 عنصر. وكل عنصر يقبل كل حقول `track` عدا `visitorId` — فالتاريخ المُعاد تشغيله لا يحمل جلسة متصفح.

### ما لا يفعله الاستيراد عمداً

|                                |                                                                                                           |
| ------------------------------ | --------------------------------------------------------------------------------------------------------- |
| **لا تنطلق أي حملة.**          | الأحداث المستوردة لا تحمل أي علامة مُطلِق أصلاً. ألف عملية شراء عمرها سنة يجب ألّا ترسل ألف رسالة الليلة. |
| **لا انفجار في دخول الشرائح.** | إعادة حساب الشرائح الناتجة عن استيراد تجري بالجملة: العضوية تتحرك، ومُطلِقات الدخول تبقى صامتة.           |
| **لا تلويث لنسب المصدر.**      | التاريخ المُعاد من قاعدة بيانات ليس وصولاً من الويب، فحالة الزائر تبقى كما هي.                            |

وكل ما **ينبغي** للاستيراد فعله يحدث: جهات الاتصال تُطابَق أو تُنشأ، والأنشطة تنزل بتاريخها الحقيقي في `occurredAt`، وإحصاءات أول وآخر وعدد المرات تتحدث (والصفوف غير المرتبة تُعالَج)، والسمات والوسوم تُطبَّق، و`value` يُحتسب ضمن الإيراد.

### الرد

العناصر تفشل منفردة — الصف الخاطئ يبلّغ عن موضعه وسببه، وبقية الدفعة تنزل.

```json theme={null}
{
  "success": true,
  "message": "Batch recorded with failures",
  "data": {
    "received": 500,
    "recorded": 497,
    "deduped": 2,
    "failed": 1,
    "failures": [{ "index": 314, "error": "occurredAt cannot be in the future" }],
    "warnings": ["events[12]: custom field \"plan\" needs a number — skipped"]
  }
}
```

حقل `warnings` محدود بخمسين مدخلاً حتى لا يطغى عمود واحد خاطئ التسمية على الرد كله.

أعطِ كل صف `idempotencyKey` ويصبح تشغيل الاستيراد كاملاً مرة أخرى آمناً.

## POST /api/sdk/identify

يقول **من هو الشخص** — وبماذا تناديه. رفيق `track`: هوية وملف، بلا حدث.

```bash theme={null}
curl -X POST https://app.hamla.io/api/sdk/identify \
  -H "Authorization: Bearer sk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "identity": { "email": "omar@raqmi.co" },
    "profile": { "name": "عمر آل صالح", "country": "JO" },
    "method": "signup"
  }'
```

يطابق أو يُنشئ: المجهول يصير جهة اتصال، والمعروف يُثرى. وآمن أن تناديه عند كل تسجيل دخول — حقول الملف تُملأ فراغاتها ولا تُستبدل أبداً.

### الحقول

| الحقل        | النوع |           | ملاحظات                                                                                                                                                                    |
| ------------ | ----- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `identity`   | كائن  | **مطلوب** | الحقول الثلاثة نفسها بالقواعد نفسها كما في `track` أعلاه.                                                                                                                  |
| `profile`    | كائن  | اختياري   | الاسم والموقع. انظر أدناه.                                                                                                                                                 |
| `traits`     | كائن  | اختياري   | قيم الحالة الراهنة ← حقول مخصصة. **بمفتاح سري فقط** — سمات المُنادي بلا مفتاح تُتجاهل مع تنبيه في `warnings`، لأن ادّعاءً مجهولاً عن باقة أحدهم ليس حقيقة.                 |
| `method`     | نص    | اختياري   | تسميتك للباب الذي دخل منه: `signup` أو `checkout` أو `google`. حتى 64 حرفاً، ويُسجَّل على الإشارة. وهو ما يفرّق بين «لم يسجّل أحد اليوم» و«توقّف نداء identify عند الدفع». |
| `visitorId`  | نص    | اختياري   | كوكي الزائر في حملة، يربط تصفّحه المجهول — ومعه نقرة الإعلان التي جاءت به — بهذا الشخص. حزمة المتصفح ترسله عنك، والخادم يستطيع قراءة الكوكي وتمريره.                       |
| `businessId` | نص    | اختياري   | للتحقق المتقاطع فقط. يجب أن يطابق نشاط المفتاح.                                                                                                                            |

### حقل `profile`

| الحقل                    | ملاحظات                                                                                                                                                                         |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name`                   | الاسم كاملاً في نص واحد. يُقسَّم عند الوصول: الكلمة الأولى ← `firstName` والبقية ← `lastName`، فيُنادى صاحب «سارة آل عتيبي» باسم **سارة**.                                      |
| `firstName` / `lastName` | أرسلهما بدلاً منه إن كانت قاعدة بياناتك تفصلهما أصلاً. وإن أرسلتهما مع `name` غلبا حقلاً حقلاً.                                                                                 |
| `country` و `city`       | نص حر، تستخدمه شرائح الموقع.                                                                                                                                                    |
| `language`               | اللغة التي تُكتب بها الحملات الموجّهة لهذا الشخص.                                                                                                                               |
| `timezone`               | منطقة IANA، مثل `Asia/Amman`. تُبقي خطوة الحملة المؤجَّلة خارج ليل المستلم. حزمة المتصفح تملؤها عنك، والدولة وحدها لا تجيب عنها لأحد في أمريكا أو البرازيل أو كندا أو أستراليا. |

وسبب واحد يجعل إرسال الاسم يستحق: `{{contact.firstName}}` في الحملة. بدونه تفتتح كل رسالة بـ «مرحباً صديقي».

### المصادقة، على غير العادة

هذا هو مسار الكتابة الوحيد الذي **يخدم أيضاً من لا مفتاح لديه**، لأن حزم المتصفح المنشورة لا تستطيع إرسال ترويسة `Authorization` ولا تُحدَّث بعد أن تصل ذاكرة CDN لدى العميل. ومع ذلك يستحق المفتاح الإرسال من الخادم: يحصر الكتابة في نشاطك ويجعلها منسوبة، كما أن `traits` تشترطه.

### الرد

```json theme={null}
{
  "success": true,
  "message": "New contact created and linked",
  "data": {
    "contactId": "cm4x...",
    "isNew": true,
    "linkedVisitor": true,
    "subscriptions": { "email": true, "sms": false, "whatsapp": false, "push": false },
    "warnings": []
  }
}
```

قيمة `linkedVisitor` تكون `true` حين يكون هذا النداء هو ما ربط المتصفح المجهول بالشخص — اللحظة التي تتوقف فيها زياراته السابقة، والحملة التي جاءت به، عن كونها مجهولة. و`warnings` غائبة حين يصل كل شيء كما أُرسل.

## ‏hamla.track() — مسار المتصفح

للأفعال المتعمَّدة التي تحدث فعلاً داخل الصفحة. بلا مفتاح، لأنه لا يوجد مفتاح يمكن شحنه بأمان إلى متصفح.

```js theme={null}
hamla.track('download_ebook', { asset: 'guide.pdf' });
hamla.track('appointment_booked', { date: '2026-04-15', price: 100 });
```

يُستخدم اسم الحدث حرفياً. ولأن كود الصفحة نفسه اختار إرساله، فهو يُعدّ متعمَّداً **ويستطيع** بدء حملة — بخلاف أحداث الالتقاط التلقائي المسافرة في الدفعة نفسها.

أما ما يتعلق بالمال أو بسجلاتك الخاصة فمكانه مسار الخادم، حيث يوجد المفتاح وحيث لا يستطيع زائر تعديل الوقائع.

## ماذا يفعل الحدث بعد تخزينه

| السطح                           | أين                                                        | السلوك                                                                                                                                                         |
| ------------------------------- | ---------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **مُطلِق الحملة**               | متى تبدأ الحملة؟ ← حدث في تطبيق ← أحداث تطبيقك ← اسم الحدث | ينطلق لكل حدث مطابق من الآن فصاعداً. والتسجيل مرة واحدة لكل جهة اتصال ما لم يُفعَّل **يمكن الدخول مرة أخرى لاحقاً**. ويمكن كتابة الاسم قبل وصول الحدث أول مرة. |
| **قواعد الشرائح**               | بنّاء الشرائح ← الأحداث                                    | حقلان لكل اسم حدث — انظر أدناه. والمجموعة تظهر بعد وجود أول حدث.                                                                                               |
| **هدف التحويل / نهاية التسلسل** | نهاية التسلسل ← عند تحقق حدث                               | من يُطلقه يتوقف عن استقبال البقية، ويُفحص قبل كل إرسال. ويُقفل بعد النشر.                                                                                      |
| **مرحلة الرحلة**                | ملف جهة الاتصال                                            | للأسماء المعروفة أدناه فقط.                                                                                                                                    |
| **السجل الزمني**                | ملف جهة الاتصال                                            | كل حدث، محفوظاً بوقته في `occurredAt`.                                                                                                                         |
| **التحليلات**                   | التقارير ونسب المصدر                                       | يغذّي التفاعل والتقييم والمقاييس وتمرير التحويلات إلى Meta.                                                                                                    |

### حقول الشرائح

| المسار               | يقرأ            | المعاملات                                                                                               |
| -------------------- | --------------- | ------------------------------------------------------------------------------------------------------- |
| `event.<name>`       | متى حدث آخر مرة | معاملات التاريخ — `exists` (سبق أن حدث)، `not_exists` (لم يحدث أبداً)، `within_days`، `older_than_days` |
| `event.<name>.count` | كم مرة حدث      | معاملات الأرقام                                                                                         |
| `event.<name>.first` | متى حدث أول مرة | معاملات التاريخ. مُقيَّم لكنه غير معروض في قائمة الاختيار.                                              |

جهة الاتصال التي لا تملك الحدث تُقيَّم كغير معرّفة في **كل** المقاييس، بما فيها `count`. لذلك «أقل من 5 مرات، بما في ذلك ولا مرة» هي `count < 5` أو `not_exists`، وليست صفراً صامتاً.

### الأسماء التي تحرّك مرحلة الرحلة

أسماؤك الخاصة تُسجَّل وتُستخدم كاملة، لكنها لا تحرّك المرحلة. استعِر من هذه حين تناسبك:

| المرحلة  | اسم الحدث                                                                                                                                                               |
| -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| الوعي    | `page_view`، `inbound_message`، `lead_submitted`، `form_submitted`، `email_opened`، `manual_contact_created`، `imported`                                                |
| التفكير  | `email_clicked`، `whatsapp_replied`، `product_viewed`، `add_to_cart`، `booking_started`، `pricing_page_viewed`                                                          |
| القرار   | `checkout_started`، `appointment_booked`، `subscription_started`، `trial_started`، `purchase`، `deal_won`، `booking_confirmed`، `reservation_confirmed`، `invoice_paid` |
| الاحتفاظ | `order_delivered`، `appointment_attended`، `subscription_renewed`، `repeat_purchase`، `product_reviewed`                                                                |
| المناصرة | `referral_made`، `review_5_star`، `ugc_posted`، `affiliate_signup`                                                                                                      |

## ماذا يعني الحدث

`event` كلمتك أنت. و`type` كلمتنا نحن، وهي سبع كلمات لا غير.

لست مضطراً لإرسال `type` أبداً — تستنتج حملة من المبلغ ومن الأسماء التي تعرفها
أصلاً. وحين ترسله لا يبقى شيء للتخمين.

| `type`             | ماذا يعني                | الإيراد       | يُحتسب طلباً |
| ------------------ | ------------------------ | ------------- | ------------ |
| `purchase`         | مبلغ مستلم               | **+ `value`** | نعم          |
| `refund`           | مبلغ مُعاد               | **− `value`** | لا           |
| `booking`          | التزام قبل الدفع         | —             | لا           |
| `lead`             | تم التقاط بيانات التواصل | —             | لا           |
| `checkout_started` | بدأ ولم يُكمل            | —             | لا           |
| `fulfilled`        | تم التسليم               | —             | لا           |
| `cancelled`        | انتهى                    | —             | لا           |

```json theme={null}
{
  "event": "treatment_completed",
  "type": "purchase",
  "identity": { "phone": "+962790000000" },
  "value": 100, "currency": "JOD"
}
```

عيادة تسميه `treatment_completed`، ومدرسة تسميه `lesson_finished`، وكلاهما
`purchase`. اسمك أنت لا يُترجَم ولا يُستبدَل ولا يُقارَن بقائمة — يُحفظ كما
أرسلته بالضبط، ويبقى هو ما تُكتب به تقاريرك وشرائحك ومُطلِقاتك.

<Note>
  **`booking` هو النوع الذي يستحق الانتباه.** كل المفردات التحليلية الأخرى صُمِّمت
  للمتاجر الإلكترونية، حيث أن تقول نعم هو أن تدفع. الموعد المؤكَّد والعقد الموقَّع
  والتجربة المجانية التزامات حقيقية لا يتحرك فيها مال — فهي `booking` لا
  `purchase`، ولن تظهر في الإيراد ولن ترفع متوسط قيمة الطلب لديك. وحين يحمل أحدها
  مبلغاً فعلاً، أرسل المبلغ وتحتسبه حملة.
</Note>

### إن لم ترسل شيئاً

تقرر حملة بهذا الترتيب:

1. **`type` إن أرسلته.** يفوز دائماً.
2. **وجود `value`.** المال تحرَّك، مهما سميت الحدث. والمبلغ السالب استرداد.
3. **اسم تعرفه حملة أصلاً.** محفوظ كي تبقى التكاملات المكتوبة قبل وجود `type`
   تعمل تماماً كما كانت.

وإن لم ينطبق أي منها، يُسجَّل الحدث ويبقى قابلاً للتشريح ويستطيع أن يبدأ حملة —
لكنه لا يدّعي أنه إيراد.

وهذه الأسماء كاملة. لست بحاجة إليها — `type` يقول الشيء نفسه بأي مفردات — لكن
لا شيء ترسله اليوم يتغير معناه بسببها.

| `type`             | أسماء ما زالت حملة تعرفها                                                                                 |
| ------------------ | --------------------------------------------------------------------------------------------------------- |
| `purchase`         | `deal_won`، `invoice_paid`، `purchase`، `repeat_purchase`، `subscription_renewed`، `subscription_started` |
| `booking`          | `appointment_booked`، `booking_confirmed`، `reservation_confirmed`، `trial_started`                       |
| `lead`             | `form_submitted`، `hamla_form.submitted`، `hamla_link.submitted`، `lead_submitted`                        |
| `checkout_started` | `add_to_cart`، `booking_started`، `checkout_started`                                                      |
| `fulfilled`        | `appointment_attended`، `order_delivered`                                                                 |
| `cancelled`        | `appointment_cancelled`، `subscription_canceled`، `subscription_expired`                                  |

<Warning>
  **لا يُحتسب مبلغ في صمت أبداً.** إن أرسلت `value` ولم تستطع حملة تحديد معناه،
  قال لك الرد ذلك في `warnings` بدل أن يخزنه ويمضي. وكل رد يعيد إليك `interpreted`
  أيضاً، فترى القرار من أول طلب لا في تقرير بعد أسابيع.
</Warning>

## السمات والحقول المخصصة

`traits` تكتب **الحالة الراهنة** لجهة الاتصال، والحدث يسجّل ما جرى. أرسل الاثنين في نداء واحد.

وأول مرة يصل فيها مفتاح سمة جديد، تنشئ حملة الحقل المخصص المقابل وتستنتج نوعه: `3` يصبح رقماً، و`true` منطقياً، و`"2026-08-08"` تاريخاً، وما عدا ذلك نصاً. ويظهر في **العملاء ← إدارة العملاء ← الحقول المخصصة**، وتستطيع الشرائح التصفية عليه فوراً.

قاعدتان، وكلتاهما تظهران في مصفوفة `warnings`:

* **المفاتيح بصيغة `snake_case`** — `businesses_count` لا `Businesses-Count`.
* **الحقل يحتفظ بنوعه الأول.** فبعد أن يصبح `businesses_count` رقماً، تُتجاهَل قيمة `"lots"` لاحقاً بدل إفساد المخزَّن.

## الحدود

|                  |                                                                                        |
| ---------------- | -------------------------------------------------------------------------------------- |
| اسم الحدث        | 1–64 حرفاً                                                                             |
| `properties`     | 32,768 بايت بعد التحويل                                                                |
| `tags`           | 50 لكل طلب، وكل وسم 1–64 حرفاً                                                         |
| `idempotencyKey` | 1–200 حرف                                                                              |
| `occurredAt`     | أي وقت ماضٍ، وحتى 5 دقائق في المستقبل                                                  |
| حجم الدفعة       | 500 حدث لكل طلب                                                                        |
| حد المعدل        | 120 طلباً في الدقيقة لكل نشاط تجاري — وطلب الدفعة يكلّف وحدة واحدة مهما بلغ عدد أحداثه |

ومحدِّد المعدل يفشل مفتوحاً: إن تعذّر الوصول إليه سُمح بالطلبات بدل رفضها.

## حزمة Node

`npm install @gethamla/node` تغلّف المسارات نفسها — ولا شيء تفعله غير متاح عبر HTTP عادي.

```ts theme={null}
import { Hamla } from '@gethamla/node';
const hamla = new Hamla(); // يقرأ HAMLA_API_KEY

hamla.identify({ identity: { email: 'omar@raqmi.co' }, profile: { name: 'Omar Al Saleh' } });
hamla.track({ event: 'subscription_started', identity: { email: 'omar@raqmi.co' } });

await hamla.trackNow({ event: 'purchase', identity: { email: 'omar@raqmi.co' } }); // بانتظار الرد
await hamla.trackBatch(historicalEvents);                                          // إعادة تشغيل مجزّأة
await hamla.flush();                                                               // بيئات serverless: أفرغ الطابور قبل التجميد
```

`track` و`identify` ترسلان دون انتظار ولا ترميان استثناءً أبداً — فنداء تسويقي يجب ألّا يكسر عملية دفع. و`trackNow` تعيد `DeliveryResult` حين تريد أن تعرف. و`trackBatch` تعيد ترقيم مواضع الفشل بحسب مصفوفتك أنت، وترفض `visitorId` للسبب نفسه الذي يرفضه المسار.
