اقرأ هذا أولاً. ما تحته شارة مُنفَّذ يعمل في الكود اليوم.
وما تحته مواصفة شكلٌ نلتزم به ولم يُبنَ بعد،
ويُبنى لنسختك عند الطلب. ننشره هنا قبل البناء حتى تخطّط عليه.
المنفَّذ اليوم نقطة واحدة: /health. وكل ما عداها مواصفة، ومنه المصادقة والحدود والويبهوكس.
وكل نقطة في الصفحة تحمل شارة حالتها.
بداية في خمس دقائق
النداء الوحيد الذي يعمل اليوم بلا مفتاح ولا إعداد هو فحص الحالة. ابدأ به لتتأكّد أن نسختك تعمل وأن الشبكة تصل إليها.
يردّ 200 بجسم ثابت. لا يحمل بيانات ولا يحتاج مصادقة، فيصلح لأداة مراقبة الخدمة عندك.
# استبدل النطاق بنطاق خادمك
curl https://bot.example.sa/health
# الرد
{ "ok": true }
ثم اطلب مفتاحاً من فريق التأسيس، واحفظه في متغيّر بيئة. لا تضعه في شيفرة الواجهة الأمامية، لأن المفتاح يمنح صلاحية كاملة على سجلّات عملائك.
export NAATEQ_URL=https://bot.example.sa export NAATEQ_KEY=nq_live_… curl "$NAATEQ_URL/api/v1/conversations?limit=1" \ -H "Authorization: Bearer $NAATEQ_KEY"
العنوان الأساسي هو نطاقك أنت
لكل عميل نسخته الخاصة من ناطق، فلا يوجد عنوان واحد لكل العملاء. عنوانك هو النطاق الذي رُكّب عليه نظامك:
https://<نطاقك>/api/v1
ومع التشغيل على خادمك تبقى محادثات عملائك داخل منشأتك، إلا قراءة المستندات والصور فتحتاج مزوّداً سحابياً، كما تشرح صفحة الأمان. فالخادم والمفتاح وسجلّ الوصول كلها عندك.
وفي المقابل، استمرار الخادم في العمل مسؤوليتك. لا نلتزم بنسبة تشغيل لخادم لا نديره، وما نلتزم به مكتوب في صفحة الدعم.
مفتاح واحد في الترويسة
كل طلب يحمل مفتاحك في Authorization. المفتاح يُعرض مرة واحدة عند الإنشاء ولا يُسترجع بعدها. إن ضاع فأنشئ غيره وألغِ القديم.
| البادئة | النطاق | أين تستعمله |
|---|---|---|
| nq_live_ | قراءة وكتابة | خادمك الخلفي حصراً |
| nq_ro_ | قراءة فقط | لوحات وتقارير |
| nq_test_ | قراءة وكتابة على بيانات وهمية | للتطوير، ولا يرسل أي رسالة واتساب |
مفتاح لكل تكامل. فإلغاء مفتاح لا يوقف البقية، والسجلّ يُظهر ما فعله كل تكامل.
# مفتاح ناقص أو ملغى
HTTP/1.1 401 Unauthorized
{
"error": {
"type": "authentication_error",
"code": "key_revoked",
"message": "المفتاح مُلغى. اطلب مفتاحاً جديداً.",
"request_id": "req_01J8…"
}
}
المحادثات والرسائل
كل محادثة معزولة برقم العميل، والعزل مبنيّ في النظام ويعمل دائماً، فلا يصل أي نداء إلى سجلّ عميل آخر.
| المعامل | النوع | الوصف |
|---|---|---|
| stage | enum | inquiry · quoted · signed · delivered |
| updated_after | ISO 8601 | ما تغيّر بعد وقت معيّن، وهو الأنسب للمزامنة الدورية |
| limit | integer | الافتراضي 50، الأقصى 200 |
| cursor | string | من next_cursor في الرد السابق |
المراحل الأربع أعلاه هي التي يسجّلها النظام في سجل العميل عند كل انتقال.
يرسل رسالة نصية باسم نشاطك. وتمرّ بفلتر المخرجات نفسه الذي يمرّ به ردّ النظام، فما يُمنع في ردود النظام يُمنع في الواجهة أيضاً.
POST /api/v1/messages
Authorization: Bearer $NAATEQ_KEY
Idempotency-Key: a3f1c9de-…
{
"to": "966501234567",
"text": "جهّزنا لك العرض، تحب نرسله؟"
}
العملاء
سجلّ العميل هو نفسه الذي يكتبه النظام، والحقول أدناه منقولة منه كما هي. فما تقرؤه من الواجهة هو ما يراه النظام لحظة الردّ.
| الحقل | النوع | الوصف |
|---|---|---|
| number | string | رقم الجوال بصيغة دولية بلا رموز |
| name | string | null | null حتى يذكر العميل اسمه بنفسه |
| stage | enum | المرحلة الحالية من الأربع |
| project | string | null | وصف المشروع كما استخلصه النظام |
| quote | object | null | amount · project · status · date |
| contract | object | null | النطاق داخله وخارجه · تاريخ التسليم · خطة الدفع · تاريخ التوقيع |
| milestones | array | مراحل التنفيذ المتّفق عليها |
| tickets | array | type · detail · status |
| payment_status | string | null | حالة التحصيل الحالية |
| payment_links | array | روابط الدفع الصادرة وحالتها |
| next_payment_due | date | null | تاريخ الدفعة القادمة |
| delivered | object | null | تاريخ التسليم ومدّة الضمان بالأشهر |
| created_at | ISO 8601 | لحظة أول رسالة |
| updated_at | ISO 8601 | وقت آخر تغيير، استعمله في updated_after |
{
"number": "966501234567",
"name": "محمد الحربي",
"stage": "quoted",
"project": "مجلس 12 متر — تفصيل",
"quote": {
"amount": 4200,
"project": "مجلس 12 متر — تفصيل",
"status": "sent",
"date": "2026-08-04"
},
"contract": null,
"milestones": [],
"tickets": [],
"payment_links": [],
"next_payment_due": null,
"created_at": "2026-08-04T09:12:44.108Z",
"updated_at": "2026-08-04T09:31:02.771Z"
}
المستندات
عروض الأسعار والعقود. يُبنى رقم المستند من بادئة نشاطك وحرف النوع والتاريخ وتسلسل من ثلاث خانات:
HT-Q-260804-058 │ │ │ └── تسلسل اليوم │ │ └────────── YYMMDD │ └─────────────── Q عرض سعر · C عقد └──────────────────── بادئة نشاطك
⚠ البادئة تتغيّر بتغيّر حزمة النشاط، فـلا تكتبها ثابتةً في تكاملك وطابِق النمط كاملاً.
يُرجع بيانات المستند وسطوره. وللملفّ نفسه أضف /pdf، فيردّ 302 إلى رابط موقّع صالح لمدة 15 دقيقة.
| الحقل | النوع | الوصف |
|---|---|---|
| doc_number | string | المعرّف الفريد، ونمطه مشروح أعلاه |
| kind | enum | quote · contract |
| lines | array | سطور التسعير: الوصف والكمية وسعر الوحدة |
| total | integer | الإجمالي بالريال |
| payment_plan | string | ملخّص خطة الدفع المحسوبة من الإجمالي |
| approved_by_owner | boolean | العقد لا يُرسَل للعميل قبل أن تصير true |
المدفوعات
المبالغ بالهللات دائماً، وبأعداد صحيحة. فـ4,200 ريال تُكتب 420000.
الخلط بين الريال والهللة من أكثر أسباب الأخطاء المالية في تكاملات الدفع، لذلك لا يتعامل النظام داخلياً مع كسور عشرية، والتحويل يحدث عند الأطراف فقط.
ويحسب النظام خطة الدفع من الإجمالي. الدفعة الأخيرة تُحسب بطرح ما سبقها من الإجمالي، فيساوي مجموع الدفعات الإجمالي بالضبط مهما كان التقريب، ولا يظهر فرق ريال عند التحصيل:
| الدفعة | النسبة | تُستحقّ عند |
|---|---|---|
| الأولى | 50% | التوقيع وبدء العمل |
| الثانية | 30% | التسليم الأولي |
| الأخيرة | 20% | التسليم النهائي، وتُحسب بالطرح |
يُرجع حالة كل بوابة في نسختك: هل هي مهيّأة، ووضعها، وحدّها الأدنى، وأسماء حقول اعتمادها.
[
{ "id": "moyasar", "label_ar": "مُيسّر",
"configured": true, "mode": "live",
"min_amount_minor": 100,
"fields": ["secret_key"] },
{ "id": "paylink", "label_ar": "Paylink",
"configured": false, "mode": null,
"min_amount_minor": 500,
"fields": ["api_id", "secret_key"] },
{ "id": "tap", "label_ar": "تاب",
"configured": false, "mode": null,
"min_amount_minor": 100,
"fields": ["secret_key"] }
]
نتحقق من حدّ Paylink الأدنى (5.00 ريال) قبل إرسال الطلب إليهم، فيصلك الخطأ فوراً من خادمك.
المبلغ يُقرأ دائماً من جدول دفعات العقد المعتمَد.
وهذا قيد في الكود، فلا توجد أداة بيد النموذج تقبل مبلغاً.
والواجهة تخضع للقيد نفسه، لذلك تُرسل instalment وليس amount.
| الحقل | النوع | الوصف |
|---|---|---|
| doc_number | string مطلوب | عقد معتمَد من المالك |
| instalment | integer مطلوب | رقم الدفعة في جدول العقد، ومنها يُقرأ المبلغ |
| provider | enum | moyasar · paylink · tap، والافتراضي أول بوابة مهيّأة |
| idempotency_key | uuid | يمنع رابطين لدفعة واحدة |
{
"provider": "moyasar",
"url": "https://…",
"ref": "inv_01J8…",
"status": "pending",
"amount_minor": 210000,
"currency": "SAR"
}
الويبهوكس
تسجّل رابطاً واحداً، فتصلك الأحداث المشتركة عليه بصيغة JSON مع توقيع X-Naateq-Signature.
الأحداث كلّها مواصفة، ولا يُرسَل أي منها بعد.
message.receivedوصلت رسالة من عميل. تحمل النص ورقم المرسل والنية المصنَّفة.reply.sentردّ النظام. تحمل الرد والمصدر (آلي · أداة · بشري) وزمن المعالجة.stage.changedانتقل العميل بين المراحل الأربع. تحمل المرحلة السابقة والجديدة.quote.issuedصدر عرض سعر. تحمل رقم المستند والإجمالي وخطة الدفع.contract.approvedوافق المالك على إرسال العقد. تحمل رقم العقد وقيمته.payment.recordedأُثبتت دفعة. تحمل رقم الدفعة والمبلغ بالهللات والبوابة.handoff.requestedطُلب تصعيد لموظف بشري. تحمل سبب التصعيد.guard.blockedحجب فلتر المدخلات رسالة. تحمل نوع المحاولة.POST /your-endpoint
X-Naateq-Signature: t=1785838800,v1=5f2b…
X-Naateq-Delivery: whd_01J8…
{
"event": "quote.issued",
"created_at": "2026-08-04T09:31:02.771Z",
"data": {
"doc_number": "NQ-Q-260804-058",
"customer": { "number": "966501234567", "name": "محمد الحربي" },
"total": 420000,
"currency": "SAR",
"payment_plan": "50% عند التوقيع • 30% عند التسليم الأولي • 20% عند التسليم النهائي"
}
}
| البند | القيمة |
|---|---|
| النجاح | أي رمز 2xx خلال المهلة |
| مهلة نقطتك | 5 ثوانٍ |
| إعادة المحاولة | 5 محاولات بتباعد أُسّي خلال 24 ساعة |
| الترتيب | غير مضمون، فرتّب حسب created_at |
| التكرار | ممكن، فاستعمل X-Naateq-Delivery لتتجاهل الحدث المكرّر |
كل نظام يعيد المحاولة قد يرسل الحدث مرتين أو بترتيب مختلف، فابنِ معالجك على هذا الأساس.
لا تثق بأي طلب غير موقّع
احسب HMAC-SHA256 على t + "." + الجسم الخام بمفتاح الويبهوك، وقارنه بـv1 بمقارنة ثابتة الزمن. وارفض أي طلب أقدم من خمس دقائق حتى لا يُعاد تشغيل طلب قديم عليك.
import crypto from "node:crypto";
export function verify(raw, header, secret) {
const [t, v1] = header.split(",").map(p => p.split("=")[1]);
// نافذة زمنية: تمنع إعادة تشغيل طلب قديم
if (Math.abs(Date.now() / 1000 - +t) > 300) return false;
const expect = crypto
.createHmac("sha256", secret)
.update(`${t}.${raw}`)
.digest("hex");
// المقارنة ثابتة الزمن: == يسرّب الفرق عبر زمن التنفيذ
const a = Buffer.from(expect), b = Buffer.from(v1);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
⚠ timingSafeEqual يرمي استثناءً إن اختلف الطولان، لذلك نفحص الطول قبله.
وهذا خطأ شائع يوقف تكاملات كثيرة في بيئة الإنتاج.
واحسب التوقيع على الجسم الخام قبل تحليل JSON، لأن إعادة ترتيب المفاتيح تُفسد التوقيع.
منع التكرار حتى لا تدفع مرّتين
كل POST يقبل Idempotency-Key. إن انقطع الاتصال ولم يصلك الرد، فأعد الطلب نفسه بالمفتاح نفسه،
وستحصل على النتيجة نفسها دون رابط دفع ثانٍ أو رسالة مكرّرة.
| الحالة | السلوك |
|---|---|
| نفس المفتاح ونفس الجسم | يُعاد الرد المحفوظ مع Idempotency-Replayed: true |
| نفس المفتاح وجسم مختلف | 409 مع idempotency_key_reused |
| مدّة الحفظ | 24 ساعة |
التنقل بين الصفحات بمؤشّر
لا نستعمل page=2، لأن وصول سجلّ جديد بين طلبين يُزيح الترقيم فتفوتك عناصر أو تتكرّر. أما المؤشّر فيحفظ موضعك.
{
"data": [ … ],
"has_more": true,
"next_cursor": "cur_01J8…"
}
وللمزامنة الدورية استعمل updated_after بآخر updated_at استلمته، فتنقل ما تغيّر فقط بدل السجلّ كلّه.
شكل واحد لكل خطأ
كل خطأ يحمل type للتصنيف وcode للتعامل البرمجي وmessage للعرض وrequest_id للدعم. اعتمد على code في برمجتك وليس message، لأن نص الرسالة قد نحسّنه أما الرمز فثابت.
| الحالة | الرمز | السبب وما تفعله |
|---|---|---|
| 400 | invalid_request | حقل ناقص أو نوع خاطئ. التفاصيل في errors[] |
| 401 | key_missing · key_revoked | راجع الترويسة أو أنشئ مفتاحاً |
| 403 | scope_insufficient | مفتاح قراءة فقط في نداء كتابة |
| 404 | not_found | لا سجلّ بهذا المعرّف في نسختك |
| 409 | idempotency_key_reused | نفس المفتاح بجسم مختلف |
| 409 | contract_not_approved | طلبتَ رابط دفع لعقد لم يعتمده المالك بعد |
| 422 | gateway_not_configured | لا توجد بوابة مهيّأة. راجع GET /api/v1/gateways |
| 422 | amount_below_minimum | المبلغ دون حدّ البوابة الأدنى |
| 429 | rate_limited | انتظر Retry-After ثم أعد |
| 503 | gateway_unavailable | البوابة لم تستجب. أعد لاحقاً بنفس مفتاح منع التكرار |
⚠ لا تظهر اعتمادات البوابة في أي رسالة خطأ، كاملةً أو مقتطعة. هذه قاعدة مبنية في طبقة الدفع، لأن رسائل الخطأ من أكثر الأماكن التي تتسرّب منها المفاتيح.
الحدود وسياسة البيانات
| البند | القيمة |
|---|---|
| حدّ الطلبات | 120 / دقيقة لكل مفتاح |
| ترويسات الحدّ | X-RateLimit-Limit · X-RateLimit-Remaining · X-RateLimit-Reset |
| عند التجاوز | 429 مع Retry-After بالثواني |
| حجم الجسم | 256 كيلوبايت |
| أين تُخزَّن البيانات | على خادمك في التشغيل المحلي، أو على خوادمنا في التشغيل المُدار، والذكاء الاصطناعي عند مزوّد سحابي نسمّيه في العقد |
| مدّة الاحتفاظ | تضبطها أنت، والنظام لا يحذف شيئاً تلقائياً |
الحدود أعلاه مواصفة، ولا يُطبَّق منها شيء بعد. ولأن لكل عميل نسخته، فحدّ الطلبات يحمي موارد نسختك أنت. ويمكن رفعه من إعدادات نسختك دون أن تشتريه منّا.
الإصدار والإيقاف المجدول
الإصدار في المسار: /api/v1/. وداخل الإصدار الواحد نضيف ولا نحذف.
| التغيير | كاسر؟ |
|---|---|
| حقل جديد في الرد | لا، تجاهل الحقول التي لا تعرفها |
قيمة جديدة في enum | لا، ولا تجعل القيمة المجهولة تعطّل تكاملك |
| نقطة نهاية جديدة | لا |
| حذف حقل أو تغيير نوعه | نعم، في إصدار جديد |
| تشديد تحقّق قائم | نعم، في إصدار جديد |
وإن قرّرنا إيقاف نقطة: نُشعرك قبل 180 يوماً، ونضيف ترويسة Sunset على كل ردّ منها، وتبقى تعمل طوال المدّة.
سجلّ التغييرات
نسجّل هنا كل تغيير في هذا الدليل بتاريخه، ومنه تصحيح ما كُتب خطأً.
2026-08-04
أول نشر لهذا الدليل. وفي الوقت نفسه حذفنا من صفحة المطوّرين أربعة أمور
لم تكن مبنيّة: عنوان على نطاق غير محجوز، وسبعة أحداث لا تُرسَل،
وحدّ طلبات غير مطبّق، وتصدير وشهادة حذف.
وصار لكل نقطة شارة حالة صريحة.
2026-09-28
الأحداث السبعة عادت إلى صفحة المطوّرين بشارة «مواصفة»، ومثالها صار بالهللات
كما يقول هذا الدليل. وسُحبت Webhooks من عدّاد «جاهزة اليوم» في صفحة التكاملات.
2026-10-01
أوضحنا حالة الدليل في رأس الصفحة: فحص الحالة وحده منفَّذ، والباقي مواصفة تُبنى عند الطلب.
ووسمنا جدول الحدود بشارة «مواصفة»، وصحّحنا مكان معالجة البيانات في التشغيل المُدار.
تبني تكاملاً وتحتاج نقطة غير موجودة؟
نرتّب الأولويات حسب ما يبنيه العملاء. أخبرنا بحالتك وبأي نقطة تحتاج أولاً.