إرسال قالب واتساب
يُرسل قالباً معتمداً مسبقاً إلى مستلم واتساب. القوالب يمكن إرسالها في أي وقت، بما في ذلك خارج نافذة خدمة العملاء (24 ساعة) من ميتا — هذه النقطة المناسبة للإرسال الاستباقي مثل تأكيدات الطلبات، تذكيرات المواعيد، التحقّق برمز OTP، ورسائل إعادة الإشراك.
أي نقطة أستدعي؟
POST إلى /v1/whatsapp/templates عبر HTTPS. النقطة تتطلّب نطاق whatsapp:send على مفتاح API الخاص بك — نفس النطاق المستخدم في إرسال النص الحر.
لست متأكداً من اسم القالب بدقة أو لغته أو ما يحتاجه؟ اعرض قوالب الرقم أولاً: يأتي كل قالب بحالته ومتغيّراته وأزراره.
POST /v1/whatsapp/templates
كيف يبدو الطلب؟
استدعاء أدنى يحتاج إلى وكيل، ورقم إرسال، ومستلم، واسم القالب + اللغة. معظم الإرسالات الفعلية تتضمّن أيضاً parameters لملء المتغيّرات، وكثير من القوالب تتطلّب كذلك buttons[] أو header_parameters أو header_media_url حسب ما اعتُمد به القالب.
curl -X POST https://api.mojeeb.app/v1/whatsapp/templates \
-H "Authorization: Bearer mk_live_YOUR_KEY_HERE" \
-H "Content-Type: application/json" \
-d '{
"agent_id": "12345678-1234-1234-1234-123456789012",
"from": "+15557654321",
"to": "+15551234567",
"template": {
"name": "arabic_signup_message",
"language": "ar"
}
}'
مع متغيّرات الجسم:
curl -X POST https://api.mojeeb.app/v1/whatsapp/templates \
-H "Authorization: Bearer mk_live_YOUR_KEY_HERE" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-12345-shipped" \
-d '{
"agent_id": "12345678-1234-1234-1234-123456789012",
"from": "+15557654321",
"to": "+15551234567",
"template": {
"name": "order_shipped",
"language": "en",
"parameters": {
"customer_name": "Sara",
"tracking_url": "https://example.com/track/A123"
}
}
}'
ماذا يُوضع في جسم الطلب؟
الحقول الخمسة الأولى مطلوبة لأي إرسال. الباقي اختياري ولا يهم إلا عندما اعتُمد قالبك بالمكوّن المطابق.
| الحقل | النوع | مطلوب | الوصف |
|---|---|---|---|
agent_id | UUID | نعم | الوكيل المُرسِل. |
from | نص E.164 | نعم | رقم عمل واتساب المُرسِل. |
to | نص E.164 | نعم | هاتف المستلم. |
template.name | نص | نعم | اسم قالب معتمد من ميتا (مثل order_shipped). |
template.language | نص | نعم | رمز لغة القالب لدى ميتا، كما تعرضه قائمة القوالب (مثل en، ar، en_US). |
template.parameters | كائن | لا | متغيّرات الجسم. خريطة مسطّحة اسم → قيمة. تعمل لكلا النوعين POSITIONAL ({{1}}) و NAMED ({{customer_name}}). |
template.header_parameters | كائن | لا | قيم متغيّرات رأس النص. مستقلّة عن parameters — ميتا تُرقّم متغيّرات الرأس والجسم بشكل منفصل. الرأس يسمح بمتغيّر واحد كحد أقصى. |
template.header_media_url | نص | لا | لقوالب رأس IMAGE / VIDEO / DOCUMENT — رابط عام يشير إلى الأصل المُراد إرساله. عند الحذف → مجيب يستخدم صورة العيّنة من وقت الاعتماد. |
template.buttons | مصفوفة | لا | قيم الأزرار للقوالب ذات الأزرار الديناميكية. كل عنصر يستهدف زراً واحداً عبر index المُفهرَس من الصفر. راجع كيف تعمل الأزرار؟. |
كيف تعمل متغيّرات الجسم؟
القوالب يمكن أن تحوي متغيّرات في جسمها — مثلاً قالب جسمه Hello {{customer_name}}, your order is on the way: {{tracking_url}}. مرّر القيم كخريطة مسطّحة:
"parameters": {
"customer_name": "Sara",
"tracking_url": "https://example.com/track/A123"
}
للقوالب من نوع POSITIONAL (مثل جسم Hello {{1}}, your code is {{2}})، استخدم رقم المتغيّر مفتاحاً:
"parameters": {
"1": "Sara",
"2": "493281"
}
لا تحتاج إلى إخبارنا أي نمط يستخدم القالب — مجيب يكتشف ذلك من جسم القالب المعتمد.
كيف تعمل متغيّرات رأس النص؟
القوالب ذات رأس TEXT يمكن أن تحوي متغيّراً واحداً في نص الرأس — مثلاً رأس Hello {{name}}. أرسل القيمة بشكل منفصل عن متغيّرات الجسم:
curl -X POST https://api.mojeeb.app/v1/whatsapp/templates \
-H "Authorization: Bearer mk_live_YOUR_KEY_HERE" \
-H "Content-Type: application/json" \
-d '{
"agent_id": "12345678-1234-1234-1234-123456789012",
"from": "+15557654321",
"to": "+15551234567",
"template": {
"name": "promo_with_header",
"language": "en",
"header_parameters": { "promo_label": "Summer Sale" },
"parameters": { "customer_name": "Sara", "discount_pct": "30" }
}
}'
ميتا تُرقّم متغيّرات الرأس والجسم بشكل منفصل، لذا يمكن لكليهما استخدام {{1}} دون تعارض — احتفظ بكل منهما في حقله. الرأس يسمح بمتغيّر واحد كحد أقصى؛ القوالب ذات الرؤوس متعدّدة المتغيّرات لن تجتاز اعتماد ميتا.
كيف تعمل الأزرار؟
القوالب يمكن أن تحوي أزراراً ديناميكية (URL، QUICK_REPLY، COPY_CODE، OTP، FLOW، MPM، CATALOG، ORDER_DETAILS). كل زر يحصل على عنصر واحد في template.buttons[]، مُفهرَس بـ index (مُفهرَس من الصفر) في القالب المعتمد. وتُعطيك قائمة القوالب index كل زر، والحقل الذي يحمل قيمته في send_field. الحقول التي توفّرها تعتمد على نوع الزر الفرعي:
| الزر المعتمد | الحقل المُرسَل | قيمة مثال |
|---|---|---|
URL (مع {{1}} في الرابط) | url_suffix | "orders/12345" |
| OTP (قوالب AUTHENTICATION) | otp_code | "493281" |
| QUICK_REPLY | quick_reply_payload | "FEEDBACK_GOOD" |
| COPY_CODE (قسيمة تسويق) | coupon_code | "SPRING30" |
| FLOW / MPM / SPM / CATALOG / ORDER_DETAILS | action | كائن — راجع وثائق ميتا |
| PHONE_NUMBER / VOICE_CALL | — | ثابت؛ لا يحتاج قيمة |
زر URL ديناميكي
زر URL في القالب يمكن أن يُعتمد مع {{1}} في نهاية الرابط — ميتا تستبدل قيمتك وقت الإرسال:
"buttons": [
{ "index": 0, "url_suffix": "orders/12345" }
]
إن كان الرابط المعتمد https://shop.example.com/{{1}}، يرى العميل زراً يفتح https://shop.example.com/orders/12345.
زر QUICK_REPLY
quick_reply_payload يُعاد إلى webhook الخاص بك عندما يضغط المستخدم الزر — هو كيفية تمييز أي زر تم الضغط عليه. نص الزر نفسه ثابت عند اعتماد القالب؛ هذا فقط نص التوجيه:
"buttons": [
{ "index": 0, "quick_reply_payload": "FEEDBACK_GOOD" },
{ "index": 1, "quick_reply_payload": "FEEDBACK_BAD" }
]
زر COPY_CODE (قسيمة تسويق)
15 حرفاً كحد أقصى حسب قواعد ميتا. يرى العميل زراً ينسخ الرمز إلى الحافظة:
"buttons": [
{ "index": 0, "coupon_code": "SPRING30" }
]
زر OTP (قوالب AUTHENTICATION)
لقوالب OTP من نوع one-tap، copy-code، و zero-tap، أرسل رمز التحقق عبر otp_code. الرمز عادة يظهر أيضاً في جسم {{1}}، لذا مرّره في كلا المكانين:
curl -X POST https://api.mojeeb.app/v1/whatsapp/templates \
-H "Authorization: Bearer mk_live_YOUR_KEY_HERE" \
-H "Content-Type: application/json" \
-d '{
"agent_id": "12345678-1234-1234-1234-123456789012",
"from": "+15557654321",
"to": "+15551234567",
"template": {
"name": "otp_verification_v1",
"language": "en",
"parameters": { "1": "493281" },
"buttons": [
{ "index": 0, "otp_code": "493281" }
]
}
}'
أزرار OTP من نوع copy-code و one-tap تستخدم نفس شكل الإرسال — ميتا تختار العرض الصحيح حسب كيفية اعتماد القالب. otp_code هو اسم مستعار أكثر وضوحاً لـ url_suffix؛ كلاهما يعمل.
زر FLOW
لقوالب Flow التفاعلية، وفّر كائن الإجراء الذي توثّقه ميتا. مجيب يُمرّره كما هو:
"buttons": [
{
"index": 0,
"action": {
"flow_token": "tok_3f4a91",
"flow_action_data": { "branch_id": "cairo-downtown" }
}
}
]
راجع وثائق Flow Templates من ميتا لشكل الإجراء الكامل لكل flow.
أزرار ثابتة (PHONE_NUMBER، VOICE_CALL، URL ثابت)
إن لم يحوِ الزر المعتمد متغيّراً (مثل رقم هاتف ثابت، أو رابط بدون {{1}})، احذفه من buttons[]. ميتا تستخدم القيمة المُدمَجة في القالب.
كيف تعمل صور الرأس المخصّصة؟
للقوالب ذات رأس IMAGE، VIDEO، أو DOCUMENT، يمكنك إرسال أصل مختلف لكل مستلم عبر header_media_url:
curl -X POST https://api.mojeeb.app/v1/whatsapp/templates \
-H "Authorization: Bearer mk_live_YOUR_KEY_HERE" \
-H "Content-Type: application/json" \
-d '{
"agent_id": "12345678-1234-1234-1234-123456789012",
"from": "+15557654321",
"to": "+15551234567",
"template": {
"name": "order_shipped_with_photo",
"language": "en",
"header_media_url": "https://cdn.customer.example.com/shipments/A12345.jpg",
"parameters": { "tracking_no": "EG-A12345" }
}
}'
يتحقّق مجيب من أمر واحد قبل وضع الإرسال في الطابور: يجب أن يكون الرابط رابط https:// كاملاً، وإلا يفشل الطلب بـ422 template_header_media_url_invalid. وتفرض ميتا الباقي:
- يجب أن يكون مُتاحاً علنياً من خوادم ميتا — ميتا تُنزّل الأصل بنفسها، لذا الروابط الداخلية، VPN-only، أو IP-allowlisted لن تعمل.
- يجب أن يطابق MIME والحجم حدود ميتا لكل صيغة — عادة JPG/PNG ≤ 5 MB لـ IMAGE، MP4 ≤ 16 MB لـ VIDEO، PDF ≤ 100 MB لـ DOCUMENT.
- إن كان الرابط غير قابل للوصول أو مرفوضاً، ينتقل الإرسال إلى
status: failed(مرئي عبر استعلام حالة الرسالة).
عند حذف header_media_url، مجيب يُرسل صورة العيّنة من وقت الاعتماد لكل مستلم — مفيد عندما يكون نفس الأصل مناسباً للجميع.
كيف تبدو استجابة النجاح؟
HTTP 202 Accepted مع كائن الرسالة: id، وstatus: "queued"، وagent_id وto كما أرسلتهما، وtype: "template"، وcreated_at. كل الحقول مع مثال موجودة في مرجع هذه النقطة، المعروض من عقد الواجهة نفسه فلا يتقادم أبداً.
استخدم id المُعاد مع استعلام حالة الرسالة لتتبّع التسليم.
ماذا قد يفشل؟
ظرف الخطأ القياسي. حالات القوالب الشائعة (الكتالوج الكامل في صفحة الأخطاء):
code | HTTP | متى |
|---|---|---|
invalid_request_body | 422 | حقل مطلوب مفقود أو فارغ (مثل template.name أو template.language) — param يُسمي أيهما |
from_phone_not_found_for_agent | 422 | from لا يطابق أي اتصال واتساب نشط — available_phones يُدرج ما يعمل |
agent_not_authorized | 403 | مفتاح API غير مسموح له باستخدام هذا الوكيل |
template_not_found | 422 | لا يوجد على الاتصال قالب بهذا الاسم، أياً كانت حالته — available_templates يُدرج أسماء قوالبه |
template_not_found_for_language | 422 | القالب غير موجود بهذه اللغة — available_languages يُدرج اللغات الموجود بها |
template_header_media_url_invalid | 422 | header_media_url ليس رابط HTTPS كاملاً |
هذه الأخطاء تُطلَق قبل وضع الإرسال في الطابور (422 متزامن). أما الإخفاقات من جانب ميتا فتظهر بشكل غير متزامن، وكذلك فحوص مجيب وقت الإرسال لقيم الأزرار أو الرأس المفقودة — يُعيد الطلب 202 مع معرّف رسالة في الطابور، وتكتشف الفشل بالاستعلام عن حالة الرسالة: تصبح status بقيمة failed وتوضّح failure_reason السبب (TemplatePaused وTemplateBroken وRecipientUnreachable وغيرها).
أسئلة شائعة
كيف أُنشئ قالباً؟
استخدم صفحة واتساب في مجيب: فهي تُنشئ القوالب وتعدّلها وتحذفها وتقدّمها لميتا في كل باقة تشمل واتساب؛ راجع قوالب واتساب. وتراجع ميتا كل قالب، وبعد اعتماده تستطيع إرساله. ولا تُنشئ الواجهة البرمجية القوالب ولا تعدّلها بعد: عرض قوالب واتساب يعرضها مع حالة كل منها.
ما الفرق بين لغة en و en_US؟
كلاهما رمز لغة قالب لدى ميتا. تستخدم ميتا هذه الرموز لاختيار الترجمة الصحيحة للقالب: en هي الإنجليزية العامة، وen_US هي الإنجليزية الأمريكية تحديداً. وهي ليست علامات BCP-47: تكتب ميتا النسخة الإقليمية من اللغة بشرطة سفلية (en_US) لا بشرطة عادية (en-US). أرسل الرمز نفسه الذي اعتُمد به قالبك، كما تعرضه قائمة القوالب.
هل يمكنني حذف parameters إن لم يحوِ قالبي متغيّرات؟
نعم. احذف حقل parameters تماماً (أو مرّر كائناً فارغاً). القوالب بدون متغيّرات تتجاهل المتغيّرات على أي حال. نفس الأمر ينطبق على header_parameters، header_media_url، و buttons.
هل تتحقّق الواجهة من المتغيّرات مقابل متغيّرات القالب المتوقَّعة؟
جزئياً. يتحقّق مجيب من أن على الاتصال قالباً بهذا الاسم وبلغتك، أياً كانت حالته، وأن header_media_url رابط HTTPS كامل. هذه الإخفاقات تُعيد 422 برمز محدّد قبل وضع الإرسال في الطابور. والقالب غير المعتمد يجتاز هذا التحقّق ثم يفشل لدى ميتا، لذا اعرض القوالب ولا ترسل إلا ما status فيه approved. وحين يتعذّر على مجيب قراءة القوالب من ميتا، أو يكون لدى الرقم قوالب أكثر مما يقرؤه مجيب ولا يكون قالبك بينها، يتخطّى التحقّق من الاسم واللغة: يُقبل الإرسال (202)، ويظهر حكم ميتا في حالة الرسالة.
ويتحقّق مجيب أيضاً من أمرين حين يُنفَّذ الإرسال من الطابور. أولاً، أن لكل زر ديناميكي يحتاج قيمة قيمتَه: زر URL فيه متغيّر، وOTP، وCOPY_CODE، وFLOW وبقية أزرار الإجراءات. أما QUICK_REPLY فيرجع إلى نص الزر. ثانياً، أن رأس TEXT ذا المتغيّرات يحصل على قيمة واحدة على الأقل في header_parameters. إن نقص شيء من ذلك، يظل الطلب يُعيد 202، ثم تصبح الرسالة status: failed مع failure_reason: TemplateBroken وfailure_code: null وfailure_detail يُسمّي فهرس الزر أو متغيّرات الرأس.
لا يتحقّق مجيب من قيم المتغيّرات نفسها، ولا من عدد قيم الجسم أو الرأس التي ترسلها، ولا من تطابقها. ميتا تتحقّق من ذلك، ويظهر أي عدم تطابق أيضاً كـstatus: failed حين تستعلم عن الرسالة.
هل يمكنني إرسال نفس القالب مرّتين بأمان؟
نعم — استخدم Idempotency-Key. راجع الإعادة الآمنة.
هل يمكنني رفع صورة مباشرة بدلاً من استضافتها بنفسي؟
ليس في v1 — header_media_url يتطلّب استضافة الأصل على رابط HTTPS عام. العملاء الذين لديهم CDN خاص يحتاجون إلى كشف رابط يمكن لميتا الوصول إليه (روابط موقّعة، حاوية عامة، إلخ). نقطة رفع مباشر قد تظهر في إصدار مستقبلي.
ما الحجم الأقصى لـ header_media_url؟
الحدود من ميتا، لا من مجيب. حتى 2026: JPG/PNG ≤ 5 MB، MP4 ≤ 16 MB، PDF ≤ 100 MB. راجع مواصفات الوسائط من ميتا للأرقام الحالية.
كيف أجد index الزر؟
اعرض قوالب الرقم: يأتي كل زر بـ index الخاص به، ويسمّي send_field الحقل الذي توضع فيه قيمته. الأزرار مُفهرَسة من الصفر بالترتيب الذي تظهر به في القالب المعتمد. الزر الأول index: 0، الثاني index: 1، إلخ. الأزرار الثابتة (PHONE_NUMBER، VOICE_CALL، URL بدون متغيّر) تحتفظ بفهرسها رغم عدم إرسالك قيماً لها — مرّر قيم الأزرار الديناميكية مُفهرَسة بموضعها الحرفي في القالب.