إرسال قالب واتساب
يُرسل قالباً معتمداً مسبقاً إلى مستلم واتساب. القوالب يمكن إرسالها في أي وقت، بما في ذلك خارج نافذة خدمة العملاء (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 | نص | نعم | علامة لغة BCP-47 (مثل 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 (مُفهرَس من الصفر) في القالب المعتمد. الحقول التي توفّرها تعتمد على نوع الزر الفرعي:
| الزر المعتمد | الحقل المُرسَل | قيمة مثال |
|---|---|---|
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 فقط.
- يجب أن يكون مُتاحاً علنياً من خوادم ميتا — ميتا تُنزّل الأصل بنفسها، لذا الروابط الداخلية، 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": "01c45011-d803-4eb0-a762-3228a4c392f3",
"status": "queued",
"agent_id": "12345678-1234-1234-1234-123456789012",
"to": "+15551234567",
"type": "template",
"platform_message_id": null,
"created_at": "2026-04-30T09:18:31Z",
"sent_at": null,
"failed_at": null
}
استخدم id المُعاد مع استعلام حالة الرسالة لتتبّع التسليم.
ماذا قد يفشل؟
ظرف الخطأ القياسي. حالات خاصة بالقوالب:
code | HTTP | متى |
|---|---|---|
invalid_template_name | 422 | template.name مفقود أو فارغ |
invalid_request_body | 422 | حقل مطلوب مفقود — param يُسمي أيهما |
from_phone_not_found_for_agent | 422 | from لا يطابق أي اتصال واتساب نشط — available_phones يُدرج ما يعمل |
agent_not_authorized | 403 | مفتاح API غير مسموح له باستخدام هذا الوكيل |
template_button_value_missing | 422 | زر ديناميكي يحتاج قيمة لكن لم تُوفَّر — param يُسمي الحقل المفقود |
template_header_variable_missing | 422 | متغيّر رأس TEXT يحتاج قيمة لكن header_parameters لا تتضمّنها |
template_header_too_many_parameters | 422 | الرأس يحوي أكثر من متغيّر واحد — أصلح القالب، الرؤوس تسمح بمتغيّر واحد فقط |
template_header_kind_mismatch | 422 | header_media_url مُرسَل لقالب رأس TEXT، أو header_parameters مُرسَل لقالب رأس وسائط |
template_header_media_url_invalid | 422 | header_media_url ليس رابط HTTPS عام صالحاً |
mixed_parameter_format | 422 | القالب يخلط بين متغيّرات {{1}} و {{name}} — اختر نمطاً واحداً |
هذه أخطاء التحقّق تُطلَق قبل وضع الإرسال في الطابور (422 متزامن). الإخفاقات من جانب ميتا (template_paused_by_meta، template_broken_at_meta، recipient_unreachable، إلخ) تظهر بشكل غير متزامن — الطلب لا يزال يُعيد 202 مع معرّف رسالة في الطابور، وتكتشف الإخفاق بالاستعلام عبر استعلام حالة الرسالة.
أسئلة شائعة
كيف أُنشئ قالباً؟
القوالب تُنشأ وتُعتمد عبر WhatsApp Business Manager من ميتا، ثم تصبح متاحة للإرسال. هذا إعداد لمرة واحدة لكل قالب، منفصل عن تكامل مجيب. لوحة تحكم مجيب تُظهر قوالبك القائمة تحت اتصال واتساب للوكيل.
ما الفرق بين لغة en و en_US؟
علامات لغة BCP-47. ميتا تستخدمها لاختيار الترجمة الصحيحة للقالب. en هي الإنجليزية العامة؛ en_US هي الإنجليزية الأمريكية تحديداً. استخدم أي علامة لغة اعتُمد قالبك بها.
هل يمكنني حذف parameters إن لم يحوِ قالبي متغيّرات؟
نعم. احذف حقل parameters تماماً (أو مرّر كائناً فارغاً). القوالب بدون متغيّرات تتجاهل المتغيّرات على أي حال. نفس الأمر ينطبق على header_parameters، header_media_url، و buttons.
هل تتحقّق الواجهة من المتغيّرات مقابل متغيّرات القالب المتوقَّعة؟
نعم. مجيب يتحقّق أن كل زر ديناميكي له قيمة، أن كل متغيّر رأس TEXT مطلوب له مدخل في header_parameters، وأنك لم تخلط أنماط POSITIONAL/NAMED ضمن قالب واحد. الإخفاقات تُعيد 422 مع code محدّد قبل وضع الإرسال في الطابور.
ما لا نتحقّق منه محلياً: تطابق نوع الزر مع الحقل (مثلاً وفّرت coupon_code لزر تعرفه ميتا بـ URL). تلك تصل إلى ميتا وينتقل الإرسال إلى 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: 0، الثاني index: 1، إلخ. الأزرار الثابتة (PHONE_NUMBER، VOICE_CALL، URL بدون متغيّر) تحتفظ بفهرسها رغم عدم إرسالك قيماً لها — مرّر قيم الأزرار الديناميكية مُفهرَسة بموضعها الحرفي في القالب.