تخطَّ إلى المحتوى
المحتويات
الإرسال والاستقبال

إرسال الرسائل

نقطة واحدة ترسل كل رسالة. تقبل الرسالة وتردّ فورا وتسلّم لاحقا — فالمهم إذن هو ما يقبله الطلب، وماذا تعني الحالات، وكيف تجعل إعادة المحاولة آمنة.

إرسال رسالة

POST/v1/messages

رد 201 يعني القبول لا التسليم. تُوضع الرسالة في طابور ويوزّعها عامل مستقل؛ تابعها عبر نقاط الرسائل أو عبر الـ webhooks.

curl -X POST https://whats.azzamkh.sa/api/v1/messages \
  -H "Authorization: Bearer $WA_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "channel_id": "ch_...",
    "to": "+9665XXXXXXXX",
    "type": "text",
    "text": { "body": "Your code is 481902" }
  }'

# 201 Created
# {
#   "id": "msg_...",
#   "status": "queued",
#   "channel_id": "ch_...",
#   "to": "+9665XXXXXXXX",
#   "type": "text",
#   "category": "utility",
#   "created_at": "2026-08-13T10:00:00.000Z"
# }

جسم الطلب

المطلوب دائما هو to وحده. وtext مطلوب حين يكون النوع نصا، وtemplate.slug حين يكون قالبا.

الحقلالنوعمطلوبالوصف
tostringمطلوبالمستلم بصيغة E.164 الصارمة، مثل ‎+9665XXXXXXXX. وأي شكل آخر خطأ تحقق يسمّي الحقل.
type"text" | "template"اختياريالافتراضي text.
text.bodystringاختيارينص الرسالة، من 1 إلى 4096 حرفا. مطلوب للرسالة النصية.
template.slugstringاختياريمعرّف القالب النصي. مطلوب لرسالة القالب.
template.languagestringاختياريلغة القالب. القالب يُعرَّف بالـ slug واللغة معا.
template.variablesobject<string, string>اختياريقيم متغيرات القالب. والقيمة المطلوبة الناقصة تعطي 422.
channel_idstringاختياريأغفله فتُستخدم القناة الافتراضية لمساحة العمل. وسمّه حين يكون لديك أكثر من قناة.
category"authentication" | "utility" | "marketing"اختياريتحدد أولوية الطابور. الافتراضي utility. وفئة القالب نفسه تعلو على ما ترسله هنا.

الحقول المجهولة تُرفض

جسم الطلب وكلا كائنيه الفرعيين صارمة: الحقل المكتوب خطأ يعطي 422 برمز VALIDATION_ERROR يسمّيه، لا قيمة تُتجاهل بصمت. وهذا هو السلوك المرغوب — فخطأ إملائي في اسم حقل يخفق بصوت عال بدل أن يرسل رسالة ناقصة.

قائمة الأنواع هي بالضبط "text", "template". وإرسال صورة أو مستند أو صوت أو فيديو غير متاح بعد على هذه النقطة؛ أما الوسائط الواردة فتصل وتُسجّل على الرسالة.

الاستجابة

استجابة الإرسال صغيرة عمدا. اقرأ الرسالة إن احتجت conversation_id أو معرّف المزوّد أو خط الزمن — فلا شيء من ذلك موجود لحظة القبول.

الحقلالنوعالوصف
idstringالمعرّف العام للرسالة. استخدمه في كل متابعة.
statusstringدائما queued عند القبول.
channel_idstringالقناة التي سترسل منها الرسالة، بعد استنتاجها إن أغفلتها.
tostringالمستلم بعد التطبيع إلى E.164.
typestringيعيد النوع المطلوب.
categorystringالفئة بعد التصنيف، وقد تختلف عمّا أرسلته إن تجاوزها قالب.
created_atstringوقت إنشاء صف الرسالة.

إرسال قالب

اجعل النوع template وسمّ الـ slug واللغة. راجع القوالب لمعرفة كيف يُعرَّف المحتوى ومتغيراته.

curl
curl -X POST https://whats.azzamkh.sa/api/v1/messages \
  -H "Authorization: Bearer $WA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "channel_id": "ch_...",
    "to": "+9665XXXXXXXX",
    "type": "template",
    "category": "utility",
    "template": {
      "slug": "order_confirmation",
      "language": "ar",
      "variables": { "order_id": "A-10428", "name": "سارة" }
    }
  }'

الفئات والأولوية

تصف الفئة الغرض من الرسالة، وتستخدمها المنصة لتقرر ما يتقدم حين تمتلئ القناة.

الفئةالأولويةالوصف
authenticationالأعلىكلمات المرور لمرة واحدة ورموز التحقق. حساسة للوقت: الرمز المتأخر بلا قيمة.
utilityعاديةرسائل معاملات عن شيء فعله المستلم أصلا — طلب أو حجز أو شحنة.
marketingالأدنىمحتوى ترويجي. الأولوية الأدنى، والفئة الأكثر تقييدا بالسياسات.

إغفال الفئة يصنّف الرسالة على أنها utility.

الأولوية ترتّب الطابور ولا تزيد الطاقة

حين تبلغ القناة معدل تمريرها تتقدم رسالة المصادقة على الترويجية. وحين لا تبلغه لا تغيّر الأولوية شيئا. وهي ليست وسيلة لرفع حد.

الحماية من التكرار

أرسل header باسم Idempotency-Key مع كل إرسال. هو اختياري، وهو الفرق بين انقطاع شبكة لا يكلّفك شيئا وانقطاع يكلّفك رسالة مكررة.

الحالةالنتيجة
أول استعمال للمفتاحتُنشأ الرسالة وتُخزَّن الاستجابة مقابل المفتاح.
المفتاح نفسه وجسم مطابقتُعاد الاستجابة المخزنة حرفا بحرف مع header باسم Idempotent-Replay قيمته true. ولا تُنشأ رسالة ثانية.
المفتاح نفسه وجسم مختلف409 برمز IDEMPOTENCY_CONFLICT. ولا يُرسل شيء.
المفتاح نفسه والطلب الأول ما زال يعمل409 برمز CONFLICT و details.reason يساوي idempotent_request_in_progress. أعد المحاولة بعد قليل.
الطلب الأول أخفقيُحرَّر المفتاح، فيمكن إعادة المحاولة به نفسه. وترى الخطأ الأصلي.
curl
KEY=$(uuidgen)

# First call: the message is created.
curl -X POST https://whats.azzamkh.sa/api/v1/messages \
  -H "Authorization: Bearer $WA_API_KEY" \
  -H "Idempotency-Key: $KEY" \
  -d '{ "to": "+9665XXXXXXXX", "type": "text", "text": { "body": "Hello" } }'
# 201 Created   { "id": "msg_A", "status": "queued", ... }

# Same key, IDENTICAL body: the stored response is replayed byte for byte.
# No second message is created.
# 201 Created
# idempotent-replay: true
# { "id": "msg_A", "status": "queued", ... }

# Same key, DIFFERENT body:
# 409 Conflict
# { "error": { "code": "IDEMPOTENCY_CONFLICT", ... } }
  • تُقارن الأجسام بتجزئة صورتها القياسية، فترتيب المفاتيح والمسافات لا يهم — القيم وحدها هي التي تهم.
  • المفاتيح محصورة بمساحة عملك. وعميلان يمكن أن يستعملا القيمة نفسها دون تصادم.
  • يُحفظ المفتاح 24 ساعة ثم يُنسى. وإعادة استعماله بعدها ترسل رسالة جديدة.
  • طول المفتاح 255 حرفا بحد أقصى. والـ UUID اختيار جيد.

الحالات وخط الزمن

docs.pages.messages.lifecycle.body

الحالةالوصفالانتقالات المسموحة
createdالصف موجود ولم يُوضع في طابور بعد.queued, cancelled, failed_permanent
queuedمقبولة وتنتظر عاملا. وهذا ما يعيده الإرسال.dispatching, cancelled, expired, failed_retryable, failed_permanent
dispatchingالتقطها عامل وهو يخاطب المزوّد.provider_accepted, sent, failed_retryable, failed_permanent, cancelled
provider_acceptedأخذها المزوّد ولم يؤكد الإرسال بعد.sent, delivered, read, failed_retryable, failed_permanent, expired
sentسُلّمت إلى واتساب.delivered, read, failed_permanent, expired
deliveredوصلت إلى جهاز المستلم.read, failed_permanent
readقرأها المستلم، حيث يبلّغ المزوّد بذلك.
failed_retryableأخفقت إخفاقا ستُعاد المحاولة بعده. وليست نهائية.queued, dispatching, failed_permanent, cancelled, expired
failed_permanentأخفقت نهائيا. نهائية.
cancelledأُوقفت قبل الإرسال — مثلا حين تعذّر بلوغ طابور التوزيع.
expiredانقضت نافذة تسليمها قبل أن تُرسل.

أربع حالات نهائية لا يتبعها شيء: read, failed_permanent, cancelled, expired.

لا توجد حالة failed مجردة

الإخفاق إما failed_retryable وإما failed_permanent، والاثنان يعنيان لكودك أمرين متعاكسين. ومعاملتهما واحدا أشيع خطأ هنا: فالإخفاق القابل لإعادة المحاولة ما زال في الطريق.

قراءة الرسائل

GET/v1/messagesGET/v1/messages/{messageId}

المرشّحات

المعاملالقيمالوصف
statuscreated, queued, dispatching, provider_accepted, …الترشيح بالحالة. ويُقبل state اسما بديلا للمعامل نفسه.
directionoutbound, inboundالرسائل الصادرة التي أرسلتها أو الواردة التي استقبلتها.
categoryauthentication, utility, marketingالترشيح بالفئة.
channel_idch_…الحصر بقناة واحدة. ومعرّف قناة مجهول يعطي 404 لا صفحة فارغة.
limit, afterتقسيم بالـ cursor. الأحدث أولا.

قيمة المرشّح المجهولة خطأ

على خلاف بعض القوائم الأخرى في هذه الـ API، ترفض مرشّحات الرسائل القيمة التي لا تعرفها بـ 422 يسمّي المجموعة المسموحة. فخطأ إملائي في مرشّح لا يعيد لك كل شيء بصمت أبدا.

رسالة واحدة مع خط زمنها

قراءة رسالة واحدة تضيف مصفوفة events، الأقدم أولا: كل انتقال حالة مع مصدره ورقم محاولته وسببه. وهذا هو السجل الذي تنظر فيه حين لا تصل رسالة.

curl
curl https://whats.azzamkh.sa/api/v1/messages/msg_... \
  -H "Authorization: Bearer $WA_API_KEY"

# 200 OK
# {
#   "id": "msg_...",
#   "status": "delivered",
#   "direction": "outbound",
#   "attempts": 1,
#   "queued_at": "2026-08-13T10:00:00.000Z",
#   "sent_at": "2026-08-13T10:00:01.140Z",
#   "delivered_at": "2026-08-13T10:00:02.980Z",
#   "read_at": null,
#   "events": [
#     { "id": "mev_...", "from_status": null, "status": "created", ... },
#     { "id": "mev_...", "from_status": "created", "status": "queued", ... },
#     { "id": "mev_...", "from_status": "queued", "status": "dispatching", ... },
#     { "id": "mev_...", "from_status": "dispatching", "status": "sent", ... },
#     { "id": "mev_...", "from_status": "sent", "status": "delivered", ... }
#   ]
# }

ما قد يخفق

كل هذه تتبع الغلاف الموصوف في الأخطاء والحدود. أما الإخفاق بعد القبول فليس خطأ HTTP أصلا — بل يصل تغيّرَ حالة وwebhook.

الرمزHTTPمتى يحدث
VALIDATION_ERROR422جسم غير سليم: مستلم بغير صيغة E.164، أو نص ناقص، أو حقل مجهول.
CHANNEL_NOT_FOUND404القناة المسمّاة غير موجودة في مساحة العمل هذه.
CHANNEL_NOT_CONNECTED409القناة موجودة لكنها غير متصلة.
CHANNEL_CAPABILITY_UNAVAILABLE409القناة لا تستطيع ما تحتاجه الرسالة، أو لم تُحدَّث قدراتها قط.
SANDBOX_TEST_KEY_REQUIRED403استُعمل مفتاح live على قناة Sandbox.
SANDBOX_RECIPIENT_NOT_VERIFIED403لم يوثّق المستلم ملكيته على هذه الجلسة، أو هو رقم غير الرقم الموثّق.
TEMPLATE_NOT_SENDABLE409القالب مسودة أو موقوف أو معطّل.
TEMPLATE_VARIABLE_MISSING422متغيّر قالب مطلوب بلا قيمة. وتسرد details أيّها.
ENTITLEMENT_LIMIT_REACHED409بلغت حدّ خطة. وتسمّي details الاستحقاق والحد والعدد الحالي.
IDEMPOTENCY_CONFLICT409استُعمل مفتاح الحماية من التكرار من قبل بجسم مختلف.
QR_SAFETY_LIMIT_REACHED429استُنفد الحد الأمني لقناة الـ QR في النافذة الحالية.
SERVICE_UNAVAILABLE503طابور التوزيع متعذّر. تُلغى الرسالة بدل تركها معلّقة؛ أعد الإرسال.