اقرأ هذا أولاً. ما تحته شارة مُنفَّذ يعمل في الكود اليوم.
وما تحته مواصفة عقدٌ نلتزم به ولم يُبنَ بعد —
يُفعَّل على خادمك أثناء التأسيس، ونثبّت شكله هنا قبل البناء حتى تبني عليه بلا انتظار.
لن تجد في هذه الصفحة نقطةً تُقدَّم على أنها جاهزة وهي ليست كذلك.
بداية في خمس دقائق
النداء الوحيد الذي يعمل اليوم بلا مفتاح ولا إعداد هو فحص الحياة. ابدأ به لتتأكّد أن نسختك تعمل وأن الشبكة تصل إليها.
يردّ 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 في الرد السابق |
المراحل الأربع أعلاه هي مراحل النظام الفعلية لا تصنيفاً تسويقياً — يكتبها crm.js عند كل تحوّل.
يرسل رسالة نصية باسم نشاطك. الرسالة تمرّ بحارس المخرجات نفسه الذي يمرّ به ردّ النظام — فلا تستطيع الواجهة أن تقول ما لا يستطيع البوت قوله.
POST /api/v1/messages
Authorization: Bearer $NAATEQ_KEY
Idempotency-Key: a3f1c9de-…
{
"to": "966501234567",
"text": "جهّزنا لك العرض، تحب نرسله؟"
}
العملاء
سجلّ العميل هو نفسه الذي يكتبه النظام في crm.json — الحقول أدناه منسوخة من الكود لا مؤلَّفة للعرض.
ولذلك ما تقرؤه من الواجهة هو ما يراه البوت لحظة الردّ، لا نسخة متأخّرة عنه.
| الحقل | النوع | الوصف |
|---|---|---|
| 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"
}
المستندات
عروض الأسعار والعقود. ورقم المستند ليس عشوائياً — يبنيه docNumber() من
بادئة نشاطك وحرف النوع والتاريخ وتسلسل من ثلاث خانات:
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": "HT-Q-260804-058",
"customer": { "number": "966501234567", "name": "محمد الحربي" },
"total": 4200,
"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
أول نشر لهذا الدليل. وفي الوقت نفسه سُحبت من صفحة المطوّرين أربع دعاوى
لم يكن يقابلها كود: مضيفٌ على نطاق غير محجوز، وسبعة أحداث بلا باعث،
وحدّ طلبات بلا محدِّد، وتصديرٌ وشهادةُ حذفٍ لم يُبنيا.
وصار لكل نقطة شارة حالة صريحة.
تبني تكاملاً وتحتاج نقطة غير موجودة؟
الترتيب يتحدّد بما يبنيه العملاء فعلاً. أخبرنا بحالتك وبأي نقطة تحتاج أولاً.