MessageHub · التوثيق

اربط نظامك بـ MessageHub في دقائق

واجهة REST بسيطة بصيغة JSON لإرسال الرسائل عبر واتساب وماسنجر وإنستغرام وتيليغرام وتيك توك، ومتابعة حالتها، واستقبال ردود العملاء لحظيًا بالويب هوك. هذا التوثيق مفتوح للجميع، ولا يحتاج تسجيل دخول.

WhatsAppMessengerInstagramTelegramTikTok
POST /v1/messages/send
curl -X POST https://derha.net/api/v1/messages/send \
  -H "Authorization: Bearer mh_live_xxxxxxxxxxxxxxxx_your_secret" \
  -H "Idempotency-Key: order-4821" \
  -d '{ "number_id": "7f2a1c9e-3b4d-5e6f-7a8b-9c0d1e2f3a4b",
        "to": "201014642581",
        "text": "Your order has shipped." }'
202 Accepted
{ "success": true,
  "data": { "message": { "status": "queued" } } }

كل ما تحتاجه للربط في واجهة واحدة

واجهة واحدة لكل المنصات

أرسل عبر واتساب وماسنجر وإنستغرام وتيليغرام وتيك توك بالطلب نفسه. القناة هي التي تتغير فقط.

ردود العملاء لحظيًا

ويب هوك موقّع يصل إلى خادمك مع كل رسالة جديدة وكل تغيّر في الحالة.

تتبع التسليم

من الطابور إلى الإرسال والتسليم والقراءة، مع سبب واضح عند الفشل.

إعادة محاولة آمنة

مفتاح Idempotency-Key يمنع تكرار الرسالة، والويب هوك يُعاد تسليمه تلقائيًا حتى 8 محاولات.

أمان من البداية

HTTPS فقط، ومفاتيح يمكن تقييدها بعناوين IP، وتوقيع HMAC لكل حدث مع حماية من إعادة التشغيل.

مفتوح للنماذج الذكية

مواصفة OpenAPI وملفات llms.txt تجعل أي نموذج يفهم التوثيق ويكتب الربط بدقة.

كيف يعمل

1

أنشئ مفتاحًا

من لوحة التحكم، في ثوانٍ.

2

أرسل طلب POST

رقم المستلم والنص، والباقي علينا.

3

استقبل الأحداث

الحالة والردود تصل إلى ويب هوك خادمك.

استخدامات شائعة

رموز التحقق

رسائل جاهزة بخانات، ويمكن ألا يُحفظ الرمز في قاعدة البيانات.

إشعارات الطلبات

تأكيد الشحن والتسليم والإيصالات تلقائيًا.

ربط CRM

كل رسالة واردة تصل إلى نظامك مع بيانات جهة الاتصال.

روبوتات المحادثة

استقبل الرسالة وردّ على المحادثة نفسها بـ conversation_id.

عنوان الأساسhttps://derha.net/api/v1
الإصدارv1
الصيغةapplication/json · UTF-8
AuthAuthorization: Bearer <token>

ابدأ في 5 دقائق #

المطلوب خمس خطوات: مفتاح، ثم معرّف القناة، ثم أول رسالة، ثم متابعة حالتها، ثم استقبال ردود العملاء.

  1. أنشئ مفتاح API. من لوحة التحكم افتح «واجهة API للمطورين» ثم «المفاتيح» ثم أنشئ مفتاحًا. يظهر الرمز مرة واحدة فقط، فانسخه واحفظه على الخادم. تحتاج رصيد رسائل API في مساحة العمل، وإلا يرد الطلب بالكود api_not_enabled.
  2. اعرف معرّف القناة التي سترسل منها: GET /v1/numbers ثم انسخ قيمة id لقناة حالتها connected.
  3. أرسل أول رسالة: POST /v1/messages/send. يرد الخادم فورًا بالرمز 202 ومعه معرّف الرسالة، والإرسال الفعلي يتم بعدها.
  4. تابع الحالة: GET /v1/messages/show/{id}. تمر الرسالة بالحالات queued ثم sent ثم delivered ثم read، أو failed.
  5. استقبل الردود: من «الويب هوك» أضف رابط HTTPS على خادمك واختر الحدث message.received. يصلك POST موقّع عند كل رسالة جديدة من عميل.

الخطوة 2: القنوات

curl https://derha.net/api/v1/numbers \
  -H "Authorization: Bearer mh_live_xxxxxxxxxxxxxxxx_your_secret"

الخطوة 3: أول رسالة

curl -X POST https://derha.net/api/v1/messages/send \
  -H "Authorization: Bearer mh_live_xxxxxxxxxxxxxxxx_your_secret" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-4821" \
  -d '{
  "number_id": "7f2a1c9e-3b4d-5e6f-7a8b-9c0d1e2f3a4b",
  "to": "201014642581",
  "type": "text",
  "text": "Hello, your order #4821 has shipped."
}'

الخطوة 4: الحالة

curl https://derha.net/api/v1/messages/show/3c9d8e7f-1a2b-3c4d-5e6f-7a8b9c0d1e2f \
  -H "Authorization: Bearer mh_live_xxxxxxxxxxxxxxxx_your_secret"

للذكاء الاصطناعي والأدوات #

هذه الصفحة مكتوبة بـ HTML ثابت بلا جافاسكربت، ويقرؤها أي زاحف أو نموذج ذكاء اصطناعي مباشرة. لمن يفضّل صيغة أخف أو صيغة آلية توجد هذه الملفات العامة:

الملفما هو
https://derha.net/llms.txtفهرس قصير للتوثيق حسب معيار llms.txt، مع روابط كل الملفات.
https://derha.net/llms-full.txtالتوثيق الكامل في ملف Markdown واحد بالإنجليزية. ألصقه كما هو في سياق النموذج.
https://derha.net/llms-full.ar.txtالنسخة الكاملة نفسها بالعربية.
https://derha.net/api-docs/openapi.jsonمواصفة OpenAPI 3.1 لكل المسارات. تصلح لتوليد عميل برمجي أو لتعريف أدوات لنموذج ذكاء اصطناعي.

ملخص لنموذج يكتب الكود

  • العنوان الأساس: https://derha.net/api/v1. الصيغة JSON بترميز UTF-8. HTTPS فقط.
  • المصادقة: ترويسة Authorization: Bearer <token> في كل طلب. لا تضع الرمز في كود المتصفح.
  • كل استجابة بالشكل { success, message, data } عند النجاح، و { success:false, code, message } عند الخطأ. ابنِ المنطق على code وليس على message.
  • الإرسال غير متزامن: POST /messages/send يرد 202 فقط، والنتيجة النهائية تأتي من GET /messages/show/{id} أو من ويب هوك.
  • أرسل Idempotency-Key مع كل إرسال حتى لا تتكرر الرسالة عند إعادة المحاولة.
  • حدّد المستلم بطريقة واحدة فقط: to مع number_id، أو conversation_id وحده، أو contact_id مع number_id.
  • تحقق من توقيع كل ويب هوك قبل الوثوق به، واستعمل معرّف الحدث لتجاهل التكرار.
  • لا تخترع مسارات أو حقولًا غير موجودة هنا. المسارات المذكورة في هذا التوثيق هي كل ما يتاح.

المفاهيم الأساسية #

المفهومالمعنى
مساحة العملالحساب الذي يملك المفاتيح والرصيد. كل مفتاح يعمل داخل مساحة عمل واحدة فقط ولا يرى غيرها.
الشركةنشاط داخل مساحة العمل، وله قنواته وعملاؤه. المعرّف: business_id.
القناةخط التواصل الذي تخرج منه الرسائل: رقم واتساب، أو حساب ماسنجر أو إنستغرام أو تيليغرام أو تيك توك. معرّفها number_id (واسمه الآخر channel_id).
جهة الاتصالالعميل. قد يكون له رقم هاتف، وقد يُعرف بمعرّف المنصة فقط. المعرّف: contact_id.
المحادثةالخيط بين قناة وجهة اتصال. المعرّف: conversation_id. للرد على محادثة تعرفها أرسل conversation_id وحده.
الرسالةنص أو ملف، واردة (inbound) أو صادرة (outbound).
الويب هوكرابط HTTPS على خادمك يستقبل أحداثًا موقّعة. لا يحتاج مفتاح API.

المصادقة #

كل طلب يحمل رمز المفتاح في ترويسة Authorization بنمط Bearer. يُصدَر الرمز مرة واحدة عند إنشاء المفتاح ولا يمكن استرجاعه بعد ذلك. الرمز يبدأ بـ mh_live.

Authorization: Bearer mh_live_xxxxxxxxxxxxxxxx_your_secret

يمكن تقييد المفتاح بعناوين IP أو بأصول (origin) محددة. الطلب من غير المسموح يرد بـ ip_not_allowed أو origin_not_allowed. الطلب عبر HTTP العادي يُرفض بـ insecure_transport.

القواعد العامة #

غلاف الاستجابة

كل الاستجابات بالشكل نفسه. النجاح يحمل success=true مع كائن data. الخطأ يحمل success=false مع code ثابت (آمن للبناء عليه) ورسالة مقروءة.

// success
{ "success": true, "message": "...", "data": { } }

// error
{ "success": false, "code": "validation_failed", "message": "..." }

منع التكرار (Idempotency)

عند الإرسال أضف ترويسة Idempotency-Key بقيمة فريدة لكل عملية، مثل رقم الطلب. إذا أعدت الطلب بالقيمة نفسها بسبب انقطاع الشبكة تحصل على النتيجة نفسها دون إرسال الرسالة مرتين. استعمال القيمة نفسها مع جسم مختلف يرد بـ idempotency_key_reused، والطلب الذي ما زال قيد المعالجة يرد بـ request_in_progress.

حدود المعدل

الطلبات محدودة لكل مفتاح ولكل عنوان IP. عند تجاوز الحد يرد 429 مع الكود rate_limited وترويسة Retry-After فيها عدد الثواني قبل إعادة المحاولة. انتظر هذه المدة ثم أعد الطلب.

الترقيم

نقاط القوائم تقبل page وper_page (الأقصى 100) وتعيد meta.pagination بالحقول total وper_page وcurrent_page وlast_page.

الأرقام والتواريخ

  • أرقام الهاتف بأرقام دولية بلا علامة +، مثل 201014642581.
  • التواريخ بصيغة ISO 8601 مثل 2026-10-03T08:30:00+00:00.
  • المعرّفات UUID. نص الرسالة يُحفظ ويُعاد كما أُرسل تمامًا.

المسارات #

الطريقةالمسارالاستخدام
GET/v1/businessesعرض الشركات والقنوات
GET/v1/businesses/{id}تفاصيل شركة
GET/v1/numbersالقنوات
POST/v1/messages/sendإرسال رسالة
GET/v1/custom-messagesالرسائل المخصصة
POST/v1/messages/send-customإرسال رسالة مخصصة
GET/v1/messages/show/{id}حالة رسالة
GET/v1/messages/listقائمة الرسائل المُرسَلة
GET/v1/messages/inboundالرسائل الواردة
GET/v1/media/{id}تنزيل ملف
GET/v1/balanceالرصيد
GET/v1/webhooks/eventsأحداث الويب هوك
GET/v1/webhooksعرض الويب هوك
POST/v1/webhooks/{id}/testإرسال حدث تجريبي
GET/v1/webhooks/deliveriesسجل التسليم
POST/v1/webhooks/deliveries/{id}/redeliverإعادة إرسال حدث

عرض الشركات والقنوات

GET/v1/businesses

يعيد كل الشركات في مساحة عملك مع قنواتها. استعمل number_id لكل قناة لاختيار القناة المُرسِلة.

curl https://derha.net/api/v1/businesses \
  -H "Authorization: Bearer mh_live_xxxxxxxxxxxxxxxx_your_secret"

الاستجابة 200 OK

{
  "success": true,
  "message": "...",
  "data": {
    "businesses": [
      {
        "id": "9b1c2d3e-4f5a-6b7c-8d9e-0f1a2b3c4d5e",
        "name": "Al Amal Store",
        "numbers": [
          {
            "number_id": "7f2a1c9e-3b4d-5e6f-7a8b-9c0d1e2f3a4b",
            "name": "Sales",
            "phone": "9665xxxxxxxx",
            "channel": "whatsapp",
            "status": "connected",
            "connected": true
          }
        ]
      }
    ]
  }
}

تفاصيل شركة

GET/v1/businesses/{id}

يعيد شركة واحدة بمعرّفها مع قنواتها. شركة خارج مساحة عملك ترد بـ 404 والكود business_not_found.

curl https://derha.net/api/v1/businesses/9b1c2d3e-4f5a-6b7c-8d9e-0f1a2b3c4d5e \
  -H "Authorization: Bearer mh_live_xxxxxxxxxxxxxxxx_your_secret"

القنوات

GET/v1/numbers

قنوات مساحة العمل: المعرّف id (وهو number_id)، والمنصة channel، والاسم العام handle حيث لا يوجد رقم هاتف، وهل تستطيع القناة بدء محادثة can_initiate (any أو known أو none)، وهل هي متصلة الآن connected. المسار /v1/channels يعيد القائمة نفسها تحت المفتاح channels.

curl https://derha.net/api/v1/numbers \
  -H "Authorization: Bearer mh_live_xxxxxxxxxxxxxxxx_your_secret"

الاستجابة 200 OK

{
  "success": true,
  "data": {
    "numbers": [
      {
        "id": "7f2a1c9e-3b4d-5e6f-7a8b-9c0d1e2f3a4b",
        "business_id": "9b1c2d3e-4f5a-6b7c-8d9e-0f1a2b3c4d5e",
        "name": "Sales",
        "phone": "201014642581",
        "channel": "whatsapp",
        "handle": null,
        "can_initiate": "any",
        "status": "connected",
        "connected": true,
        "official": true,
        "mode": "conversations",
        "created_at": "2026-09-01T10:00:00+00:00"
      }
    ]
  }
}

إرسال رسالة

POST/v1/messages/send

يضع رسالة نصية في طابور الإرسال ويرد 202 فورًا. تابع حالتها بعد ذلك بنقطة «حالة رسالة» أو بالويب هوك.

الترويسات

الاسمالنوعإلزاميالوصف
Idempotency-Keystringلاقيمة فريدة تمنع تكرار الإرسال عند إعادة المحاولة.

معطيات الجسم (JSON)

حدّد المستلم بطريقة واحدة فقط: to مع number_id، أو conversation_id وحده، أو contact_id مع number_id. إرسال أكثر من طريقة يرد بـ validation_failed.

الاسمالنوعإلزاميالوصف
number_iduuidلامعرّف القناة المُرسِلة. مطلوب مع to أو contact_id.
channel_iduuidلااسم آخر لـ number_id، للقنوات التي ليست أرقامًا.
conversation_iduuidلامحادثة موجودة على أي منصة. تخرج الرسالة على قناتها، فلا يلزم number_id.
contact_iduuidلاجهة اتصال لها حساب على منصة القناة المختارة. تُفتح محادثتها إذا لم تكن موجودة.
tostringلارقم المستلم بالصيغة الدولية مع رمز الدولة، مثل 201014642581. يُقبل + والمسافات ويُزالان. الرقم المحلي بلا رمز دولة (01014642581) يُرفض.
textstringنعمنص الرسالة، حتى 4096 حرفًا.
typestringلانوع الرسالة. المدعوم حاليًا text وهو الافتراضي.
transactionalbooleanلااجعله true عندما تخص الرسالة طلب العميل أو حجزه أو حسابه (إيصال، موعد، رمز). يلزم فقط لعميل طلب إيقاف الرسائل. انظر «العملاء الذين طلبوا التوقف».
cURL
curl -X POST https://derha.net/api/v1/messages/send \
  -H "Authorization: Bearer mh_live_xxxxxxxxxxxxxxxx_your_secret" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-4821" \
  -d '{
  "number_id": "7f2a1c9e-3b4d-5e6f-7a8b-9c0d1e2f3a4b",
  "to": "201014642581",
  "type": "text",
  "text": "Hello, your order #4821 has shipped."
}'
cURL (reply to a conversation)
curl -X POST https://derha.net/api/v1/messages/send \
  -H "Authorization: Bearer mh_live_xxxxxxxxxxxxxxxx_your_secret" \
  -H "Content-Type: application/json" \
  -d '{
  "conversation_id": "9c0d1e2f-3a4b-5c6d-7e8f-9a0b1c2d3e4f",
  "text": "We received your message."
}'
Node.js
const res = await fetch("https://derha.net/api/v1/messages/send", {
  method: "POST",
  headers: {
    "Authorization": "Bearer " + process.env.MESSAGEHUB_TOKEN,
    "Content-Type": "application/json",
    "Idempotency-Key": "order-4821",
  },
  body: JSON.stringify({
    number_id: "7f2a1c9e-3b4d-5e6f-7a8b-9c0d1e2f3a4b",
    to: "201014642581",
    type: "text",
    text: "Hello, your order #4821 has shipped.",
  }),
})
const data = await res.json()
if (!data.success) throw new Error(data.code)
Python
import os, requests

res = requests.post(
    "https://derha.net/api/v1/messages/send",
    headers={
        "Authorization": "Bearer " + os.environ["MESSAGEHUB_TOKEN"],
        "Idempotency-Key": "order-4821",
    },
    json={
        "number_id": "7f2a1c9e-3b4d-5e6f-7a8b-9c0d1e2f3a4b",
        "to": "201014642581",
        "type": "text",
        "text": "Hello, your order #4821 has shipped.",
    },
    timeout=10,
)
data = res.json()
if not data["success"]:
    raise RuntimeError(data["code"])
PHP (Laravel)
$response = Http::withToken(env('MESSAGEHUB_TOKEN'))
    ->withHeaders(['Idempotency-Key' => 'order-4821'])
    ->post('https://derha.net/api/v1/messages/send', [
        'number_id' => '7f2a1c9e-3b4d-5e6f-7a8b-9c0d1e2f3a4b',
        'to'        => '201014642581',
        'type'      => 'text',
        'text'      => 'Hello, your order #4821 has shipped.',
    ]);

الاستجابة 202 Accepted

{
  "success": true,
  "message": "...",
  "data": {
    "message": {
      "id": "3c9d8e7f-1a2b-3c4d-5e6f-7a8b9c0d1e2f",
      "number_id": "7f2a1c9e-3b4d-5e6f-7a8b-9c0d1e2f3a4b",
      "to": "201014642581",
      "type": "text",
      "status": "queued",
      "created_at": "2026-07-11T10:00:00+00:00"
    }
  }
}

الرسائل المخصصة

GET/v1/custom-messages

الرسائل الجاهزة في مساحة العمل وما تتوقعه كل خانة فيها: هل تُملأ من بيانات العميل أم ترسلها أنت، وهل تُحفظ بعد الإرسال. المعرّف الذي يبدأ بـ lib: رسالة جاهزة من مكتبة المنصة وتُرسل بالطريقة نفسها. إنشاء الرسائل يتم من لوحة التحكم فقط.

curl https://derha.net/api/v1/custom-messages \
  -H "Authorization: Bearer mh_live_xxxxxxxxxxxxxxxx_your_secret"

إرسال رسالة مخصصة

POST/v1/messages/send-custom

يرسل رسالة جاهزة بعد ملء خاناتها. الخانات المربوطة ببيانات العميل تُحل على الخادم وتتجاهل أي قيمة ترسلها لها. المستلم يُحدد بنفس الطرق الثلاث لنقطة الإرسال.

الترويسات

الاسمالنوعإلزاميالوصف
Idempotency-Keystringلاأرسله دائمًا مع رموز التحقق: إعادة المحاولة بالمفتاح نفسه لا ترسل رمزًا ثانيًا.

معطيات الجسم (JSON)

الاسمالنوعإلزاميالوصف
number_iduuidلامعرّف القناة المُرسِلة. مطلوب مع to أو contact_id.
channel_iduuidلااسم آخر لـ number_id، للقنوات التي ليست أرقامًا.
conversation_iduuidلامحادثة موجودة على أي منصة. تخرج الرسالة على قناتها، فلا يلزم number_id.
contact_iduuidلاجهة اتصال لها حساب على منصة القناة المختارة. تُفتح محادثتها إذا لم تكن موجودة.
tostringلارقم المستلم بالصيغة الدولية مع رمز الدولة، مثل 201014642581. يُقبل + والمسافات ويُزالان. الرقم المحلي بلا رمز دولة (01014642581) يُرفض.
custom_message_idstringنعممعرّف الرسالة المخصصة، أو lib:key لرسالة جاهزة.
valuesobjectلاقيم الخانات، ومفتاح كل قيمة هو رقم الخانة (المفتاح "2" وقيمته "482913"). ليست قائمة، لأن الخانات المربوطة ببيانات العميل لا تظهر هنا.
curl -X POST https://derha.net/api/v1/messages/send-custom \
  -H "Authorization: Bearer mh_live_xxxxxxxxxxxxxxxx_your_secret" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: otp-2026-09-03-1" \
  -d '{
  "number_id": "7f2a1c9e-3b4d-5e6f-7a8b-9c0d1e2f3a4b",
  "to": "201014642581",
  "custom_message_id": "lib:otp",
  "values": { "2": "482913", "3": "10 minutes" }
}'

حالة رسالة

GET/v1/messages/show/{id}

تفاصيل رسالة أرسلتها بالـ API: الحالة وكل الأوقات وسبب الفشل والخط الزمني الكامل history. لا تظهر هنا الرسائل التي لم تنشأ من الـ API (الرسائل الواردة لها نقطتها الخاصة).

curl https://derha.net/api/v1/messages/show/3c9d8e7f-1a2b-3c4d-5e6f-7a8b9c0d1e2f \
  -H "Authorization: Bearer mh_live_xxxxxxxxxxxxxxxx_your_secret"

الاستجابة 200 OK

{
  "success": true,
  "message": "...",
  "data": {
    "message": {
      "id": "3c9d8e7f-1a2b-3c4d-5e6f-7a8b9c0d1e2f",
      "send_type": "single",
      "number_id": "7f2a1c9e-3b4d-5e6f-7a8b-9c0d1e2f3a4b",
      "to": "201014642581",
      "type": "text",
      "text": "Hello, your order #4821 has shipped.",
      "status": "delivered",
      "failure_reason": null,
      "failure_category": null,
      "failure_message": null,
      "created_at": "2026-07-11T10:00:00+00:00",
      "sent_at": "2026-07-11T10:00:01+00:00",
      "delivered_at": "2026-07-11T10:00:04+00:00",
      "read_at": null,
      "failed_at": null,
      "history": [
        { "at": "2026-07-11T10:00:00+00:00", "event": "queued" },
        { "at": "2026-07-11T10:00:01+00:00", "event": "attempt", "attempt": 1 },
        { "at": "2026-07-11T10:00:01+00:00", "event": "sent" },
        { "at": "2026-07-11T10:00:04+00:00", "event": "delivered" }
      ]
    }
  }
}

قائمة الرسائل المُرسَلة

GET/v1/messages/list

ملخص مرقّم لعمليات الإرسال بالـ API، الأحدث أولًا. لتفاصيل رسالة استعمل /v1/messages/show/{id}.

معطيات الاستعلام

الاسمالنوعإلزاميالوصف
business_iduuidلارسائل شركة محددة فقط.
number_iduuidلارسائل قناة محددة فقط.
statusstringلاqueued أو sent أو delivered أو read أو failed.
pageintegerلارقم الصفحة، يبدأ من 1.
per_pageintegerلاعدد العناصر في الصفحة. الافتراضي 25 والأقصى 100.
curl "https://derha.net/api/v1/messages/list?number_id=7f2a1c9e-3b4d-5e6f-7a8b-9c0d1e2f3a4b&status=delivered&per_page=25" \
  -H "Authorization: Bearer mh_live_xxxxxxxxxxxxxxxx_your_secret"

الرسائل الواردة

GET/v1/messages/inbound

الرسائل التي أرسلها العملاء إلى قنواتك، الأحدث أولًا، بنفس شكل message في الويب هوك. استعملها لتعويض أحداث فاتتك أثناء توقف خادمك، أو إذا فضّلت الاستعلام الدوري على الويب هوك.

معطيات الاستعلام

الاسمالنوعإلزاميالوصف
business_iduuidلاشركة محددة.
number_iduuidلاقناة محددة (أو channel_id).
channelstringلامنصة محددة: whatsapp أو messenger أو instagram أو telegram أو tiktok.
sinceISO 8601لاالرسائل من هذا الوقت فصاعدًا.
untilISO 8601لاالرسائل حتى هذا الوقت.
pageintegerلارقم الصفحة، يبدأ من 1.
per_pageintegerلاعدد العناصر في الصفحة. الافتراضي 25 والأقصى 100.
curl "https://derha.net/api/v1/messages/inbound?since=2026-10-03T00:00:00Z&per_page=50" \
  -H "Authorization: Bearer mh_live_xxxxxxxxxxxxxxxx_your_secret"

تنزيل ملف

GET/v1/media/{id}

يعيد محتوى ملف مرفق برسالة (صورة أو صوت أو فيديو أو مستند) عند الرابط url الموجود في المرفق. يحتاج مفتاح API كأي طلب آخر.

curl -o photo.jpg "https://derha.net/api/v1/media/01a0ff03-16e0-7350-a174-d09c254ee041" \
  -H "Authorization: Bearer mh_live_xxxxxxxxxxxxxxxx_your_secret"

الرصيد

GET/v1/balance

رصيد الرسائل الذي يُخصم منه كل إرسال، ونقاط الذكاء الاصطناعي. افحصه قبل دفعة كبيرة.

curl https://derha.net/api/v1/balance \
  -H "Authorization: Bearer mh_live_xxxxxxxxxxxxxxxx_your_secret"

الاستجابة 200 OK

{
  "success": true,
  "data": {
    "api_enabled": true,
    "messages": { "granted": 10000, "used": 2310, "remaining": 7690, "unlimited": false },
    "ai_points": { "granted": 500, "used": 120, "remaining": 380, "unlimited": false }
  }
}

أحداث الويب هوك

GET/v1/webhooks/events

قائمة الأحداث المتاحة ومعنى كل واحد.

curl https://derha.net/api/v1/webhooks/events \
  -H "Authorization: Bearer mh_live_xxxxxxxxxxxxxxxx_your_secret"

عرض الويب هوك

GET/v1/webhooks

كل الويب هوك في مساحة العمل: الرابط والأحداث والقنوات وهل هو موقوف. لا تظهر الأسرار. الإنشاء والتعديل من لوحة التحكم فقط.

curl https://derha.net/api/v1/webhooks \
  -H "Authorization: Bearer mh_live_xxxxxxxxxxxxxxxx_your_secret"

إرسال حدث تجريبي

POST/v1/webhooks/{id}/test

يرسل الحدث webhook.test إلى رابط الويب هوك المحدد. يُرسل حتى لو كان الويب هوك موقوفًا، ونجاحه يعيد تشغيله. معرّف غير موجود في مساحة عملك يرد بـ webhook_not_found.

curl -X POST https://derha.net/api/v1/webhooks/WEBHOOK_ID/test \
  -H "Authorization: Bearer mh_live_xxxxxxxxxxxxxxxx_your_secret"

سجل التسليم

GET/v1/webhooks/deliveries

الأحداث التي أرسلتها ويب هوك مساحة العمل، الأحدث أولًا: الحالة، ورد خادمك، وعدد المحاولات، والمحاولة التالية.

معطيات الاستعلام

الاسمالنوعإلزاميالوصف
webhook_iduuidلاويب هوك واحد فقط.
statusstringلاsuccess أو failed أو pending أو queued.
eventstringلانوع حدث، مثل message.received.
curl "https://derha.net/api/v1/webhooks/deliveries?status=failed" \
  -H "Authorization: Bearer mh_live_xxxxxxxxxxxxxxxx_your_secret"

إعادة إرسال حدث

POST/v1/webhooks/deliveries/{id}/redeliver

يرسل الحدث مرة أخرى بالجسم نفسه ومعرّف الحدث نفسه، فيعرف خادمك أنه عالجه من قبل. الحدث يحمل الجسم الذي بُني وقت وقوعه، وليس نسخة أحدث. الحد 10 إعادات في الدقيقة لكل ويب هوك.

curl -X POST https://derha.net/api/v1/webhooks/deliveries/DELIVERY_ID/redeliver \
  -H "Authorization: Bearer mh_live_xxxxxxxxxxxxxxxx_your_secret"

قواعد الإرسال حسب المنصة #

المنصات تختلف في من يحق له بدء الكلام وفي مدة الرد. اقرأ الحقلين can_initiate في القنوات وreply_window في كل رسالة قبل الإرسال.

الكودمتى يحدث
cannot_initiate_on_channelإنستغرام وماسنجر وتيك توك لا تسمح للشركة بالكتابة أولًا. الرد يكون على عميل كتب لك.
reply_window_closedانتهت فترة الرد على هذه المحادثة. انتظر حتى يكتب العميل من جديد.
human_only_windowبعد 24 ساعة على ماسنجر وإنستغرام يرد موظف فقط، وليس الـ API.
message_limit_reachedبلغت المحادثة حد الرسائل (10 رسائل على تيك توك) حتى يكتب العميل.
outside_service_windowواتساب الرسمي خارج 24 ساعة يقبل قالبًا معتمدًا فقط. في send-custom تُستعمل الرسالة المرتبطة بقالب، وفي غيرها انتظر العميل.
text_too_long_for_channelالنص أطول مما تسمح به المنصة.
media_not_supported_on_channelالمنصة لا تقبل هذا النوع من الملفات.
media_too_large_for_channelالملف أكبر مما تسمح به المنصة.
text_with_media_not_supportedالمنصة لا تقبل نصًا مع ملف في رسالة واحدة. أرسلهما رسالتين.

تنسيق النص

اكتب النص بعلامات واتساب: *غامق* و_مائل_ و~مشطوب~. تيليغرام يستقبلها كتنسيق. إنستغرام وماسنجر وتيك توك يستقبلون الكلمات بلا العلامات. النص المحفوظ في الحقل text يبقى كما أرسلته.

العملاء الذين طلبوا التوقف

عندما يطلب عميل إيقاف الرسائل تُعلَّم هويته على تلك المنصة، وأي إرسال له بالـ API يُرفض بـ 409 والكود contact_opted_out. الرفض خاص بالمنصة: عميل أوقف واتساب يبقى الوصول إليه على تيليغرام ممكنًا. إذا كانت الرسالة عن طلبه أو حجزه أو حسابه (إيصال، موعد، رمز) أرسل "transactional": true. هذا إقرار منك ولا يلغي طلب التوقف. قالب واتساب الرسمي من فئة MARKETING يُرفض دائمًا لعميل أوقف الرسائل.

الويب هوك #

الويب هوك يرسل إلى خادمك طلب POST موقّعًا عند كل حدث: رسالة جديدة من عميل، أو تغيّر حالة رسالة أرسلتها. تعرف ما يحدث فورًا دون استعلام دوري. الويب هوك لا يحتاج مفتاح API.

الإعداد

  1. في «واجهة API للمطورين» ثم «الويب هوك» أضف رابطًا. يجب أن يكون HTTPS وعامًا ويقبل POST.
  2. اختر الأحداث ثم القنوات: كلها أو قنوات محددة.
  3. شغّل روابط تنزيل الملفات إذا كان نظامك سينزّل المرفقات.
  4. احفظ مفتاح التوقيع، فهو يظهر مرة واحدة، ثم اضغط «اختبار» للتأكد.

الأحداث

تصلك الأحداث التي اشتركت بها فقط. قد تُضاف أنواع جديدة لاحقًا، فتجاهل النوع الذي لا تعرفه بدل أن ترفضه.

الحدثمتى يُرسل
message.receivedكتب عميل رسالة إلى إحدى قنواتك. يشمل قنوات «API فقط».
message.recoveredرسالة عميل فاتتك أثناء انقطاع القناة وعُثر عليها بعد عودتها. sent_at يحدد وقت إرسالها. لا تصلك إلا إذا اشتركت بها.
message.sentخرجت رسالة صادرة: من المنصة أو من الـ API أو بكتابة صاحب الرقم من هاتفه (source = owner_device).
message.deliveredوصلت الرسالة إلى هاتف المستلم.
message.readقرأ المستلم الرسالة.
message.failedتعذّر إرسال الرسالة. السبب في failure_reason.
webhook.testحدث تجريبي يُرسل عند الطلب فقط: من لوحة التحكم أو POST /v1/webhooks/{id}/test.

شكل الحدث

كل حدث كائن JSON فيه id (معرّف الحدث) وtype وapi_version وcreated_at وdata. أحداث الرسائل تحمل data.message بالشكل نفسه للوارد والصادر.

message.received
{
  "id": "evt_k3j9x2m8q1w7e5r4t6y0u2i8",
  "type": "message.received",
  "api_version": "v1",
  "created_at": "2026-10-03T08:30:00+00:00",
  "data": {
    "message": {
      "id": "01a0ff03-16e0-7350-a174-d09c254ee040",
      "direction": "inbound",
      "business_id": "9b1c2d3e-4f5a-6b7c-8d9e-0f1a2b3c4d5e",
      "number_id": "7f2a1c9e-3b4d-5e6f-7a8b-9c0d1e2f3a4b",
      "channel": "whatsapp",
      "channel_id": "7f2a1c9e-3b4d-5e6f-7a8b-9c0d1e2f3a4b",
      "conversation_id": "9c0d1e2f-3a4b-5c6d-7e8f-9a0b1c2d3e4f",
      "from": "201014642581",
      "to": "201111229562",
      "contact": { "id": "5e6f7a8b-9c0d-1e2f-3a4b-5c6d7e8f9a0b", "name": "Ahmed", "phone": "201014642581", "handle": null },
      "reply_window": { "state": "open", "closes_at": "2026-10-04T08:29:58+00:00", "remaining_messages": null, "reason": null },
      "source": "contact",
      "api_key_id": null,
      "type": "image",
      "text": "Is this one available?",
      "attachments": [
        {
          "id": "01a0ff03-16e0-7350-a174-d09c254ee041",
          "type": "image",
          "mime_type": "image/jpeg",
          "name": null,
          "size": 34191,
          "ready": true,
          "url": "https://derha.net/api/v1/media/01a0ff03-16e0-7350-a174-d09c254ee041"
        }
      ],
      "contacts": [],
      "reply_to": null,
      "status": "received",
      "failure_reason": null,
      "sent_at": "2026-10-03T08:29:58+00:00",
      "created_at": "2026-10-03T08:30:00+00:00"
    }
  }
}

حقول data.message

الحقلالنوعالمعنى
iduuidمعرّف الرسالة.
directionstringinbound واردة، outbound صادرة.
business_iduuidالشركة التي تتبعها القناة.
number_iduuidالقناة التي استقبلتها أو أرسلتها.
channelstringالمنصة: whatsapp أو messenger أو instagram أو telegram أو tiktok.
channel_iduuidالقيمة نفسها كـ number_id.
conversation_iduuidالمحادثة التي تتبعها الرسالة. استعملها للرد.
fromstringالمرسل بأرقام دولية بلا +. إن لم يكن للعميل هاتف فمعرّفه على المنصة.
tostringالمستلم بالصيغة نفسها.
contactobjectجهة الاتصال: id وname وphone (رقم هاتف فقط) وhandle.
reply_windowobjectهل يجوز الرد الآن: state (open أو human_only أو template_only أو closed) وcloses_at وremaining_messages وreason.
sourcestringمن أرسلها: contact للعميل، وapi وagent وcampaign وai_agent وowner_device وغيرها للصادرة.
api_key_iduuid|nullالمفتاح الذي أُرسلت به رسالة API، وإلا null. يميّز تكاملين لكل منهما مفتاح.
typestringtext أو image أو video أو audio أو voice أو document أو sticker أو location أو contact_card أو unsupported.
textstring|nullنص الرسالة أو تعليق الملف.
attachmentsarrayالملفات: id وtype وmime_type وsize وready وurl للتنزيل بالمفتاح. مع download_url وdownload_expires_at إذا فعّلت روابط التنزيل.
contactsarrayبطاقات جهات اتصال شاركها العميل.
reply_touuid|nullمعرّف الرسالة التي يرد عليها العميل.
statusstringreceived للواردة، أو حالة الصادرة.
failure_reasonstring|nullسبب الفشل في message.failed.
sent_atISO 8601وقت إرسال العميل لها. أبكر من created_at للرسالة المسترجعة أو المتأخرة.
created_atISO 8601وقت تسجيلها في المنصة.

التحقق من التوقيع

تحقق من كل طلب قبل الوثوق به. الترويسة X-MessageHub-Signature-256 بالشكل t=<الوقت>,v1=<التوقيع>، والتوقيع هو HMAC-SHA256 للنص "الوقت.الجسم" بمفتاح التوقيع، مكتوبًا بأرقام ست عشرية. ارفض الطلب إذا بعد الوقت عن ساعتك أكثر من 5 دقائق، فهذا يمنع إعادة تشغيل طلب قديم. استعمل الجسم كما وصل بالضبط دون إعادة تسلسله.

الترويسةالقيمة
X-MessageHub-Eventنوع الحدث.
X-MessageHub-Deliveryمعرّف الحدث، وهو id في الجسم. استعمله لتجاهل التكرار.
X-MessageHub-Webhook-Idمعرّف الويب هوك المرسل. مفيد إذا شاركت عدة ويب هوك رابطًا واحدًا.
X-MessageHub-Timestampوقت الإرسال بالثواني (Unix).
X-MessageHub-Signature-256التوقيع الموصى به: t=…,v1=…. قد يحمل عدة قيم v1 أثناء تغيير المفتاح.
X-MessageHub-Signatureالتوقيع الأصلي sha256=… للجسم وحده. للمستقبِلات القديمة، والأفضل ترويسة 256.
User-AgentMessageHub-Webhooks/1.0
Node.js
import crypto from 'node:crypto'

// Read the RAW body: app.post('/webhook', express.raw({ type: 'application/json' }), handler)
export function verifyMessageHub(rawBody, header, secret, toleranceSeconds = 300) {
  const parts = header.split(',').map((p) => p.split('='))
  const t = parts.find(([k]) => k === 't')?.[1]
  const signatures = parts.filter(([k]) => k === 'v1').map(([, v]) => v)
  if (!t || Math.abs(Date.now() / 1000 - Number(t)) > toleranceSeconds) return false

  const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex')
  return signatures.some(
    (sig) => sig.length === expected.length && crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected)),
  )
}

// verifyMessageHub(req.body.toString('utf8'), req.get('X-MessageHub-Signature-256'), process.env.MESSAGEHUB_WEBHOOK_SECRET)
PHP
<?php
function verifyMessageHub(string $rawBody, string $header, string $secret, int $tolerance = 300): bool
{
    $t = null;
    $signatures = [];
    foreach (explode(',', $header) as $part) {
        [$k, $v] = array_pad(explode('=', $part, 2), 2, '');
        if ($k === 't') { $t = $v; } elseif ($k === 'v1') { $signatures[] = $v; }
    }
    if ($t === null || abs(time() - (int) $t) > $tolerance) {
        return false;
    }
    $expected = hash_hmac('sha256', $t.'.'.$rawBody, $secret);
    foreach ($signatures as $signature) {
        if (hash_equals($expected, $signature)) {
            return true;
        }
    }
    return false;
}

$raw = file_get_contents('php://input');
if (! verifyMessageHub($raw, $_SERVER['HTTP_X_MESSAGEHUB_SIGNATURE_256'] ?? '', getenv('MESSAGEHUB_WEBHOOK_SECRET'))) {
    http_response_code(401);
    exit;
}
http_response_code(200); // answer first, then process the event in a queue
Python
import hashlib, hmac, time

def verify_messagehub(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
    t, signatures = None, []
    for part in header.split(','):
        key, _, value = part.partition('=')
        if key == 't':
            t = value
        elif key == 'v1':
            signatures.append(value)
    if t is None or abs(time.time() - int(t)) > tolerance:
        return False
    expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
    return any(hmac.compare_digest(expected, s) for s in signatures)

# Flask: verify_messagehub(request.get_data(), request.headers.get('X-MessageHub-Signature-256', ''), SECRET)

تغيير مفتاح التوقيع

عند إنشاء مفتاح توقيع جديد يبقى القديم صالحًا 24 ساعة، ويحمل كل طلب توقيعين v1، واحدًا لكل مفتاح. حدّث خادمك خلال هذه المدة فلا يضيع أي حدث.

التسليم وإعادة المحاولة

  • يُعد الحدث مسلَّمًا عندما يرد خادمك بأي 2xx خلال 5 ثوانٍ.
  • الرد 5xx أو 429 أو خطأ شبكة: تُعاد المحاولة، مع احترام Retry-After إن أرسلته.
  • أي 4xx آخر أو تحويل (redirect): فشل نهائي دون إعادة. لا تُتبع التحويلات.
  • المحاولات بعد 10 ثوانٍ ثم دقيقة ثم 5 دقائق ثم 30 دقيقة ثم ساعة ثم 3 ساعات ثم 6 ساعات.
  • بعد 20 فشلًا نهائيًا متتاليًا يُوقف الويب هوك ويُنبَّه مالك مساحة العمل.
  • حدث تجريبي ناجح يعيد تشغيل الويب هوك الموقوف. ويمكن إعادة إرسال أي حدث من السجل.

الاستقبال الجيد

  • رد بـ 200 بسرعة، ثم عالج الحدث في الخلفية.
  • قد يصل الحدث نفسه أكثر من مرة. احفظ معرّفه وتجاهل التكرار.
  • ترتيب الوصول غير مضمون. اعتمد على created_at وعلى ترتيب الحالات: sent ثم delivered ثم read.
  • تحقق من التوقيع والوقت قبل أي شيء آخر.
  • نزّل الملفات من url بمفتاحك، وأعد المحاولة ما دام ready=false.
  • إذا توقف خادمك فعوّض ما فاتك بـ GET /v1/messages/inbound، أو أعد إرسال الأحداث الفاشلة من السجل.

مثال كامل: مستقبِل بـ Node.js

Node.js (Express)
import express from 'express'
import { verifyMessageHub } from './verify.js'

const app = express()
const seen = new Set() // use Redis or a database table in production

app.post('/messagehub', express.raw({ type: 'application/json' }), (req, res) => {
  const raw = req.body.toString('utf8')
  if (!verifyMessageHub(raw, req.get('X-MessageHub-Signature-256') ?? '', process.env.MESSAGEHUB_WEBHOOK_SECRET)) {
    return res.sendStatus(401)
  }
  res.sendStatus(200) // answer first, within 5 seconds

  const event = JSON.parse(raw)
  if (seen.has(event.id)) return // the same event can arrive twice
  seen.add(event.id)

  if (event.type === 'message.received') {
    const m = event.data.message
    console.log('New message from', m.from, ':', m.text)
  }
})

app.listen(3000)

روابط تنزيل الملفات

الرابط url في المرفق يحتاج مفتاح API. إذا كان نظامك يستقبل الويب هوك دون مفتاح فشغّل «روابط تنزيل الملفات» للويب هوك، فيحمل كل مرفق download_url يُفتح دون مفتاح حتى download_expires_at (24 ساعة افتراضيًا). الرابط الموقّع لا يمكن إلغاؤه، فمن يحصل عليه يفتح الملف حتى ينتهي. لذلك هو مغلق افتراضيًا.

الأخطاء #

كل خطأ يعيد الغلاف نفسه مع code ثابت يمكنك البناء عليه. الرمز HTTP وحده لا يكفي، فعدة أكواد تشترك في الرمز نفسه (مثل 404 و422). ابنِ منطقك على code. أخطاء التحقق تضيف كائن errors بتفاصيل كل حقل.

الكودHTTPالمعنى
unauthenticated401مفتاح API غير صالح أو غير موجود.
origin_not_allowed401الطلب من أصل غير مسموح لهذا المفتاح.
insecure_transport403يجب استدعاء الـ API عبر HTTPS.
ip_not_allowed403الطلب من عنوان IP غير مسموح لهذا المفتاح.
business_not_found404لا توجد شركة بهذا المعرّف في مساحة عملك.
number_not_found404لا توجد قناة بهذا number_id في مساحة عملك.
conversation_not_found404لا توجد محادثة بهذا المعرّف في مساحة عملك.
contact_not_found404لا توجد جهة اتصال بهذا المعرّف.
media_not_found404لا يوجد ملف بهذا المعرّف يحق لمفتاحك فتحه.
webhook_not_found404لا يوجد ويب هوك بهذا المعرّف في مساحة عملك.
message_not_found404لا توجد رسالة بهذا المعرّف.
custom_message_not_found404الرسالة المخصصة المطلوبة غير موجودة في مساحة عملك.
number_not_connected409القناة المختارة غير متصلة الآن.
media_not_ready409لم يُجلب الملف من المنصة بعد. أعد المحاولة بعد 30 ثانية.
request_in_progress409طلب بالـ Idempotency-Key نفسه ما زال قيد المعالجة.
contact_opted_out409طلب العميل التوقف على هذه المنصة. أرسل transactional=true فقط إذا كانت الرسالة عن طلبه.
validation_failed422جسم الطلب غير صالح.
slot_value_missing422قيمة ناقصة لخانة إلزامية في الرسالة المخصصة.
contact_not_on_channel422جهة الاتصال ليس لها حساب على منصة هذه القناة.
cannot_initiate_on_channel422هذه المنصة لا تسمح للشركة بالكتابة أولًا.
reply_window_closed422انتهت فترة الرد على هذه المحادثة.
human_only_window422الرد الآن للموظفين فقط وليس للـ API.
message_limit_reached422بلغت المحادثة حد الرسائل حتى يكتب العميل.
outside_service_window422واتساب الرسمي خارج 24 ساعة يقبل قالبًا معتمدًا فقط.
text_too_long_for_channel422النص أطول مما تسمح به المنصة.
media_not_supported_on_channel422المنصة لا تقبل هذا النوع من الملفات.
media_too_large_for_channel422الملف أكبر مما تسمح به المنصة.
text_with_media_not_supported422المنصة لا تقبل نصًا مع ملف. أرسلهما رسالتين.
idempotency_key_reused422استُعمل هذا الـ Idempotency-Key لطلب بجسم مختلف.
quota_exhausted422رصيد رسائل API في مساحة العمل انتهى. اشحنه لتتابع.
api_not_enabled422الـ API غير مفعّل لمساحة العمل. اشترِ رصيد رسائل API لتفعيله.
rate_limited429طلبات كثيرة. انتظر Retry-After ثم أعد المحاولة.
server_error500حدث خطأ غير متوقع. أعد المحاولة.

تُعاد المحاولة بأمان مع 429 و5xx (ومع Idempotency-Key لا يتكرر الإرسال). أخطاء 4xx الأخرى سببها الطلب نفسه، فصحّحه ولا تكرره كما هو.

أسئلة شائعة #

كيف أحصل على مفتاح API؟

من لوحة التحكم افتح «واجهة API للمطورين» ثم «المفاتيح» وأنشئ مفتاحًا. يظهر الرمز مرة واحدة فقط، فانسخه فورًا. ويلزم رصيد رسائل API في مساحة العمل.

هل الإرسال فوري؟

لا. نقطة الإرسال ترد 202 فورًا بعد وضع الرسالة في الطابور، ويتم الإرسال الفعلي بعدها. للنتيجة النهائية استعمل «حالة رسالة» أو الويب هوك.

لماذا لا تظهر رسائل الـ API في صندوق الوارد؟

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

كيف أمنع إرسال الرسالة مرتين عند إعادة المحاولة؟

أرسل ترويسة Idempotency-Key بقيمة فريدة لكل عملية، مثل رقم الطلب. الطلب المعاد بالقيمة نفسها يعيد النتيجة الأولى.

هل أستطيع الكتابة لأي عميل أولًا؟

يعتمد على المنصة. واتساب يسمح حسب نوع القناة، أما إنستغرام وماسنجر وتيك توك فلا تسمح للشركة بالكتابة أولًا. اقرأ can_initiate في GET /v1/numbers.

كيف أعرف أن رسالة وصلت؟

اشترك بالأحداث message.delivered وmessage.read في الويب هوك، أو اقرأ status في نقطة «حالة رسالة».

هل يمكنني إنشاء الويب هوك من الكود؟

لا. الإنشاء والتعديل من لوحة التحكم فقط. من الكود تستطيع عرضها واختبارها وقراءة سجلها وإعادة إرسال أحداثها.

هل يمكن إرسال صور أو ملفات بالـ API؟

ليس الآن. نقطة الإرسال تدعم النص (type=text)، والرسائل المخصصة تغطي الرسائل الجاهزة. استقبال الملفات من العملاء وتنزيلها مدعوم.

جاهز للربط؟

أنشئ مفتاحك من لوحة التحكم وأرسل أول رسالة اليوم.

افتح لوحة التحكم اقرأ دليل البدء