يأخذ هذا الدليل أي نظام خارجي — نظام CRM أو ERP أو متجر إلكتروني أو نظام حجوزات أو الخادم الخلفي لموقع ويب أو أي تطبيق مخصص — من الصفر إلى تكامل WhatsApp يعمل فعليًا مع Scale: إرسال الرسائل، ومتابعة تسليمها، واستقبال الردود، والاستجابة لتغيّرات حالة الاتصال. كل مثال في هذا الدليل هو الشكل الدقيق الذي تقبله واجهة Scale API وتعيده.
البدء
باستخدام واجهة Scale API يستطيع نظامك الخارجي:
- إرسال رسائل WhatsApp من مساحة العمل (Workspace) الخاصة بك في Scale: نصوص، وقوالب معتمدة، وصور، وفيديو، وصوت، ومستندات؛
- معرفة حالة كل رسالة أرسلها (pending و sent و delivered و read و failed و unknown)؛
- استقبال Webhooks موقّعة عندما يكتب العميل، وعندما تُرسَل رسالة أو تُسلَّم أو تُقرأ أو تفشل، وعندما يتم ربط WhatsApp أو فصله؛
- تمكين العميل من ربط رقم WhatsApp الخاص به عبر صفحة تستضيفها Scale؛
- قراءة حالة اتصال WhatsApp لمساحة العمل واستهلاكها من الرسائل.
Your system (CRM, ERP, store, app)
│ HTTPS + Authorization: Bearer sk_live_…
▼
Scale Public API (https://getscale.me/wp-json/scale/v1/)
│
▼
Scale WhatsApp (your workspace, its Inbox, its allowance)
│
▼
Meta WhatsApp Cloud APIنظامك لا يتحدث إلا مع Scale. اتصال WhatsApp وبيانات اعتماد Meta الخاصة به والتواصل مع Meta تبقى كلها داخل Scale، ولا تعيدها الواجهة أبدًا. كل ما يرسله نظامك عبر الواجهة يظهر أيضًا في صندوق الوارد (Inbox) في Scale كجزء من المحادثة نفسها.
المتطلبات الأساسية
| ما تحتاجه | من أين تحصل عليه |
|---|---|
| مساحة عمل (Workspace) في Scale باشتراك فعّال | حسابك في Scale. رسائل الواجهة تستخدم رصيد الرسائل نفسه المخصص لبقية مساحة العمل. |
| تفعيل الوصول إلى Public API لمساحة العمل | تفعّله Scale لمساحة عملك. بدونه يُرفض كل طلب إلى الواجهة بالرد 403 API_ACCESS_DISABLED ولا تظهر صفحة Developer & API. |
| مفتاح API | لوحة التحكم ← Settings ← Developer & API ← API keys ← Create API key. يُعرض المفتاح مرة واحدة فقط. |
| رقم WhatsApp مربوط | لوحة التحكم ← Settings ← Channels، أو عبر الربط المستضاف (Onboarding) الموضّح أدناه. |
| رابط Webhook (اختياري، ويُنصح به) | نقطة نهاية https:// خاصة بك، تحفظها في Settings ← Developer & API ← Webhook. |
| رابط عودة للربط (اختياري) | Settings ← Developer & API ← Onboarding return URLs — فقط إذا كنت تستخدم الربط المستضاف مع إعادة التوجيه إلى موقعك. |
https:// على الإنترنت العام.الرابط الأساسي (Base URL)
https://getscale.me/wp-json/scale/v1/كل نقطة نهاية (Endpoint) في هذا الدليل نسبية إليه: POST /messages تعني POST https://getscale.me/wp-json/scale/v1/messages. الطلبات والردود بصيغة JSON (Content-Type: application/json).
المصادقة (Authentication)
أرسل مفتاح API في الترويسة Authorization مع كل طلب:
Authorization: Bearer sk_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX- المفتاح هو
sk_live_متبوعًا بـ 40 حرفًا ورقمًا. ينتمي إلى مساحة عمل واحدة فقط، وكل ما يفعله يحدث داخل تلك المساحة. - في الترويسة فقط. المفتاح الموضوع في الرابط (
?api_key=أو?key=أو?token=أو?access_token=أو?apikey=) يُرفض بالرد401 INVALID_API_KEYحتى لو وُجدت ترويسة صحيحة أيضًا — ألغِ (Revoke) أي مفتاح وُضع في رابط ولو مرة واحدة. - على الخادم فقط. لا تضع المفتاح أبدًا في JavaScript يعمل في المتصفح، أو في تطبيق جوال، أو في أي شيء يمكن للعميل تنزيله. احفظه في مخزن الأسرار على خادمك.
- يُعرض مرة واحدة. تحتفظ Scale ببصمة (Hash) للمفتاح فقط؛ بعد إنشائه لا ترى إلا أحرفه الأولى (
sk_live_xxxxxx…). إذا فقدته، أنشئ مفتاحًا جديدًا وألغِ القديم. - يمكن أن يكون لمساحة العمل حتى 20 مفتاحًا فعّالًا. يمكن لكل نظام أن يحصل على مفتاحه الخاص، وتتشارك المفاتيح حدّ معدل الطلبات ورصيد الرسائل الخاصين بمساحة العمل.
curl https://getscale.me/wp-json/scale/v1/usage \
-H "Authorization: Bearer sk_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"كل رد يكون بصيغة JSON. الرد الناجح يحتوي على "success": true مع بياناته؛ أما الفشل فله الشكل نفسه في كل مكان:
{
"success": false,
"error": {
"code": "INVALID_API_KEY",
"message": "The API key is missing or invalid.",
"details": {}
},
"request_id": "req_k2V9pQ7sL1mN4xR8tY3wZ6aB"
}كل رد يحمل الترويسة X-Scale-Request-Id بمعرّف req_… نفسه — اذكره إذا تواصلت مع الدعم. والردود التي تتجاوز مرحلة المصادقة تحمل أيضًا X-RateLimit-Limit و X-RateLimit-Remaining و X-RateLimit-Reset (راجع حدود معدل الطلبات).
البدء السريع
- أنشئ مفتاح API — من Settings ← Developer & API ← API keys. انسخه إلى مخزن الأسرار على خادمك.
- اربط WhatsApp — من Settings ← Channels في لوحة التحكم، أو أرسل عميلك عبر الربط المستضاف (القسم التالي). تحقّق عبر
GET /whatsapp/status: القيمة"can_send": trueتعني أنك جاهز. - اضبط الـ Webhook — من Settings ← Developer & API ← Webhook: أدخل رابط
https://الخاص بك. انسخ سرّ التوقيع (whsec_…) الذي يُعرض مرة واحدة، ثم اضغط "Send test event". - أرسل أول رسالة —
POST /messagesمعIdempotency-Key. - استقبل أحداث الـ Webhook —
message.sentثمmessage.deliveredوmessage.read؛ وmessage.receivedعندما يرد العميل. - تحقّق من حالة الرسالة —
GET /messages/{id}باستخدام معرّفmsg_…الذي حصلت عليه.
curl https://getscale.me/wp-json/scale/v1/whatsapp/status \
-H "Authorization: Bearer $SCALE_API_KEY"curl -X POST https://getscale.me/wp-json/scale/v1/messages \
-H "Authorization: Bearer $SCALE_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-123-message-1" \
-d '{"to":"201012345678","type":"text","text":{"body":"Hello from our store! Your order #123 is confirmed."}}'{
"success": true,
"message": {
"id": "msg_Q2w3E4r5T6y7U8i9O0p1A2s3",
"wamid": "wamid.HBgMMjAxMDEyMzQ1Njc4FQIAERgSQzE5",
"status": "accepted",
"type": "text",
"to": "201012345678",
"created_at": "2026-09-24T10:00:00Z"
},
"replayed": false
}curl https://getscale.me/wp-json/scale/v1/messages/msg_Q2w3E4r5T6y7U8i9O0p1A2s3 \
-H "Authorization: Bearer $SCALE_API_KEY"ربط WhatsApp (Onboarding)
يتيح الربط المستضاف (Hosted Onboarding) لمساحة العمل ربط رقم WhatsApp الخاص بها دون لوحة التحكم: يطلب نظامك من Scale رابطًا لمرة واحدة، يفتحه الشخص، ويُكمل تسجيل Meta على صفحة تستضيفها Scale، ثم يُعاد إلى موقعك. تسجيل Meta يعمل على Scale — ولا تصل بيانات اعتماد Meta إلى نظامك أبدًا.
- (اختياري) سجّل رابط العودة: Settings ← Developer & API ← Onboarding return URLs.
- يستدعي خادمك
POST /whatsapp/onboarding/session. - أرسل الشخص إلى
onboarding_urlالوارد في الرد (بإعادة توجيه المتصفح أو برابط). - يُكمل الشخص Embedded Signup الخاص بـ Meta على صفحة Scale (ويُدخل رمز PIN الخاص بـ WhatsApp المكوّن من 6 أرقام إذا طلبه الرقم).
- تعيد Scale متصفحه إلى
return_urlالخاص بك مع النتيجة — وإذا كنت قد ضبطت Webhook، ترسل أيضًاwhatsapp.connected. - يتأكد خادمك باستخدام
GET /whatsapp/onboarding/session/{id}أوGET /whatsapp/status.
بدء جلسة
curl -X POST https://getscale.me/wp-json/scale/v1/whatsapp/onboarding/session \
-H "Authorization: Bearer $SCALE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"return_url":"https://crm.example.com/scale/connected","state":"crm-account-42"}'| الحقل | إلزامي | المعنى |
|---|---|---|
return_url | لا | المكان الذي تعيد Scale المتصفح إليه في النهاية. يجب أن يطابق تمامًا رابطًا مسجّلًا في Settings ← Developer & API ← Onboarding return URLs، وإلا يكون الرد 422 INVALID_RETURN_URL. بدونه تخبر الصفحةُ الشخصَ بأنه يستطيع إغلاقها. |
state | لا | مرجعك الخاص، حتى 200 حرف ASCII مرئي. يُعاد إليك على رابط العودة. |
{
"success": true,
"session_id": "onb_Z1x2C3v4B5n6M7a8S9d0F1g2",
"onboarding_url": "https://getscale.me/?scale_onboarding=1#t=Hk3mP9sQ2vX7yB4nC8dF1gJ6kL0zR5tW3eA9uI2oN7pM4qS8",
"expires_at": "2026-09-24T10:30:00Z",
"return_url": "https://crm.example.com/scale/connected"
}- الرابط صالح لمدة 30 دقيقة ويُفتح مرة واحدة: أول متصفح يفتحه يرتبط بالجلسة؛ وفتحه مرة أخرى يعرض "This link has already been used".
- لا توجد إلا جلسة واحدة نشطة لكل مساحة عمل: إنشاء جلسة جديدة يلغي أي جلسة سابقة لم تكتمل.
- إذا كان لدى مساحة العمل اتصال WhatsApp يعمل بالفعل، يكون الرد
409 WHATSAPP_ALREADY_CONNECTED— افصل الاتصال في Scale أولًا لربط رقم مختلف. 503 ONBOARDING_UNAVAILABLEتعني أن الربط غير متاح على نسخة Scale هذه في الوقت الحالي.
روابط العودة (Return URLs)
| القاعدة | التفاصيل |
|---|---|
| HTTPS | https:// فقط، واسم نطاق عام، وبحد أقصى 500 حرف. |
| تطابق تام | يجب أن يكون return_url في الطلب مطابقًا حرفًا بحرف لأحد الروابط المسجّلة. |
| غير مسموح | بيانات دخول (user:pass@)، أو #fragment، أو استعلام يحتوي أصلًا على session_id أو status أو state أو error — فـ Scale تضيفها بنفسها. |
| العدد | حتى 10 روابط لكل مساحة عمل. |
عندما ينتهي الشخص، أو يلغي، أو يفشل الاتصال، تعيد Scale توجيه المتصفح إلى رابط العودة الخاص بك مع إضافة معاملات الاستعلام التالية (تُحذف المعاملات الفارغة):
https://crm.example.com/scale/connected?session_id=onb_Z1x2C3v4B5n6M7a8S9d0F1g2&status=connected&state=crm-account-42
https://crm.example.com/scale/connected?session_id=onb_Z1x2C3v4B5n6M7a8S9d0F1g2&status=failed&state=crm-account-42&error=meta_pin_requiredGET /whatsapp/onboarding/session/{id} قبل الاعتماد عليها.التحقق من جلسة
curl https://getscale.me/wp-json/scale/v1/whatsapp/onboarding/session/onb_Z1x2C3v4B5n6M7a8S9d0F1g2 \
-H "Authorization: Bearer $SCALE_API_KEY"{
"success": true,
"session_id": "onb_Z1x2C3v4B5n6M7a8S9d0F1g2",
"status": "connected",
"error": null,
"return_url": "https://crm.example.com/scale/connected",
"created_at": "2026-09-24T10:00:00Z",
"expires_at": "2026-09-24T10:30:00Z",
"completed_at": "2026-09-24T10:04:12Z",
"whatsapp": {
"phone_number_id": "109876543210987",
"waba_id": "123456789012345"
}
}| status | المعنى |
|---|---|
created | أُنشئت الجلسة، ولم يُفتح الرابط بعد. |
opened | فُتح الرابط؛ تسجيل Meta قيد التنفيذ. |
connecting | تُكمل Scale الاتصال مع Meta. |
pin_required | يحتاج الرقم إلى رمز PIN الخاص بـ WhatsApp المكوّن من 6 أرقام؛ والصفحة تطلبه. |
connected | تم. يحتوي whatsapp على Phone Number ID و WABA ID. |
failed | لم يتم الربط؛ يحتوي error على رمز السبب. |
cancelled | ألغى الشخص العملية، أو حلّت محلها جلسة أحدث (error: superseded). |
expired | انقضت الـ 30 دقيقة قبل الاكتمال. |
الجلسة تنتمي إلى مساحة عملها: معرّف جلسة من مساحة عمل أخرى يعيد 404 ONBOARDING_SESSION_NOT_FOUND.
إرسال رسائل WhatsApp
POST /messages ترسل رسالة واحدة إلى مستلم واحد. يحتوي جسم الطلب دائمًا على to و type، بالإضافة إلى كائن واحد يحمل اسم النوع.
| الحقل | القاعدة |
|---|---|
to | الرقم الدولي الكامل للمستلم، مع رمز الدولة: 201012345678. تُقبل العلامة + أو 00 في البداية والمسافات والشرطات، وتُحذف؛ من 8 إلى 15 رقمًا. الرقم المحلي بدون رمز الدولة لا يُحوَّل. |
type | text أو template أو image أو video أو audio أو document. |
الترويسة Idempotency-Key | اختيارية لكن يُنصح بها بشدة — راجع منع التكرار. |
- نافذة الـ 24 ساعة. لا تُقبل رسائل
textوالوسائط إلا خلال 24 ساعة من آخر رسالة أرسلها العميل إلى رقمك؛ وإلا يكون الرد422 OUTSIDE_MESSAGING_WINDOWمعdetails.alternative: "template". يمكن إرسال القوالب في أي وقت، والقالب المرسل إلى رقم جديد يُنشئ أيضًا المحادثة في Scale. - رسالة واحدة في كل مرة لكل مساحة عمل. ترسل Scale رسائل الواجهة الخاصة بمساحة العمل واحدة تلو الأخرى. الطلب الذي يصل أثناء إرسال رسالة أخرى ينتظر حتى ثانية واحدة، ثم يرد بـ
429 RATE_LIMITEDمعdetails.reason: "send_in_progress"وRetry-After: 1— أعد المحاولة (بالقيمة نفسها لـ Idempotency-Key). - معنى النجاح. الرد
200مع"status": "accepted"يعني أن WhatsApp قبل الرسالة وأعاد معرّفها (wamid). التسليم والقراءة يأتيان لاحقًا — عبر الـ Webhooks أوGET /messages/{id}. - إنها رسالة محادثة عادية. تظهر الرسالة في Inbox الخاص بـ Scale، وتُخصم رسالة واحدة من رصيد مساحة العمل.
نص (text)
{
"to": "201012345678",
"type": "text",
"text": { "body": "Your appointment is confirmed for Thursday at 4 pm." }
}text.body إلزامي، وبحد أقصى 4,000 حرف.
قالب (template)
{
"to": "201012345678",
"type": "template",
"template": {
"name": "order_shipped",
"language": "ar",
"components": [
{
"type": "body",
"parameters": [
{ "type": "text", "text": "Ahmed" },
{ "type": "text", "text": "#123" }
]
}
]
}
}template.name و template.language إلزاميان؛ و template.components اختياري. التفاصيل في قسم القوالب.
صورة (image)
{
"to": "201012345678",
"type": "image",
"image": { "link": "https://cdn.example.com/products/sku-778.jpg", "caption": "Your new bag" }
}فيديو (video)
{
"to": "201012345678",
"type": "video",
"video": { "link": "https://cdn.example.com/howto/setup.mp4", "caption": "How to set it up" }
}صوت (audio)
{
"to": "201012345678",
"type": "audio",
"audio": { "link": "https://cdn.example.com/voice/welcome.ogg" }
}مستند (document)
{
"to": "201012345678",
"type": "document",
"document": {
"link": "https://cdn.example.com/invoices/INV-2026-0042.pdf",
"filename": "INV-2026-0042.pdf",
"caption": "Your invoice"
}
}| النوع | إلزامي | اختياري | الحدود |
|---|---|---|---|
text | text.body | — | 4,000 حرف |
template | template.name و template.language | template.components | حتى 20 مكوّنًا |
image | image.link | image.caption | التعليق حتى 1,000 حرف |
video | video.link | video.caption | التعليق حتى 1,000 حرف |
audio | audio.link | — | الصوت لا يقبل تعليقًا |
document | document.link | document.filename و document.caption | التعليق حتى 1,000 حرف |
كل الأنواع ترد بشكل النجاح نفسه الموضّح في البدء السريع. الرسالة التي تعذّر إرسالها ترد بخطأ (راجع الأخطاء)؛ والخطأ META_API_ERROR هو الذي يجب قراءته بعناية.
القوالب (Templates)
القالب رسالة اعتمدتها Meta مسبقًا لحساب WhatsApp Business الخاص بك. وهو النوع الوحيد من الرسائل الذي يسمح WhatsApp بإرساله خارج نافذة الـ 24 ساعة — كأول تواصل، أو تحديث لطلب، أو تذكير.
name— اسم القالب المعتمد تمامًا كما هو لدى Meta: أحرف إنجليزية صغيرة وأرقام وشرطات سفلية (order_shipped).language— رمز لغة WhatsApp للترجمة المعتمدة:"ar"أو"en"أو"en_US". الصيغة{ "code": "en_US" }مقبولة أيضًا.components— الأجزاء المتغيرة، وتُمرَّر إلى Meta تمامًا كما ترسلها. كل مكوّن كائن قيمةtypeفيه هيheaderأوbodyأوbuttonأوfooter؛ وparametersالخاصة به تتبع صيغة رسائل القوالب لدى Meta. احذفcomponentsللقالب الذي لا يحتوي على متغيرات.- يجب أن يكون القالب معتمدًا لحساب WhatsApp Business المربوط بمساحة العمل. تتحقق Meta من الاسم واللغة والمعاملات: القالب غير الموجود أو غير المعتمد أو الذي يحصل على عدد خاطئ من المعاملات ترفضه Meta —
502 META_API_ERRORمعdetails.outcome: "refused"، ولا يُرسَل شيء ولا يُخصم شيء. - القالب المرسل عبر الواجهة يأخذ رسالة واحدة من رصيد مساحة العمل، مثل أي رسالة عبر الواجهة. رسوم WhatsApp الخاصة بـ Meta منفصلة، وتفوترها Meta على حساب WhatsApp Business المربوط.
{
"to": "201012345678",
"type": "template",
"template": {
"name": "new_collection",
"language": { "code": "en_US" },
"components": [
{ "type": "header", "parameters": [ { "type": "image", "image": { "link": "https://cdn.example.com/banner.jpg" } } ] },
{ "type": "body", "parameters": [ { "type": "text", "text": "Sara" } ] }
]
}
}رسائل الوسائط (Media)
- أنت من يستضيف الملف. لا ترفع Scale الوسائط التي ترسلها ولا تخزّنها: يجب أن يكون
linkرابطhttps://عامًا، وتقوم Meta بتنزيل الملف منه. أي ملف خلف تسجيل دخول، أو شبكة VPN، أو توقيع تنتهي صلاحيته قبل الأوان سيفشل. - تتحقق Scale فقط من أن
linkرابطhttps://صالح. نوع الملف وحجمه يتحقق منهما WhatsApp؛ والملف الذي لا يستطيع استخدامه يُرفض (502 META_API_ERROR) أو يُبلَّغ عنه لاحقًا كـmessage.failed. captionمقبول للصورة والفيديو والمستند (حتى 1,000 حرف)؛ والصوت لا يقبل تعليقًا.document.filenameهو الاسم الذي يراه العميل.- الوسائط رسائل حرة: لا تُرسل إلا داخل نافذة الـ 24 ساعة.
الوسائط التي يرسلها إليك عملاؤك تصل بشكل مختلف — راجع message.received: تخزّن Scale الصور والمقاطع الصوتية والمستندات، ويحمل الـ Webhook رابطًا إلى نسخة Scale منها.
منع التكرار (Idempotency)
الشبكات تفشل. قد يصل الطلب إلى Scale، وتُرسَل الرسالة، ثم يضيع الرد في طريق العودة — وإذا أعاد نظامك المحاولة ببساطة، يستلم العميل الرسالة مرتين. الترويسة Idempotency-Key تمنع ذلك.
Idempotency-Key: order-123-message-1- اختيارية، لكن استخدمها مع كل إرسال. بدونها يكون كل طلب رسالة جديدة، وإعادة المحاولة بعد انتهاء المهلة تصبح رسالة مكررة.
- من 1 إلى 150 حرف ASCII مرئي (وإلا
400 INVALID_MESSAGE). اجعلها تصف الرسالة لا المحاولة:order-123-message-1، وليس قيمة عشوائية لكل محاولة. المفاتيح خاصة بكل مساحة عمل. - المفتاح نفسه والطلب نفسه ← الرد الأصلي: معرّف
msg_…نفسه، و"replayed": true، ولا رسالة ثانية ولا خصم ثانٍ — حتى بعد ساعات. - المفتاح نفسه وطلب مختلف ←
409 IDEMPOTENCY_CONFLICT، معdetails.message_idالذي يشير إلى الرسالة الأصلية. لا يُرسَل شيء. - تتم مقارنة الطلب بعد التحقق من صحته، لذا فالرسالة نفسها المكتوبة بمسافات JSON مختلفة أو بترتيب مفاتيح مختلف تُعد الطلب نفسه.
- المفتاح نفسه بينما الطلب الأول ما زال قيد الإرسال ←
429 RATE_LIMITED (send_in_progress). انتظر ثانية وأعد المحاولة بالمفتاح نفسه: ستحصل على الرسالة الأصلية. - بعد الرفض (
META_API_ERROR،outcome: "refused") لم يُرسَل شيء، لذا فإعادة المحاولة بالمفتاح نفسه تحاول الإرسال من جديد. - بعد نتيجة غير معروفة (
outcome: "unknown") يعيد المفتاح نفسه دائمًا الرد غير المعروف نفسه ولا يُرسَل مرة أخرى أبدًا — راجع ما يلي.
انتهاء المهلة والنتائج غير المعروفة
إذا انتهت مهلة طلب HTTP الخاص بك، أعد المحاولة بالمفتاح نفسه: إذا كانت Scale قد انتهت، تحصل على ردها؛ وإذا كان الإرسال ما زال جاريًا تحصل على send_in_progress؛ فأعد المحاولة بعد لحظات.
أحيانًا لا تستطيع Scale نفسها أن تعرف: لم يرد WhatsApp في الوقت المحدد، أو رد بخطأ من جهة الخادم، أو رد دون معرّف رسالة. قد تكون الرسالة وصلت إلى العميل وقد لا تكون. عندها ترد Scale بـ 502 META_API_ERROR مع details.outcome: "unknown" و "retry_safe": false و "charged": false. لن تخصمها ولن ترسلها مرة أخرى، لأن WhatsApp لا يوفّر طريقة لـ "الإرسال فقط إن لم تكن أُرسلت من قبل".
حالة الرسالة
curl https://getscale.me/wp-json/scale/v1/messages/msg_Q2w3E4r5T6y7U8i9O0p1A2s3 \
-H "Authorization: Bearer $SCALE_API_KEY"{
"success": true,
"message": {
"id": "msg_Q2w3E4r5T6y7U8i9O0p1A2s3",
"wamid": "wamid.HBgMMjAxMDEyMzQ1Njc4FQIAERgSQzE5",
"type": "text",
"to": "201012345678",
"status": "delivered",
"failure": null,
"created_at": "2026-09-24T10:00:00Z"
}
}| status | المعنى |
|---|---|
pending | سُجّلت الرسالة؛ والاتصال بـ WhatsApp قيد التنفيذ. |
sent | قبلها WhatsApp (علامة صح واحدة). |
delivered | وصلت إلى هاتف العميل. |
read | فتحها العميل (فقط إذا كان يسمح بإيصالات القراءة). |
failed | لم تُسلَّم. قيمة failure.stage هي send (رفض WhatsApp الطلب؛ و failure.meta_code هو رمز خطئه؛ ولم يُخصم شيء) أو delivery (قُبلت ثم فشلت؛ و failure.message هو سبب WhatsApp). |
unknown | لم تستطع Scale معرفة ما إذا كان WhatsApp قد أرسلها — راجع منع التكرار. والإرسال الذي يبقى pending بعد 5 دقائق يُقرأ أيضًا unknown. |
{
"success": true,
"message": {
"id": "msg_Q2w3E4r5T6y7U8i9O0p1A2s3",
"wamid": "wamid.HBgMMjAxMDEyMzQ1Njc4FQIAERgSQzE5",
"type": "text",
"to": "201012345678",
"status": "failed",
"failure": { "stage": "delivery", "meta_code": null, "message": "Message undeliverable" },
"created_at": "2026-09-24T10:00:00Z"
}
}- الحالة تتقدّم إلى الأمام فقط (
sent ←delivered ←read)؛ والتحديث المتأخر أو المكرر لا يعيدها إلى الخلف أبدًا. قد تصلreadدون أن يكون قد أُبلغ عنdelivered. - إنها الحالة نفسها التي يعرضها Inbox الخاص بـ Scale — تحدّث Scale الاثنين من الـ Webhook الموقّع الخاص بحالات WhatsApp.
- فضّل الـ Webhooks على الاستعلام الدوري (Polling)؛ وإذا استعلمت دوريًا فافعل ذلك باعتدال — طلبات
GETتتشارك حد الـ 300 طلب في الدقيقة. - المعرّف التابع لمساحة عمل أخرى، والمعرّف غير المعروف، والمعرّف غير الصالح، كلها ترد بالخطأ نفسه
404 MESSAGE_NOT_FOUND.
حالة اتصال WhatsApp
curl https://getscale.me/wp-json/scale/v1/whatsapp/status \
-H "Authorization: Bearer $SCALE_API_KEY"{
"success": true,
"connected": true,
"status": "connected",
"reason": null,
"provider": "meta",
"phone_number": "+201000000001",
"display_name": "Nile Leather House",
"waba_id": "123456789012345",
"phone_number_id": "109876543210987",
"connected_at": "2026-09-20T08:15:00Z",
"health": { "checked": true, "checked_at": "2026-09-24T09:58:03Z" },
"can_send": true,
"blockers": []
}| الحقل | المعنى |
|---|---|
status | connected · error (مربوط، لكن Meta رفضت الرمز المخزّن — أعد الربط) · connecting (يجري إعداد اتصال) · disconnected (كان مربوطًا ثم فُصل) · pending (لم يُربط أبدًا). |
connected | تكون true فقط عندما تكون status هي connected. |
reason | سبب عدم صلاحية الاتصال للاستخدام: token_invalid أو provider_unsupported (مربوط عبر Twilio، وهو ما لا تدعمه الواجهة)؛ وإلا null. |
provider | meta أو twilio أو null عندما لا يوجد رقم مربوط. |
phone_number و display_name و waba_id و phone_number_id و connected_at | الرقم المربوط، واسم العرض الموثّق، ومعرّفات Meta؛ و null عند عدم الربط. |
health | ما إذا كانت Scale قد تحققت من الاتصال مع Meta ومتى. نتائج التحقق تُخزَّن مؤقتًا لبضع دقائق؛ والتحقق الذي لم تستطع Meta الرد عليه لا يعني شيئًا في أي اتجاه. عندما ترفض Meta الرمز، يحتوي health.meta_code على رمز خطأ Meta. |
can_send | تكون true عندما لا يمنع شيء الإرسال — نافذة الـ 24 ساعة وحدود معدل الطلبات تعتمد على الطلب نفسه ولا تدخل هنا. |
blockers | ما يمنع الإرسال: whatsapp_not_connected أو token_invalid أو provider_unsupported أو subscription_required أو message_limit_reached. |
نقطة النهاية هذه للقراءة فقط — لا تغيّر الاتصال ولا لوحة التحكم أبدًا.
Webhooks
يمكن لـ Scale إرسال حدث (POST) إلى نظامك كلما حدث شيء في مساحة العمل. اضبط ذلك من Settings ← Developer & API ← Webhook: رابط https:// الخاص بك (على الإنترنت العام) والأحداث التي تريدها (الأحداث السبعة كلها افتراضيًا). تعرض Scale سرّ التوقيع (whsec_…) مرة واحدة عند إنشاء الـ Webhook وفي كل مرة تقوم فيها بتدويره (Rotate)؛ وبعد ذلك تعرض آخر أربعة أحرف منه فقط. زر "Send test event" يرسل حدث webhook.test لتتحقق من نقطة النهاية الخاصة بك.
كل حدث هو طلب POST بصيغة JSON وبالغلاف نفسه:
{
"event": "message.delivered",
"id": "evt_H7j8K9l0Z1x2C3v4B5n6M7q8",
"created_at": "2026-09-24T10:00:05Z",
"workspace_id": "ws_90408561-0487-485d-b513-0053582f3d55",
"data": { }
}id هو هوية الحدث ولا يتغير أبدًا، حتى عند إعادة إرسال الحدث — أزِل التكرار بناءً عليه. و workspace_id هو المعرّف العام لمساحة العمل. تُرسل الأحداث لمحادثات WhatsApp فقط.
message.received
أرسل عميل رسالة إلى رقم WhatsApp الخاص بمساحة العمل (نص أو صورة أو صوت أو مستند).
{
"message_id": "wamid.HBgMMjAxMDEyMzQ1Njc4FQIAEhgUM0E5",
"from": "201012345678",
"type": "text",
"text": { "body": "Is size 42 available?" },
"media": null,
"contact": { "name": "Ahmed" },
"timestamp": "2026-09-24T10:02:11Z"
}{
"message_id": "wamid.HBgMMjAxMDEyMzQ1Njc4FQIAEhgUM0F0",
"from": "201012345678",
"type": "image",
"text": null,
"media": { "type": "image", "url": "https://getscale.me/wp-content/uploads/scale-ai/img-7f3a.jpg", "mime": "image/jpeg", "caption": "This one" },
"contact": { "name": "Ahmed" },
"timestamp": "2026-09-24T10:03:40Z"
}- قيمة
typeهيtextأوimageأوaudioأوdocument. في الوسائط تكونtextبقيمةnullويحتويmediaعلى رابط إلى النسخة التي خزّنتها Scale، ونوعها، ونوع MIME (عند معرفته)، وتعليق العميل. الأنواع الأخرى من الرسائل الواردة لا تحملmediaفي هذا الإصدار. - ما يجب فعله: خزّن الرسالة في سجل العميل (
from)، ورد عبرPOST /messages— الرد خلال 24 ساعة لا يحتاج إلى قالب. - تصل رسالة العميل أيضًا إلى Inbox الخاص بـ Scale، وتُحتسب كرسالة واردة من الرصيد، وقد يرد عليها مساعد الذكاء الاصطناعي الخاص بمساحة العمل إذا كان مفعّلًا.
message.sent
قبل WhatsApp رسالة أرسلتها مساحة العمل — من الواجهة، أو من لوحة التحكم، أو من مساعد الذكاء الاصطناعي، أو من حملة.
{
"message_id": "wamid.HBgMMjAxMDEyMzQ1Njc4FQIAERgSQzE5",
"api_message_id": "msg_Q2w3E4r5T6y7U8i9O0p1A2s3",
"to": "201012345678",
"type": "text",
"source": "api",
"status": "sent",
"text": { "body": "Hello from our store! Your order #123 is confirmed." },
"template": null,
"timestamp": "2026-09-24T10:00:01Z"
}- قيمة
source:api(أُرسلت عبر الواجهة)، أوai(مساعد الذكاء الاصطناعي)، أوscale(شخص في لوحة التحكم، أو حملة). api_message_idهو معرّفmsg_…الخاص بك للرسائل المرسلة عبر الواجهة، وإلاnull. وقيمةtypeهيtextأوtemplateأو نوع الوسائط؛ وفي حالة القالب يحتويtemplateعلىnameوlanguageالخاصين به.- ما يجب فعله: طابِق الرسائل المرسلة عبر الواجهة مع سجلاتك بواسطة
api_message_id؛ واستخدم البقية لعكس المحادثة في نظامك.
message.delivered
{
"message_id": "wamid.HBgMMjAxMDEyMzQ1Njc4FQIAERgSQzE5",
"api_message_id": "msg_Q2w3E4r5T6y7U8i9O0p1A2s3",
"to": "201012345678",
"status": "delivered",
"timestamp": "2026-09-24T10:00:05Z"
}وصلت الرسالة إلى هاتف العميل. ما يجب فعله: علّمها كـ delivered.
message.read
{
"message_id": "wamid.HBgMMjAxMDEyMzQ1Njc4FQIAERgSQzE5",
"api_message_id": "msg_Q2w3E4r5T6y7U8i9O0p1A2s3",
"to": "201012345678",
"status": "read",
"timestamp": "2026-09-24T10:07:30Z"
}فتحها العميل — فقط إذا كان يسمح بإيصالات القراءة، لذا لا تنتظرها أبدًا.
message.failed
{
"message_id": "wamid.HBgMMjAxMDEyMzQ1Njc4FQIAERgSQzE5",
"api_message_id": "msg_Q2w3E4r5T6y7U8i9O0p1A2s3",
"to": "201012345678",
"status": "failed",
"timestamp": "2026-09-24T10:00:06Z",
"error": { "message": "Message undeliverable" }
}قبل WhatsApp الرسالة ثم لم يستطع تسليمها. error.message هو سبب WhatsApp. ما يجب فعله: أحِلها إلى شخص للمراجعة — لا تُعد إرسالها بشكل أعمى.
أحداث الحالة تتقدّم إلى الأمام فقط: الحالة التي ستعود إلى الخلف (delivered بعد read) أو تتكرر لا تُرسَل، ولا يُرسَل حدث message.sent ثانٍ للرسالة نفسها.
whatsapp.connected
{
"phone_number": "+201000000001",
"phone_number_id": "109876543210987",
"waba_id": "123456789012345",
"timestamp": "2026-09-24T10:04:12Z"
}تم ربط رقم WhatsApp بمساحة العمل — من لوحة التحكم أو عبر الربط المستضاف. ما يجب فعله: فعّل ميزات WhatsApp في نظامك.
whatsapp.disconnected
{
"phone_number_id": "109876543210987",
"timestamp": "2026-09-24T12:00:00Z"
}فُصل الرقم في Scale. عمليات الإرسال ترد بـ 403 WHATSAPP_NOT_CONNECTED حتى يُربط من جديد. ما يجب فعله: أوقف الإرسال مؤقتًا وأبلغ مستخدميك.
webhook.test
{ "message": "This is a test event from Scale.", "timestamp": "2026-09-24T09:00:00Z" }يُرسَل فقط عند الضغط على "Send test event". وهو موقّع مثل أي حدث آخر.
أمان الـ Webhooks
| الترويسة | القيمة |
|---|---|
X-Scale-Event | اسم الحدث، مثل message.delivered. |
X-Scale-Event-Id | معرّف الحدث (evt_…) — هو نفسه id في جسم الطلب. |
X-Scale-Timestamp | وقت Unix بالثواني الذي وُقّعت فيه محاولة التسليم هذه. |
X-Scale-Signature | sha256= متبوعًا بقيمة HMAC-SHA256 بالنظام الست عشري (Hex). |
X-Scale-Delivery-Attempt | 1 للمحاولة الأولى، و 2 لأول إعادة محاولة، وهكذا. |
التوقيع هو HMAC-SHA256(secret, timestamp + "." + raw body) بترميز Hex ومسبوقًا بـ sha256=. للتحقق من حدث:
- اقرأ جسم الطلب الخام بايتًا بايتًا — قبل أي تحليل أو إعادة ترميز لـ JSON.
- احسب
sha256=+ قيمة HMAC-SHA256 بترميز Hex لـX-Scale-Timestamp+.+ الجسم الخام، باستخدام سرّ الـ Webhook كمفتاح. - قارِن النتيجة مع
X-Scale-Signatureبمقارنة ثابتة الزمن (Constant-time). - ارفض أي طابع زمني يبعد أكثر من 5 دقائق عن ساعة خادمك (للحماية من إعادة الإرسال Replay).
- أزِل التكرار بناءً على
X-Scale-Event-Id: قد يصل الحدث نفسه أكثر من مرة.
<?php
$secret = getenv('SCALE_WEBHOOK_SECRET'); // whsec_…
$body = file_get_contents('php://input'); // the RAW body
$ts = $_SERVER['HTTP_X_SCALE_TIMESTAMP'] ?? '';
$sig = $_SERVER['HTTP_X_SCALE_SIGNATURE'] ?? '';
$fresh = ctype_digit($ts) && abs(time() - (int) $ts) <= 300;
$expected = 'sha256=' . hash_hmac('sha256', $ts . '.' . $body, $secret);
if (!$fresh || !hash_equals($expected, $sig)) {
http_response_code(401);
exit;
}
$event = json_decode($body, true);
// Seen $event['id'] before? Answer 200 and stop.
// Otherwise record it, answer 200 quickly, and do the work afterwards.
http_response_code(200);const crypto = require('crypto');
const express = require('express');
const app = express();
app.post('/scale/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const secret = process.env.SCALE_WEBHOOK_SECRET; // whsec_…
const ts = req.get('X-Scale-Timestamp') || '';
const sig = req.get('X-Scale-Signature') || '';
const raw = req.body.toString('utf8'); // the RAW body
const fresh = /^\d+$/.test(ts) && Math.abs(Date.now() / 1000 - Number(ts)) <= 300;
const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(ts + '.' + raw).digest('hex');
const a = Buffer.from(expected), b = Buffer.from(sig);
if (!fresh || a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
return res.sendStatus(401);
}
const event = JSON.parse(raw);
// De-duplicate on event.id, answer quickly, process afterwards.
res.sendStatus(200);
});إعادة المحاولة والتسليم في الـ Webhooks
- النجاح هو أي رد
2xxخلال 5 ثوانٍ. أجب بسرعة ونفّذ الأعمال البطيئة بعد ذلك. - أي شيء آخر — رد
3xxلا ينتهي بـ2xx، أو أي4xxأو5xx، أو انتهاء المهلة، أو خطأ في الاتصال — يُعد محاولة فاشلة، ويُعاد إرسال الحدث بعد 1، 2، 5، 15، 30، 60 و 120 دقيقة. - بعد 8 محاولات إجمالًا يُعلَّم الحدث كفاشل نهائيًا ولا يُرسَل مرة أخرى. تعرض بطاقة Webhook في لوحة التحكم عمليات التسليم الأخيرة ونتيجتها.
- إعادة المحاولة ترسل الحدث نفسه:
X-Scale-Event-Idنفسه وجسمًا مطابقًا بايتًا بايتًا. فقطX-Scale-TimestampوX-Scale-SignatureوX-Scale-Delivery-Attemptتكون جديدة — لذا تحقّق من التوقيع في كل محاولة، وأزِل التكرار بناءً على معرّف الحدث. - الترتيب: الأحداث الخاصة بالرسالة نفسها تُسلَّم بترتيب حدوثها —
message.sentثمmessage.deliveredثمmessage.read؛ والحدث اللاحق ينتظر أثناء إعادة محاولة حدث سابق (والحدث السابق الذي يفشل نهائيًا يُطلق التالي). أما الأحداث الخاصة برسائل مختلفة فلا ترتيب بينها. - التسليم غير متزامن (Asynchronous) ومضمون مرة واحدة على الأقل (At-least-once). لا تستدعي Scale نقطة النهاية الخاصة بك من داخل طلب آخر أبدًا، ونقطة نهاية بطيئة أو فاشلة لا تؤخر أبدًا الـ Webhooks الخاصة بأعمال أخرى.
- تغيير رابط الـ Webhook يسري على الأحداث التي ما زالت تنتظر؛ وحذف الـ Webhook يُنهي التسليم، وتُسقَط الأحداث التي لم تُسلَّم بعد.
الاستخدام والفوترة
ليس للواجهة حصة أو رصيد أو باقة خاصة بها. كل رسالة عبر الواجهة تستخدم رصيد رسائل مساحة العمل نفسه الذي يستخدمه Inbox ومساعد الذكاء الاصطناعي والحملات.
- تُستخدم رسالة واحدة عندما يقبل WhatsApp رسالة مرسلة عبر الواجهة (أي نوع، بما في ذلك القوالب).
- لا يُستخدم شيء عندما تُرفض الرسالة قبل الإرسال، أو يرفضها WhatsApp (
outcome: "refused")، أو تنتهي بـunknown. - رسائل العملاء الواردة، وردود لوحة التحكم، وردود الذكاء الاصطناعي، ورسائل الحملات (رسالة لكل مستلم) تستخدم الرصيد نفسه.
- عند نفاد الرصيد ترد عمليات الإرسال بـ
403 MESSAGE_LIMIT_REACHED؛ ودون اشتراك فعّال بـ403 SUBSCRIPTION_REQUIRED. - رسوم محادثات WhatsApp الخاصة بـ Meta منفصلة: تفوترها Meta على حساب WhatsApp Business.
curl https://getscale.me/wp-json/scale/v1/usage \
-H "Authorization: Bearer $SCALE_API_KEY"{
"success": true,
"period": { "basis": "subscription_cycle", "starts_at": "2026-09-01T00:00:00Z", "ends_at": "2026-10-01T00:00:00Z" },
"messages": { "limit": 10000, "used": 812, "remaining": 9188, "unlimited": false },
"breakdown": { "dashboard": 120, "ai": 390, "api": 214, "incoming": 88, "campaigns": 0, "total": 812 },
"shared_allowance": true,
"can_send": true
}| الحقل | المعنى |
|---|---|
period | قيمة basis هي subscription_cycle (دورة الفوترة الحالية) أو last_30_days؛ و starts_at / ends_at بتوقيت UTC. |
messages | الرصيد الواحد: limit و used و remaining. تكون limit و remaining بقيمة null عندما تكون unlimited بقيمة true. |
breakdown | مصدر الرسائل المستخدمة في الفترة نفسها: dashboard (أشخاص في Scale)، و ai، و api، و incoming (رسائل العملاء)، و campaigns، ومجموعها total. |
shared_allowance | دائمًا true — الواجهة تتشارك رصيد مساحة العمل. |
can_send | ما إذا كان الاشتراك والرصيد يسمحان بالإرسال الآن. |
حدود معدل الطلبات (Rate Limits)
| الطلبات | الحد |
|---|---|
POST /messages | 60 طلبًا في الدقيقة لكل مساحة عمل |
| كل طلبات الواجهة الأخرى | 300 طلب في الدقيقة لكل مساحة عمل |
- الحدود تخص مساحة العمل: كل مفاتيح API الخاصة بها تتشاركها.
- كل دقيقة نافذة ثابتة على الساعة؛ ويُعاد ضبط الحد مع بداية الدقيقة التالية.
- كل رد يذكر
X-RateLimit-LimitوX-RateLimit-RemainingوX-RateLimit-Reset(وقت Unix الذي تُعاد فيه النافذة). - عند تجاوز الحد:
429 RATE_LIMITEDمع الترويسةRetry-Afterبالثواني. الطلب المرفوض لا يفعل شيئًا — لا رسالة ولا خصم. - رد 429 مختلف —
details.reason: "send_in_progress"وRetry-After: 1— يعني أن رسالة أخرى من مساحة العمل كانت قيد الإرسال في تلك اللحظة. أعد المحاولة بالقيمة نفسها لـ Idempotency-Key.
HTTP/1.1 429 Too Many Requests
Retry-After: 37
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1790161260
{
"success": false,
"error": {
"code": "RATE_LIMITED",
"message": "Too many requests for this workspace. Retry after the number of seconds in Retry-After.",
"details": { "limit": 60, "retry_after": 37 }
},
"request_id": "req_k2V9pQ7sL1mN4xR8tY3wZ6aB"
}الأخطاء
اقرأ error.code — فهو ثابت. أما error.message فهو موجّه للبشر وقد يتغير؛ و error.details يضيف حقائق عند وجودها.
| HTTP | error.code | المعنى | ما يجب فعله |
|---|---|---|---|
| 401 | INVALID_API_KEY | المفتاح مفقود أو مشوّه أو غير معروف — أو أُرسل مفتاح في الرابط. | أرسل Authorization: Bearer sk_live_… في الترويسة فقط. |
| 401 | API_KEY_REVOKED | أُلغي المفتاح من لوحة التحكم. | استخدم مفتاحًا فعّالًا آخر. |
| 403 | API_ACCESS_DISABLED | الوصول إلى Public API غير مفعّل لمساحة العمل هذه. لم يُنفَّذ شيء. | تواصل مع Scale لتفعيله. المفاتيح والإعدادات محفوظة. |
| 404 | WORKSPACE_NOT_FOUND | مساحة العمل الخاصة بالمفتاح مؤرشفة أو محذوفة. | تواصل مع مالك مساحة العمل. |
| 403 | WHATSAPP_NOT_CONNECTED | لا يوجد رقم WhatsApp مربوط (details.reason: not_connected)، أو أنه مربوط عبر Twilio (provider_unsupported). | اربط WhatsApp (من لوحة التحكم أو عبر الربط المستضاف). |
| 403 | SUBSCRIPTION_REQUIRED | لا يوجد اشتراك فعّال؛ و details.reason مثل not_subscribed أو plan_expired أو suspended. | جدّد الاشتراك. |
| 403 | MESSAGE_LIMIT_REACHED | نفد رصيد رسائل مساحة العمل. | اشحن الرصيد أو انتظر الدورة التالية. |
| 400 | INVALID_MESSAGE | جسم الطلب ليس كائن JSON، أو قيمة Idempotency-Key غير صالحة. | صحّح الطلب. |
| 422 | INVALID_MESSAGE | حقل مفقود أو غير صالح (النوع، أو النص، أو القالب، أو رابط الوسائط، أو طول التعليق). الرسالة تذكر أيّها. | صحّح الطلب. |
| 422 | INVALID_PHONE_NUMBER | قيمة to ليست رقمًا دوليًا صالحًا للاستخدام، أو تخص محادثة غير WhatsApp في مساحة العمل. | أرسل الرقم الكامل مع رمز الدولة. |
| 422 | OUTSIDE_MESSAGING_WINDOW | نص أو وسائط خارج نافذة الـ 24 ساعة. | أرسل قالبًا معتمدًا. |
| 409 | IDEMPOTENCY_CONFLICT | استُخدمت قيمة Idempotency-Key هذه لطلب مختلف (details.message_id). | استخدم مفتاحًا جديدًا للرسالة الجديدة. |
| 429 | RATE_LIMITED | بلغت حد معدل الطلبات، أو details.reason: send_in_progress. | انتظر Retry-After ثانية، ثم أعد المحاولة بالمفتاح نفسه. |
| 502 | META_API_ERROR | details.outcome: "refused" — رفض WhatsApp الرسالة (meta_code و meta_message)؛ لم يُرسَل شيء ولم يُخصم شيء. details.outcome: "unknown" — لم يؤكد WhatsApp؛ لم تُخصم، ولن تُرسَل مرة أخرى أبدًا (retry_safe: false). | عند الرفض: صحّح وأعد المحاولة. عند النتيجة غير المعروفة: راجع Inbox قبل الإرسال مرة أخرى بمفتاح جديد. |
| 404 | MESSAGE_NOT_FOUND | لا توجد رسالة كهذه في مساحة العمل هذه. | تحقّق من معرّف msg_…. |
| 404 | ONBOARDING_SESSION_NOT_FOUND | لا توجد جلسة ربط كهذه في مساحة العمل هذه. | تحقّق من معرّف onb_…. |
| 409 | WHATSAPP_ALREADY_CONNECTED | يوجد اتصال WhatsApp يعمل بالفعل. | افصل الاتصال في Scale قبل ربط رقم آخر. |
| 422 | INVALID_RETURN_URL | قيمة return_url غير مسجّلة بالضبط، أو ليست رابط https:// صالحًا. | سجّلها في Developer & API ← Onboarding return URLs. |
| 503 | ONBOARDING_UNAVAILABLE | الربط المستضاف غير متاح الآن. | اربط من لوحة التحكم، أو حاول لاحقًا. |
| 500 | INTERNAL_ERROR | حدث خطأ من جهة Scale. لم يُرسَل شيء. | أعد المحاولة لاحقًا؛ واذكر request_id إذا استمر الخطأ. |
{
"success": false,
"error": {
"code": "META_API_ERROR",
"message": "WhatsApp refused the message. Nothing was sent and nothing was charged.",
"details": {
"message_id": "msg_Q2w3E4r5T6y7U8i9O0p1A2s3",
"outcome": "refused",
"meta_code": 132001,
"meta_message": "Template name does not exist in the translation",
"charged": false
}
},
"request_id": "req_k2V9pQ7sL1mN4xR8tY3wZ6aB"
}{
"success": false,
"error": {
"code": "META_API_ERROR",
"message": "WhatsApp did not confirm this message. It may or may not have been delivered, so it will NOT be sent again and was not charged. Check the conversation before sending a new one.",
"details": {
"message_id": "msg_Q2w3E4r5T6y7U8i9O0p1A2s3",
"outcome": "unknown",
"charged": false,
"retry_safe": false,
"replayed": false
}
},
"request_id": "req_k2V9pQ7sL1mN4xR8tY3wZ6aB"
}قائمة التحقق قبل تشغيل التكامل في Production
- الوصول إلى Public API مفعّل لمساحة العمل.
- مفتاح API مُنشأ، ومحفوظ على خادمك فقط، وليس أبدًا في رابط أو متصفح أو تطبيق.
- WhatsApp مربوط —
GET /whatsapp/statusتعرض"can_send": true. - الـ Webhook مضبوط على رابط
https://، وسرّه محفوظ بأمان. - كل Webhook يُتحقق منه: التوقيع، وحداثة الطابع الزمني، والمقارنة ثابتة الزمن.
- أحداث الـ Webhook يُزال تكرارها بناءً على معرّف الحدث، ونقطة النهاية الخاصة بك ترد بـ
2xxخلال 5 ثوانٍ. - كل عملية إرسال تحمل Idempotency-Key ذا معنى، ويُعاد استخدامه عند إعادة المحاولة.
- الرد
429يُعالَج باستخدامRetry-After؛ وsend_in_progressيُعاد بالمفتاح نفسه. - الخطأ
META_API_ERRORيُعالَج:refusedيُصحَّح ويُعاد، وunknownيُحال إلى شخص. - النصوص والوسائط تُرسل فقط داخل نافذة الـ 24 ساعة؛ والقوالب تُستخدم لبدء المحادثات.
- حالة الرسائل تُتابَع عبر الـ Webhooks (أو عبر
GET /messages/{id}من حين لآخر). - سلوك إعادة المحاولة جُرّب على نقطة نهاية Staging الخاصة بك (الرد بـ 500 مرة واحدة، ثم 200).
مثال تكامل كامل: نظام CRM
نظام CRM يرسل تأكيدًا للطلب ويُبقي المحادثة متزامنة.
- ينشئ المالك مفتاح API في Scale ويحفظه في إعدادات الخادم الخاصة بالـ CRM؛ ويحفظ الـ CRM رابط الـ Webhook وسرّه في Scale.
- يتحقق الـ CRM عبر
GET /whatsapp/status. إذا كانتcan_sendبقيمة false لأن WhatsApp غير مربوط، يستدعيPOST /whatsapp/onboarding/sessionويعرض على المالكonboarding_url؛ ويصلwhatsapp.connectedعند الانتهاء. - يُسجَّل طلب جديد. لم يكتب العميل خلال آخر 24 ساعة، لذا يرسل الـ CRM القالب المعتمد
order_confirmationمعIdempotency-Key: order-5001-confirmation، ويحفظ معرّفmsg_…المُعاد مع الطلب. - تصل
message.sentثمmessage.deliveredثمmessage.read؛ فيطابقها الـ CRM بواسطةapi_message_idويحدّث السجل الزمني للطلب. - يرد العميل — فيصل
message.receivedومعهfromوالنص. يربط الـ CRM الرسالة بالعميل. - خلال 24 ساعة من ذلك الرد، يجيب الـ CRM برسالة نصية عادية.
curl -X POST https://getscale.me/wp-json/scale/v1/messages \
-H "Authorization: Bearer $SCALE_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-5001-confirmation" \
-d '{
"to": "201012345678",
"type": "template",
"template": {
"name": "order_confirmation",
"language": "en",
"components": [ { "type": "body", "parameters": [ { "type": "text", "text": "#5001" } ] } ]
}
}'{
"event": "message.received",
"id": "evt_P9o8I7u6Y5t4R3e2W1q0A9s8",
"created_at": "2026-09-24T11:20:00Z",
"workspace_id": "ws_90408561-0487-485d-b513-0053582f3d55",
"data": {
"message_id": "wamid.HBgMMjAxMDEyMzQ1Njc4FQIAEhgUQkE3",
"from": "201012345678",
"type": "text",
"text": { "body": "Thanks! Can you deliver on Saturday?" },
"media": null,
"contact": { "name": "Ahmed" },
"timestamp": "2026-09-24T11:20:00Z"
}
}curl -X POST https://getscale.me/wp-json/scale/v1/messages \
-H "Authorization: Bearer $SCALE_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-5001-reply-1" \
-d '{"to":"201012345678","type":"text","text":{"body":"Yes — Saturday between 10 am and 2 pm."}}'