ملاحظة: لا توجد واجهة عربية لهذه الميزة بعد، فواجهة المنصّة كلها تظهر بالإنجليزية. لذلك تُنقَل النصوص المقتبَسة من الواجهة أدناه بالإنجليزية كما تظهر فعليًا، بدل أن تُترجَم أو تُختلَق بالعربية.
بعض القوالب لا يرسلها شخص أصلًا. تريد Tidewater Laundry Co. أن يخرج تأكيد الاستلام لحظة أن يؤكّد نظام الحجز لديها موعدًا — لا ينبغي أن يضطر أحد إلى فتح صندوق الوارد لذلك. لهذا يوجد endpoint واحد:
POST https://api.omniocto.com/api/wa-templates/{id}/send
يرسل قالبًا معتمدًا واحدًا إلى رقم هاتف واحد. استدعِه من معالج webhook أو من سكربت — «حين يُشحن طلب، أرسل قالب الاستلام». ولأنه قالب، لا تنطبق نافذة الـ 24 ساعة (لماذا).
احصل على مفتاح API
المفاتيح في /settings/api-keys — في قائمة الترس، تحت Admin & System ← API Keys.
- انقر Create key. يطلب مربع الحوار Create API key قيمةً لـ Name (optional) — شيئًا مثل “e.g. CI pipeline”؛ سمِّه باسم النظام الذي سيستخدمه.
- يعرض API key created المفتاح مرة واحدة، مع “Copy this key now — you won’t be able to see it again.” احفظه حيث يحفظ نظامك الأسرار.
تعرض القائمة تاريخ Last used لكل مفتاح، وRevoke key يوقفه فورًا: “Any integration using it will fail.” مفتاح لكل تكامل يجعل ذلك آمنًا.
أرسل المفتاح في كل استدعاء بصيغة Authorization: Bearer <key>.
اعثر على معرّف القالب
افتح /templates/whatsapp وانقر القالب. تنفتح اللوحة ويُضاف إلى عنوان الصفحة
&template=wt-… — هذا المعرّف القصير wt- هو ما يوضع في المسار. (ومعرّف UUID
للقالب يعمل أيضًا.)
تخبرك اللوحة نفسها بما يجب أن يملأه الاستدعاء:
- Variables — متغيّرات المتن: إمّا “2 positional variables” (
{{1}}،{{2}}) وإمّا Named: متبوعةً بالأسماء. - Header — نصّها، أو
[IMAGE]أو[VIDEO]أو[DOCUMENT]لترويسة وسائطية. - Buttons — مسرودة بالترتيب، لكلٍّ منها نوعه (
[URL]،[COPY_CODE]،[QUICK_REPLY]). الأول هو الفهرس0.
من سكربت، يسرد GET /api/wa-templates?status=approved القوالب المعتمدة مع
shortId وname وlanguage وcategory.
لا يمكن إرسال إلا قالب معتمد؛ أي حالة أخرى تُرفض.
الطلب
للمتن حقلان:
| الحقل | ما هو |
|---|---|
to | رقم الهاتف، بصيغة E.164 — +14255550123 |
components | مصفوفة template.components الخاصة بـ Meta، تُمرَّر كما هي. احذفها لقالب بلا متغيّرات |
components هي المصفوفة نفسها التي ترسلها إلى Cloud API من Meta، فتنطبق عليها
وثائق Meta. باختصار:
- Header — معامل واحد:
textلترويسة نصّية فيها متغيّر، أوimageأوvideoأوdocumentمعlinkيبدأ بـhttps://. - Body — معاملات
text. لقالب موضعي ({{1}}،{{2}})، أرسلها بالترتيب دون أسماء. ولقالب مسمّى ({{customer_name}})، أعطِ كل معاملparameter_nameالخاص به. لا تخلط بين الطريقتين أبدًا. القالب الذي أنشأه Octo موضعي هنا، أيًّا كانت الأسماء التي أعطيتها. - Buttons — مكوّن لكل زر يأخذ قيمة، مع
sub_typeوindex(موضعه في قائمة أزرار القالب):url— معاملtext: الجزء المتغيّر من الرابط.copy_code— الرمز، بصيغة Meta{ "type": "coupon_code", "coupon_code": "FRESH20" }أو بصيغة{ "type": "text" }. حتى 15 حرفًا.quick_reply— اختياري؛ معامل{ "type": "payload" }إن أردت أن يعود إليك payload حين يُنقر.
مثال كامل
قالب الاستلام لدى Tidewater، pickup_confirmation، متنه
Hi {{1}}, your pickup is confirmed for {{2}}. Reply here to change it. وله زر
Track pickup واحد ينتهي رابطه برقم الطلب:
curl -X POST "https://api.omniocto.com/api/wa-templates/wt-a1b2c3d4e5/send" \
-H "Authorization: Bearer $OMNI_OCTO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "+14255550123",
"components": [
{ "type": "body", "parameters": [
{ "type": "text", "text": "Ana" },
{ "type": "text", "text": "Tuesday, 9–11am" }
] },
{ "type": "button", "sub_type": "url", "index": "0",
"parameters": [{ "type": "text", "text": "TW-20931" }] }
]
}'
لو كان القالب يستخدم متغيّرات مسمّاة، لسمّى المتن كل قيمة:
{ "type": "body", "parameters": [
{ "type": "text", "parameter_name": "customer_name", "text": "Ana" },
{ "type": "text", "parameter_name": "pickup_slot", "text": "Tuesday, 9–11am" }
] }
النجاح هو 201:
{
"messageId": "6f1c…",
"conversationId": "3a9e…",
"conversationShortId": "cv-9f8e7d6c5b",
"externalId": "wamid.HBgL…",
"isNewConversation": false,
"template": { "id": "…", "shortId": "wt-a1b2c3d4e5", "name": "pickup_confirmation", "language": "en_US" }
}
externalId هو المعرّف الذي ستبلّغ Meta على أساسه عن إيصالات التسليم والقراءة؛
وتصل هذه إلى المحادثة لاحقًا. 201 تعني أن Meta قبلت الإرسال، لا أن الهاتف
استلمه. احتفظ بـ conversationShortId — ستحتاج إليه أدناه.
أين تظهر الرسالة
في /conversations، في المحادثة مع ذلك الرقم على رقم WhatsApp الخاص بالقالب — المحادثة نفسها التي تصل إليها رسائل العميل:
- إن وُجدت محادثة مع ذلك الرقم، ينضمّ إليها القالب — وإن كانت مغلقة، تُعاد
فتحها.
isNewConversationقيمتهfalse. - وإن لم توجد، تُفتح محادثة جديدة (وتُنشأ جهة اتصال لرقم غير معروف).
isNewConversationقيمتهtrue. - تعرض الفقاعة المتن مع قيمك، موسومًا بـ Template · واسم القالب.
حين يرد العميل، يصل ردّه إلى المحادثة نفسها — والإرسال عبر الـ API لا يسلّم المحادثة إلى شخص. المحادثة التي كانت موجودة يبقى فيها من كان يتولّاها؛ والمغلقة أو الجديدة تصبح نشطة تحت وكيل الذكاء الاصطناعي الذي يجيب على رقم WhatsApp ذلك. فذلك الوكيل، إن كان للرقم وكيل، هو من يجيب عن الرد، إلا إذا تولّى شخصٌ المحادثة في /conversations.
حين يُرفض الإرسال
| الحالة | code | المعنى |
|---|---|---|
| 400 | — | to ليس بصيغة E.164، أو components ليست بشكل تقبله Meta؛ يسرد المتن المشكلات |
| 402 | billing_not_eligible | لا تستطيع مساحة العمل إرسال رسائل صادرة بعد |
| 404 | template_not_found | لا يوجد قالب بهذا المعرّف في مساحة العمل هذه |
| 409 | blocked_dnc | ألغى الرقم الاشتراك. تُحفظ الرسالة في المحادثة محجوبةً؛ ولا يصل شيء إلى Meta |
| 422 | template_not_approved | لم تعتمد Meta القالب؛ status تقول أين هو |
| 422 | template_components_mismatch | components لا تناسب القالب. error يسمّي المشكلة |
| 422 | channel_unavailable | أُزيل رقم WhatsApp الخاص بالقالب أو عُطّل |
| 422 | channel_pending_verification, workspace_suspended, route_blocked | رفضه فحص إرسال على الرقم أو مساحة العمل. تُحفظ الرسالة في المحادثة محجوبةً أو فاشلة |
| 429 | workspace_rate_limited | تجاوزت مساحة عملك معدّلها على هذا الـ endpoint. لا يُكتب شيء |
| 429 | rate_limited | امتلأت نافذة الدفعة لرقم WhatsApp — انتظر من ثوانٍ إلى دقائق |
| 429 | daily_limit_reached | استُنفد الحدّ اليومي للقوالب لدى الرقم — قد يكون الانتظار ساعات |
| 502 | send_failed | رفضت Meta الإرسال؛ error يحمل رسالة Meta. تبقى الرسالة في المحادثة موسومةً بالفشل |
كل 429 يحمل ترويسة Retry-After وقيمة retryAfterSec مطابقة في المتن. انتظر تلك
المدة وأرسل الاستدعاء نفسه مجددًا: لم تُرسَل أي رسالة أو تُحفظ أو تُحتسب في
الفوترة في المرة الأولى. (الإرسال الأول إلى رقم جديد الذي يرفضه رصيد الرقم يكون
قد فتح المحادثة مع ذلك، فارغةً؛ ويحمل المتن conversationShortId الخاص بها.)
عائلتا 429 حدّان مختلفان. مساحة عملك يمكنها استدعاء هذا الـ endpoint 300 مرة في الدقيقة افتراضيًا، في دفعات تصل إلى 10 في الثانية؛ ذلك المعدّل يحسب استدعاءات الـ API فقط، ويضبطه دعم Omni Octo لكل مساحة عمل. رقم WhatsApp له رصيد إرسال خاص به، مشترك مع صندوق الوارد وOcto والحملات — ما سرعة الإرسال المسموحة لرقم واحد يشرحه.
عدم التطابق لا يرسل شيئًا
يفحص الاستدعاء components مقابل القالب قبل أن يُكتب أي شيء. قيمة ناقصة، أو
زائدة، أو اسم غير معروف، أو نوع ترويسة خاطئ، أو معامل على زر لا يقبله — كلٌّ منها
422 template_components_mismatch، دون حفظ رسالة ودون إرسال شيء. ويقول error
بالضبط ما الخطأ:
body expects 2 parameters, got 1
body is missing variable "pickup_slot"
button 0 is a QUICK_REPLY button, got url
button 0 (a URL button) is missing its url parameter
فعدم التطابق آمن الإصلاح وإعادة الإرسال.
الاستدعاء ليس idempotent
كل استدعاء ناجح يرسل — ويُحتسب في الفوترة — قالبًا واحدًا. لا يوجد مفتاح idempotency. إذا انتهت مهلة استدعاء أو انقطع الاتصال، فلن تعرف هل خرج، وقد تؤدي إعادة المحاولة العمياء إلى إرساله مرتين.
قبل إعادة المحاولة بعد انتهاء المهلة، تحقّق من المحادثة —
GET /api/conversations/{conversationShortId}
بالمعرّف من إرسال سابق إلى ذلك الرقم، أو افتح المحادثة في /conversations. لا تُعِد الإرسال إلا إن لم يكن القالب هناك.
أما حالات الرفض أعلاه فمختلفة: الـ 4xx يخبرك أن الإرسال لم يحدث.
ما لا يفعله هذا الـ endpoint
- إرسال نص حر. يرسل القوالب المعتمدة فقط — للرد داخل النافذة، استخدم صندوق الوارد.
- الإرسال إلى قائمة. الاستدعاء الواحد رقم واحد؛ الحملات تتولّى القوائم.
- تجاوز Meta. قد ترفض Meta قالبًا تسويقيًا حتى بعد 201 — انظر لماذا.
إلى أين بعد ذلك
- أدخِل قوالبك إلى Omni Octo — يجب أن يكون القالب معتمدًا ومُزامَنًا أولًا.
- ما سرعة الإرسال المسموحة لرقم واحد
— رصيد كل رقم وراء
rate_limitedوdaily_limit_reached.