البداية السريعة
مرسلات API بتخلّي نظامك يبعت رسائل من رقمك أنت المربوط — كل الأنواع، من النص والميديا لحد الاستطلاعات والأزرار والقوائم، من endpoint واحد — ويستقبل ردود العملاء وتحديثات التسليم على الويب هوك بتاعك.
- اربط رقمك من لوحة التحكم: الأرقام ← إضافة رقم، وامسح كود الـ QR.
- افتح الأرقام ← رقمك ← تبويب المطوّرين واعمل مفتاح API (بيظهر مرة واحدة) واختار صلاحياته.
- في نفس التبويب احفظ رابط الويب هوك، اختار الإصدار 2، وانسخ سرّ التوقيع (بيظهر مرة واحدة).
- ابعت أول رسالة أزرار (تحت). كل إرسال محتاج هيدر
Idempotency-Key. - العميل يضغط زرار — أو يكتب رقمه — ويوصلك حدث
interactive.replyعلى الويب هوك.
curl -X POST "https://mursilat.com/api/v1/whatsapp/public/messages" \
-H "Content-Type: application/json" \
-H "x-api-key: $WASLA_API_KEY" \
-H "Idempotency-Key: order-1042-confirm" \
-d '{
"to": "201001234567",
"type": "buttons",
"body": {
"text": "هل تؤكد طلبك؟"
},
"footer": {
"text": "متجر النور"
},
"buttons": [
{
"id": "confirm",
"title": "تأكيد"
},
{
"id": "cancel_order",
"title": "إلغاء الطلب"
}
],
"reference": "order-1042"
}'// Node.js 18+ (built-in fetch)
const res = await fetch('https://mursilat.com/api/v1/whatsapp/public/messages', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': process.env.WASLA_API_KEY,
'Idempotency-Key': 'order-1042-confirm',
},
body: JSON.stringify({
"to": "201001234567",
"type": "buttons",
"body": {
"text": "هل تؤكد طلبك؟"
},
"footer": {
"text": "متجر النور"
},
"buttons": [
{
"id": "confirm",
"title": "تأكيد"
},
{
"id": "cancel_order",
"title": "إلغاء الطلب"
}
],
"reference": "order-1042"
}),
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.message}`);
console.log(data);<?php
$payload = [
'to' => '201001234567',
'type' => 'buttons',
'body' => [
'text' => 'هل تؤكد طلبك؟',
],
'footer' => [
'text' => 'متجر النور',
],
'buttons' => [
[
'id' => 'confirm',
'title' => 'تأكيد',
],
[
'id' => 'cancel_order',
'title' => 'إلغاء الطلب',
],
],
'reference' => 'order-1042',
];
$ch = curl_init('https://mursilat.com/api/v1/whatsapp/public/messages');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'x-api-key: ' . getenv('WASLA_API_KEY'),
'Idempotency-Key: order-1042-confirm',
],
CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_UNICODE),
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$data = json_decode($response, true);
if ($status >= 400) {
throw new RuntimeException($data['code'] . ': ' . json_encode($data['message']));
}
print_r($data);import os
import requests
payload = {
"to": "201001234567",
"type": "buttons",
"body": {
"text": "هل تؤكد طلبك؟",
},
"footer": {
"text": "متجر النور",
},
"buttons": [
{
"id": "confirm",
"title": "تأكيد",
},
{
"id": "cancel_order",
"title": "إلغاء الطلب",
},
],
"reference": "order-1042",
}
res = requests.post(
"https://mursilat.com/api/v1/whatsapp/public/messages",
headers={
"x-api-key": os.environ["WASLA_API_KEY"],
"Idempotency-Key": "order-1042-confirm",
},
json=payload,
timeout=30,
)
data = res.json()
if not res.ok:
raise RuntimeError(f"{data.get('code')}: {data.get('message')}")
print(data)العنوان الأساسي: https://mursilat.com/api/v1. الطلبات والردود JSON بترميز UTF-8، والأوقات ISO-8601 بتوقيت UTC. أرقام التليفون بالصيغة الدولية من غير + (مثلاً 201001234567).
المصادقة ومفاتيح API
كل رقم ليه مفتاح API خاص بيه. ابعته في هيدر x-api-key من السيرفر بتاعك (أبداً مش من المتصفح أو تطبيق الموبايل). إحنا بنحتفظ ببصمة المفتاح بس، فهو بيظهر مرة واحدة وقت ما تعمله؛ ولو عملت مفتاح جديد القديم بيبطل فوراً.
| الصلاحية (scope) | بتسمح بـ |
|---|---|
send | بتسمح بـكل الإرسال على POST /messages، والتفاعل/التعديل/المسح، وGET /interactions/:id |
media | بتسمح بـأنواع الميديا (مع send)، ورفع الميديا وتحميلها |
webhook | بتسمح بـاستقبال أحداث الويب هوك |
الوصول للـ API جزء من باقتك. من غيره أي طلب بيرجع 403 API_ACCESS_NOT_IN_PLAN.
منع التكرار (Idempotency)
كل إرسال لازم يكون معاه هيدر Idempotency-Key (لحد 191 حرف، مختلف لكل رسالة — مثلاً order-1042-confirm). لو الطلب عمل timeout، أعد المحاولة بنفس المفتاح: هيرجعلك نفس النتيجة الأولى والرسالة عمرها ما هتتبعت مرتين.
IDEMPOTENCY_KEY_REQUIRED— الهيدر مش موجود.IDEMPOTENCY_KEY_REUSED— المفتاح اتستخدم قبل كده مع محتوى مختلف.IDEMPOTENCY_IN_PROGRESS— أول طلب بالمفتاح ده لسه شغال؛ جرّب تاني بعد ثواني.
ابعت أي رسالة (endpoint موحّد)
POST /whatsapp/public/messages هو endpoint واحد لـ كل أنواع الرسايل: ابعت { "to": "…", "type": "…", …الخانات }. صلاحية send (وأنواع الميديا محتاجة كمان media)، وباقة فيها الـ API، وهيدر Idempotency-Key.
| `type` | هو إيه | التفاصيل |
|---|---|---|
text | هو إيهنص بتنسيق واتساب ومعاينة للرابط (اختياري) | التفاصيلالنص |
image · video · audio · voice · document | هو إيهصور، فيديو (GIF أو فيديو دائري)، ملفات صوت، رسايل صوتية، ملفات | التفاصيلالميديا |
sticker | هو إيهملصق WebP — وPNG/JPG/GIF بنحولهم | التفاصيلالملصقات |
location | هو إيهدبوس على الخريطة (مش موقع مباشر) | التفاصيلالموقع |
contact | هو إيهمن 1 لـ 5 جهات اتصال | التفاصيلجهات الاتصال |
poll | هو إيهسؤال بـ 2 لـ 12 اختيار، والأصوات بترجعلك في poll.vote | التفاصيلالاستطلاعات |
event | هو إيهدعوة لمناسبة — تجريبي | التفاصيلالمناسبات |
buttons · list · cta_url | هو إيهأزرار رد، قائمة اختيارات، زرار رابط (مع بديل مرقّم) | التفاصيلالأزرار |
الخانات المشتركة
| الخانة | ملاحظات |
|---|---|
to | ملاحظاتمطلوب. أرقام دولية من غير + (مثال: 201001234567). |
type | ملاحظاتمطلوب. نوع من الأنواع اللي فوق. غيرها بيرجع 400 MESSAGE_TYPE_NOT_SUPPORTED ومعاه reason (شوف مش ممكن). |
reference | ملاحظاتاختياري، لحد 128. معرّف من عندك بيرجع في message.status وinteractive.reply وpoll.vote. |
replyTo | ملاحظاتاختياري. رد على رسالة: الـ messageId اللي رجع من الإرسال، أو رقم رسالة في صندوق مرسلات، في نفس المحادثة. |
curl -X POST "https://mursilat.com/api/v1/whatsapp/public/messages" \
-H "Content-Type: application/json" \
-H "x-api-key: $WASLA_API_KEY" \
-H "Idempotency-Key: order-1042-shipped" \
-d '{
"to": "201001234567",
"type": "text",
"text": "أهلاً أ. منى، طلبك رقم 1042 اتشحن وهيوصلك بكرة.",
"reference": "order-1042"
}'// Node.js 18+ (built-in fetch)
const res = await fetch('https://mursilat.com/api/v1/whatsapp/public/messages', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': process.env.WASLA_API_KEY,
'Idempotency-Key': 'order-1042-shipped',
},
body: JSON.stringify({
"to": "201001234567",
"type": "text",
"text": "أهلاً أ. منى، طلبك رقم 1042 اتشحن وهيوصلك بكرة.",
"reference": "order-1042"
}),
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.message}`);
console.log(data);<?php
$payload = [
'to' => '201001234567',
'type' => 'text',
'text' => 'أهلاً أ. منى، طلبك رقم 1042 اتشحن وهيوصلك بكرة.',
'reference' => 'order-1042',
];
$ch = curl_init('https://mursilat.com/api/v1/whatsapp/public/messages');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'x-api-key: ' . getenv('WASLA_API_KEY'),
'Idempotency-Key: order-1042-shipped',
],
CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_UNICODE),
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$data = json_decode($response, true);
if ($status >= 400) {
throw new RuntimeException($data['code'] . ': ' . json_encode($data['message']));
}
print_r($data);import os
import requests
payload = {
"to": "201001234567",
"type": "text",
"text": "أهلاً أ. منى، طلبك رقم 1042 اتشحن وهيوصلك بكرة.",
"reference": "order-1042",
}
res = requests.post(
"https://mursilat.com/api/v1/whatsapp/public/messages",
headers={
"x-api-key": os.environ["WASLA_API_KEY"],
"Idempotency-Key": "order-1042-shipped",
},
json=payload,
timeout=30,
)
data = res.json()
if not res.ok:
raise RuntimeError(f"{data.get('code')}: {data.get('message')}")
print(data){
"messageId": "3EB0A1B2C3D4E5F60718",
"status": "sent",
"type": "text",
"reference": "order-1042",
"interactionId": null,
"format": null,
"fallbackIncluded": null,
"estimatedDelayMs": null
}وقت الضغط الإرسال ممكن يرجع "status": "queued" ومعاه estimatedDelayMs: رقم الرسالة محجوز والنتيجة النهائية بتوصلك في ويب هوك message.status. الأنواع التفاعلية بترجع كمان interactionId وformat وfallbackIncluded.
الميديا: رابط أو ملف مرفوع
"media": { "url": "https://…" }— رابط عام بننزّله مرة واحدة وبأمان (من غير عناوين داخلية ولا تحويلات، 20 ثانية، وفي حدود حجم النوع)."media": { "id": "med_…" }— ملف رفعته الأول من endpoint الرفع (بيتخزن بشكل خاص 24 ساعة، لنفس الرقم).- أو ابعت
multipart/form-dataبنفس الخانات ومعاهمfile(والكائنات المتداخلة كنص JSON). - بنعرف نوع الملف الحقيقي من محتواه، مش من اسمه: لو مش مناسب للنوع بيرجع
415 MESSAGE_MEDIA_TYPE_NOT_ALLOWED.
curl -X POST "https://mursilat.com/api/v1/whatsapp/public/messages" \
-H "x-api-key: $WASLA_API_KEY" \
-H "Idempotency-Key: invoice-1042-doc" \
-F "to=201001234567" \
-F "type=document" \
-F "fileName=invoice-1042.pdf" \
-F "file=@./invoice-1042.pdf"الـ endpoints القديمة لسه شغالة
message/send وmedia/send وinteractive/send بقوا قديمة (legacy): لسه مدعومة بنفس الطلب والرد بالظبط، وبتعدي على نفس طريق الإرسال. أي ربط جديد يستخدم POST /whatsapp/public/messages.
النص
"type": "text" — لحد 4096 حرف. تنسيق واتساب بيوصل زي ما هو: *عريض* _مائل_ ~مشطوب~ والخط الثابت بين ثلاث علامات backtick.
| الخانة | الحدود | ملاحظات |
|---|---|---|
text | الحدود1–4096 | ملاحظاتمطلوب. |
linkPreview | الحدودboolean | ملاحظاتالافتراضي true: بنعمل معاينة (عنوان ووصف وصورة) لأول رابط https بأمان وخلال 3 ثواني. لو الصفحة مقريتش، النص بيتبعت من غير معاينة. |
mentions | الحدود— | ملاحظاتفي الجروبات بس (400 MESSAGE_MENTIONS_GROUP_ONLY)، والإرسال للجروبات مش متاح لسه. |
curl -X POST "https://mursilat.com/api/v1/whatsapp/public/messages" \
-H "Content-Type: application/json" \
-H "x-api-key: $WASLA_API_KEY" \
-H "Idempotency-Key: order-1042-shipped" \
-d '{
"to": "201001234567",
"type": "text",
"text": "أهلاً أ. منى، طلبك رقم 1042 اتشحن وهيوصلك بكرة.",
"reference": "order-1042"
}'// Node.js 18+ (built-in fetch)
const res = await fetch('https://mursilat.com/api/v1/whatsapp/public/messages', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': process.env.WASLA_API_KEY,
'Idempotency-Key': 'order-1042-shipped',
},
body: JSON.stringify({
"to": "201001234567",
"type": "text",
"text": "أهلاً أ. منى، طلبك رقم 1042 اتشحن وهيوصلك بكرة.",
"reference": "order-1042"
}),
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.message}`);
console.log(data);<?php
$payload = [
'to' => '201001234567',
'type' => 'text',
'text' => 'أهلاً أ. منى، طلبك رقم 1042 اتشحن وهيوصلك بكرة.',
'reference' => 'order-1042',
];
$ch = curl_init('https://mursilat.com/api/v1/whatsapp/public/messages');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'x-api-key: ' . getenv('WASLA_API_KEY'),
'Idempotency-Key: order-1042-shipped',
],
CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_UNICODE),
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$data = json_decode($response, true);
if ($status >= 400) {
throw new RuntimeException($data['code'] . ': ' . json_encode($data['message']));
}
print_r($data);import os
import requests
payload = {
"to": "201001234567",
"type": "text",
"text": "أهلاً أ. منى، طلبك رقم 1042 اتشحن وهيوصلك بكرة.",
"reference": "order-1042",
}
res = requests.post(
"https://mursilat.com/api/v1/whatsapp/public/messages",
headers={
"x-api-key": os.environ["WASLA_API_KEY"],
"Idempotency-Key": "order-1042-shipped",
},
json=payload,
timeout=30,
)
data = res.json()
if not res.ok:
raise RuntimeError(f"{data.get('code')}: {data.get('message')}")
print(data)على الموبايل: فقاعة رسالة عادية على كل إصدارات واتساب. معاينة الروابط بتظهر على كل التطبيقات الحديثة.
الصور والفيديو والصوت والرسايل الصوتية والمستندات
كل أنواع الميديا بتاخد media (رابط أو رقم ملف مرفوع — شوف الميديا: رابط أو ملف مرفوع).
| `type` | الصيغ والحجم | خانات إضافية |
|---|---|---|
image | الصيغ والحجمJPEG · PNG · WebP، لحد 5 ميجا | خانات إضافيةcaption لحد 1024 · viewOnce |
video | الصيغ والحجمMP4 · 3GPP، لحد 16 ميجا | خانات إضافيةcaption لحد 1024 · viewOnce · gif · videoNote |
audio | الصيغ والحجمOGG · MP3 · M4A · AAC · AMR، لحد 16 ميجا | خانات إضافية— |
voice | الصيغ والحجمأي صيغة صوت من دول، لحد 16 ميجا | خانات إضافيةviewOnce |
document | الصيغ والحجمأي ملف ماعدا البرامج، لحد 100 ميجا | خانات إضافيةfileName لحد 120 · caption لحد 1024 |
صورة
curl -X POST "https://mursilat.com/api/v1/whatsapp/public/messages" \
-H "Content-Type: application/json" \
-H "x-api-key: $WASLA_API_KEY" \
-H "Idempotency-Key: launch-photo-201001234567" \
-d '{
"to": "201001234567",
"type": "image",
"media": {
"url": "https://example.com/products/new.jpg"
},
"caption": "المنتج الجديد وصل 🎉"
}'// Node.js 18+ (built-in fetch)
const res = await fetch('https://mursilat.com/api/v1/whatsapp/public/messages', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': process.env.WASLA_API_KEY,
'Idempotency-Key': 'launch-photo-201001234567',
},
body: JSON.stringify({
"to": "201001234567",
"type": "image",
"media": {
"url": "https://example.com/products/new.jpg"
},
"caption": "المنتج الجديد وصل 🎉"
}),
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.message}`);
console.log(data);<?php
$payload = [
'to' => '201001234567',
'type' => 'image',
'media' => [
'url' => 'https://example.com/products/new.jpg',
],
'caption' => 'المنتج الجديد وصل 🎉',
];
$ch = curl_init('https://mursilat.com/api/v1/whatsapp/public/messages');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'x-api-key: ' . getenv('WASLA_API_KEY'),
'Idempotency-Key: launch-photo-201001234567',
],
CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_UNICODE),
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$data = json_decode($response, true);
if ($status >= 400) {
throw new RuntimeException($data['code'] . ': ' . json_encode($data['message']));
}
print_r($data);import os
import requests
payload = {
"to": "201001234567",
"type": "image",
"media": {
"url": "https://example.com/products/new.jpg",
},
"caption": "المنتج الجديد وصل 🎉",
}
res = requests.post(
"https://mursilat.com/api/v1/whatsapp/public/messages",
headers={
"x-api-key": os.environ["WASLA_API_KEY"],
"Idempotency-Key": "launch-photo-201001234567",
},
json=payload,
timeout=30,
)
data = res.json()
if not res.ok:
raise RuntimeError(f"{data.get('code')}: {data.get('message')}")
print(data)على الموبايل: صورة بالتعليق بتاعها. مع viewOnce: true بتتفتح مرة واحدة وبعدها تختفي — العرض مرة واحدة محتاج نسخة واتساب حديثة، والتطبيقات القديمة ممكن متفتحهاش.
فيديو وGIF وفيديو دائري
curl -X POST "https://mursilat.com/api/v1/whatsapp/public/messages" \
-H "Content-Type: application/json" \
-H "x-api-key: $WASLA_API_KEY" \
-H "Idempotency-Key: howto-video-201001234567" \
-d '{
"to": "201001234567",
"type": "video",
"media": {
"id": "med_01J9ZK4A2B3C4D5E6F7G8H9J0K"
},
"caption": "إزاي تستخدم المنتج في دقيقة"
}'// Node.js 18+ (built-in fetch)
const res = await fetch('https://mursilat.com/api/v1/whatsapp/public/messages', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': process.env.WASLA_API_KEY,
'Idempotency-Key': 'howto-video-201001234567',
},
body: JSON.stringify({
"to": "201001234567",
"type": "video",
"media": {
"id": "med_01J9ZK4A2B3C4D5E6F7G8H9J0K"
},
"caption": "إزاي تستخدم المنتج في دقيقة"
}),
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.message}`);
console.log(data);<?php
$payload = [
'to' => '201001234567',
'type' => 'video',
'media' => [
'id' => 'med_01J9ZK4A2B3C4D5E6F7G8H9J0K',
],
'caption' => 'إزاي تستخدم المنتج في دقيقة',
];
$ch = curl_init('https://mursilat.com/api/v1/whatsapp/public/messages');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'x-api-key: ' . getenv('WASLA_API_KEY'),
'Idempotency-Key: howto-video-201001234567',
],
CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_UNICODE),
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$data = json_decode($response, true);
if ($status >= 400) {
throw new RuntimeException($data['code'] . ': ' . json_encode($data['message']));
}
print_r($data);import os
import requests
payload = {
"to": "201001234567",
"type": "video",
"media": {
"id": "med_01J9ZK4A2B3C4D5E6F7G8H9J0K",
},
"caption": "إزاي تستخدم المنتج في دقيقة",
}
res = requests.post(
"https://mursilat.com/api/v1/whatsapp/public/messages",
headers={
"x-api-key": os.environ["WASLA_API_KEY"],
"Idempotency-Key": "howto-video-201001234567",
},
json=payload,
timeout=30,
)
data = res.json()
if not res.ok:
raise RuntimeError(f"{data.get('code')}: {data.get('message')}")
print(data)gif: true— بيتكرر من غير صوت، زي الـ GIF.videoNote: true— فيديو دائري: أقصاه 60 ثانية (MESSAGE_VIDEO_NOTE_TOO_LONG)، من غير تعليق، ومينفعش يبقى عرض مرة واحدة ولا GIF. الفيديو المربع بيبان أحسن (إحنا مش بنعيد ترميزه). التطبيقات القديمة ممكن تعرضه كفيديو عادي.
curl -X POST "https://mursilat.com/api/v1/whatsapp/public/messages" \
-H "Content-Type: application/json" \
-H "x-api-key: $WASLA_API_KEY" \
-H "Idempotency-Key: thanks-note-201001234567" \
-d '{
"to": "201001234567",
"type": "video",
"media": {
"id": "med_01J9ZK4A2B3C4D5E6F7G8H9J0L"
},
"videoNote": true
}'// Node.js 18+ (built-in fetch)
const res = await fetch('https://mursilat.com/api/v1/whatsapp/public/messages', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': process.env.WASLA_API_KEY,
'Idempotency-Key': 'thanks-note-201001234567',
},
body: JSON.stringify({
"to": "201001234567",
"type": "video",
"media": {
"id": "med_01J9ZK4A2B3C4D5E6F7G8H9J0L"
},
"videoNote": true
}),
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.message}`);
console.log(data);<?php
$payload = [
'to' => '201001234567',
'type' => 'video',
'media' => [
'id' => 'med_01J9ZK4A2B3C4D5E6F7G8H9J0L',
],
'videoNote' => true,
];
$ch = curl_init('https://mursilat.com/api/v1/whatsapp/public/messages');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'x-api-key: ' . getenv('WASLA_API_KEY'),
'Idempotency-Key: thanks-note-201001234567',
],
CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_UNICODE),
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$data = json_decode($response, true);
if ($status >= 400) {
throw new RuntimeException($data['code'] . ': ' . json_encode($data['message']));
}
print_r($data);import os
import requests
payload = {
"to": "201001234567",
"type": "video",
"media": {
"id": "med_01J9ZK4A2B3C4D5E6F7G8H9J0L",
},
"videoNote": True,
}
res = requests.post(
"https://mursilat.com/api/v1/whatsapp/public/messages",
headers={
"x-api-key": os.environ["WASLA_API_KEY"],
"Idempotency-Key": "thanks-note-201001234567",
},
json=payload,
timeout=30,
)
data = res.json()
if not res.ok:
raise RuntimeError(f"{data.get('code')}: {data.get('message')}")
print(data)ملف صوتي
curl -X POST "https://mursilat.com/api/v1/whatsapp/public/messages" \
-H "Content-Type: application/json" \
-H "x-api-key: $WASLA_API_KEY" \
-H "Idempotency-Key: jingle-201001234567" \
-d '{
"to": "201001234567",
"type": "audio",
"media": {
"url": "https://example.com/audio/jingle.mp3"
}
}'// Node.js 18+ (built-in fetch)
const res = await fetch('https://mursilat.com/api/v1/whatsapp/public/messages', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': process.env.WASLA_API_KEY,
'Idempotency-Key': 'jingle-201001234567',
},
body: JSON.stringify({
"to": "201001234567",
"type": "audio",
"media": {
"url": "https://example.com/audio/jingle.mp3"
}
}),
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.message}`);
console.log(data);<?php
$payload = [
'to' => '201001234567',
'type' => 'audio',
'media' => [
'url' => 'https://example.com/audio/jingle.mp3',
],
];
$ch = curl_init('https://mursilat.com/api/v1/whatsapp/public/messages');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'x-api-key: ' . getenv('WASLA_API_KEY'),
'Idempotency-Key: jingle-201001234567',
],
CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_UNICODE),
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$data = json_decode($response, true);
if ($status >= 400) {
throw new RuntimeException($data['code'] . ': ' . json_encode($data['message']));
}
print_r($data);import os
import requests
payload = {
"to": "201001234567",
"type": "audio",
"media": {
"url": "https://example.com/audio/jingle.mp3",
},
}
res = requests.post(
"https://mursilat.com/api/v1/whatsapp/public/messages",
headers={
"x-api-key": os.environ["WASLA_API_KEY"],
"Idempotency-Key": "jingle-201001234567",
},
json=payload,
timeout=30,
)
data = res.json()
if not res.ok:
raise RuntimeError(f"{data.get('code')}: {data.get('message')}")
print(data)على الموبايل: ملف صوتي بزرار تشغيل واسمه — مش رسالة صوتية.
رسالة صوتية
curl -X POST "https://mursilat.com/api/v1/whatsapp/public/messages" \
-H "Content-Type: application/json" \
-H "x-api-key: $WASLA_API_KEY" \
-H "Idempotency-Key: voice-reply-1042" \
-d '{
"to": "201001234567",
"type": "voice",
"media": {
"id": "med_01J9ZK4A2B3C4D5E6F7G8H9J0M"
},
"reference": "order-1042"
}'// Node.js 18+ (built-in fetch)
const res = await fetch('https://mursilat.com/api/v1/whatsapp/public/messages', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': process.env.WASLA_API_KEY,
'Idempotency-Key': 'voice-reply-1042',
},
body: JSON.stringify({
"to": "201001234567",
"type": "voice",
"media": {
"id": "med_01J9ZK4A2B3C4D5E6F7G8H9J0M"
},
"reference": "order-1042"
}),
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.message}`);
console.log(data);<?php
$payload = [
'to' => '201001234567',
'type' => 'voice',
'media' => [
'id' => 'med_01J9ZK4A2B3C4D5E6F7G8H9J0M',
],
'reference' => 'order-1042',
];
$ch = curl_init('https://mursilat.com/api/v1/whatsapp/public/messages');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'x-api-key: ' . getenv('WASLA_API_KEY'),
'Idempotency-Key: voice-reply-1042',
],
CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_UNICODE),
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$data = json_decode($response, true);
if ($status >= 400) {
throw new RuntimeException($data['code'] . ': ' . json_encode($data['message']));
}
print_r($data);import os
import requests
payload = {
"to": "201001234567",
"type": "voice",
"media": {
"id": "med_01J9ZK4A2B3C4D5E6F7G8H9J0M",
},
"reference": "order-1042",
}
res = requests.post(
"https://mursilat.com/api/v1/whatsapp/public/messages",
headers={
"x-api-key": os.environ["WASLA_API_KEY"],
"Idempotency-Key": "voice-reply-1042",
},
json=payload,
timeout=30,
)
data = res.json()
if not res.ok:
raise RuntimeError(f"{data.get('code')}: {data.get('message')}")
print(data)على الموبايل: رسالة صوتية زي اللي بتتسجل من التطبيق. OGG/Opus وWebM بيحافظوا على شكل الموجة؛ والصيغ التانية بنحولها لو ينفع، ولو لأ بتشتغل كرسالة صوتية من غير شكل الموجة.
مستند
curl -X POST "https://mursilat.com/api/v1/whatsapp/public/messages" \
-H "Content-Type: application/json" \
-H "x-api-key: $WASLA_API_KEY" \
-H "Idempotency-Key: order-1042-invoice" \
-d '{
"to": "201001234567",
"type": "document",
"media": {
"url": "https://example.com/invoices/1042.pdf"
},
"fileName": "invoice-1042.pdf",
"caption": "فاتورة طلبك رقم 1042",
"reference": "order-1042"
}'// Node.js 18+ (built-in fetch)
const res = await fetch('https://mursilat.com/api/v1/whatsapp/public/messages', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': process.env.WASLA_API_KEY,
'Idempotency-Key': 'order-1042-invoice',
},
body: JSON.stringify({
"to": "201001234567",
"type": "document",
"media": {
"url": "https://example.com/invoices/1042.pdf"
},
"fileName": "invoice-1042.pdf",
"caption": "فاتورة طلبك رقم 1042",
"reference": "order-1042"
}),
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.message}`);
console.log(data);<?php
$payload = [
'to' => '201001234567',
'type' => 'document',
'media' => [
'url' => 'https://example.com/invoices/1042.pdf',
],
'fileName' => 'invoice-1042.pdf',
'caption' => 'فاتورة طلبك رقم 1042',
'reference' => 'order-1042',
];
$ch = curl_init('https://mursilat.com/api/v1/whatsapp/public/messages');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'x-api-key: ' . getenv('WASLA_API_KEY'),
'Idempotency-Key: order-1042-invoice',
],
CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_UNICODE),
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$data = json_decode($response, true);
if ($status >= 400) {
throw new RuntimeException($data['code'] . ': ' . json_encode($data['message']));
}
print_r($data);import os
import requests
payload = {
"to": "201001234567",
"type": "document",
"media": {
"url": "https://example.com/invoices/1042.pdf",
},
"fileName": "invoice-1042.pdf",
"caption": "فاتورة طلبك رقم 1042",
"reference": "order-1042",
}
res = requests.post(
"https://mursilat.com/api/v1/whatsapp/public/messages",
headers={
"x-api-key": os.environ["WASLA_API_KEY"],
"Idempotency-Key": "order-1042-invoice",
},
json=payload,
timeout=30,
)
data = res.json()
if not res.ok:
raise RuntimeError(f"{data.get('code')}: {data.get('message')}")
print(data)على الموبايل: كارت ملف باسمه وحجمه ونوعه. fileName افتراضياً اسم الملف المرفوع أو آخر جزء في الرابط.
الملصقات
"type": "sticker" مع media.
- WebP (ثابت أو متحرك) لحد 500 كيلوبايت بيتبعت زي ما هو.
- PNG وJPG وGIF (لحد 5 ميجا) بنحولهم لملصق WebP مقاس 512×512 بخلفية شفافة، والـ GIF المتحرك بيبقى ملصق متحرك.
- أي حاجة تانية بترجع
MESSAGE_STICKER_INVALID.
curl -X POST "https://mursilat.com/api/v1/whatsapp/public/messages" \
-H "Content-Type: application/json" \
-H "x-api-key: $WASLA_API_KEY" \
-H "Idempotency-Key: thanks-sticker-1042" \
-d '{
"to": "201001234567",
"type": "sticker",
"media": {
"url": "https://example.com/stickers/thanks.webp"
}
}'// Node.js 18+ (built-in fetch)
const res = await fetch('https://mursilat.com/api/v1/whatsapp/public/messages', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': process.env.WASLA_API_KEY,
'Idempotency-Key': 'thanks-sticker-1042',
},
body: JSON.stringify({
"to": "201001234567",
"type": "sticker",
"media": {
"url": "https://example.com/stickers/thanks.webp"
}
}),
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.message}`);
console.log(data);<?php
$payload = [
'to' => '201001234567',
'type' => 'sticker',
'media' => [
'url' => 'https://example.com/stickers/thanks.webp',
],
];
$ch = curl_init('https://mursilat.com/api/v1/whatsapp/public/messages');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'x-api-key: ' . getenv('WASLA_API_KEY'),
'Idempotency-Key: thanks-sticker-1042',
],
CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_UNICODE),
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$data = json_decode($response, true);
if ($status >= 400) {
throw new RuntimeException($data['code'] . ': ' . json_encode($data['message']));
}
print_r($data);import os
import requests
payload = {
"to": "201001234567",
"type": "sticker",
"media": {
"url": "https://example.com/stickers/thanks.webp",
},
}
res = requests.post(
"https://mursilat.com/api/v1/whatsapp/public/messages",
headers={
"x-api-key": os.environ["WASLA_API_KEY"],
"Idempotency-Key": "thanks-sticker-1042",
},
json=payload,
timeout=30,
)
data = res.json()
if not res.ok:
raise RuntimeError(f"{data.get('code')}: {data.get('message')}")
print(data)على الموبايل: ملصق من غير فقاعة، على كل إصدارات واتساب الحديثة.
الموقع
"type": "location" — دبوس ثابت. الموقع المباشر مينفعش يتبعت من رقم مربوط.
| الخانة | الحدود |
|---|---|
latitude | الحدودمن −90 لـ 90 (مطلوب) |
longitude | الحدودمن −180 لـ 180 (مطلوب) |
name | الحدودلحد 100 |
address | الحدودلحد 200 |
curl -X POST "https://mursilat.com/api/v1/whatsapp/public/messages" \
-H "Content-Type: application/json" \
-H "x-api-key: $WASLA_API_KEY" \
-H "Idempotency-Key: store-location-1042" \
-d '{
"to": "201001234567",
"type": "location",
"latitude": 30.0444,
"longitude": 31.2357,
"name": "متجر النور",
"address": "12 شارع التحرير، القاهرة"
}'// Node.js 18+ (built-in fetch)
const res = await fetch('https://mursilat.com/api/v1/whatsapp/public/messages', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': process.env.WASLA_API_KEY,
'Idempotency-Key': 'store-location-1042',
},
body: JSON.stringify({
"to": "201001234567",
"type": "location",
"latitude": 30.0444,
"longitude": 31.2357,
"name": "متجر النور",
"address": "12 شارع التحرير، القاهرة"
}),
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.message}`);
console.log(data);<?php
$payload = [
'to' => '201001234567',
'type' => 'location',
'latitude' => 30.0444,
'longitude' => 31.2357,
'name' => 'متجر النور',
'address' => '12 شارع التحرير، القاهرة',
];
$ch = curl_init('https://mursilat.com/api/v1/whatsapp/public/messages');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'x-api-key: ' . getenv('WASLA_API_KEY'),
'Idempotency-Key: store-location-1042',
],
CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_UNICODE),
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$data = json_decode($response, true);
if ($status >= 400) {
throw new RuntimeException($data['code'] . ': ' . json_encode($data['message']));
}
print_r($data);import os
import requests
payload = {
"to": "201001234567",
"type": "location",
"latitude": 30.0444,
"longitude": 31.2357,
"name": "متجر النور",
"address": "12 شارع التحرير، القاهرة",
}
res = requests.post(
"https://mursilat.com/api/v1/whatsapp/public/messages",
headers={
"x-api-key": os.environ["WASLA_API_KEY"],
"Idempotency-Key": "store-location-1042",
},
json=payload,
timeout=30,
)
data = res.json()
if not res.ok:
raise RuntimeError(f"{data.get('code')}: {data.get('message')}")
print(data)على الموبايل: معاينة خريطة بالاسم والعنوان، ولما العميل يدوس عليها بتفتح تطبيق الخرائط.
جهات الاتصال
"type": "contact" — من 1 لـ 5 كروت. الـ vCard بنبنيه على السيرفر (متأمّن، ولكل رقم رابط واتساب).
| الخانة | الحدود |
|---|---|
contacts[] | الحدود1–5 |
contacts[].name | الحدودلحد 100 (مطلوب) |
contacts[].phones[] | الحدود1–3، أرقام بكود الدولة (عادي + والمسافات) |
contacts[].email | الحدودلحد 120، إيميل صحيح |
contacts[].organization | الحدودلحد 100 |
curl -X POST "https://mursilat.com/api/v1/whatsapp/public/messages" \
-H "Content-Type: application/json" \
-H "x-api-key: $WASLA_API_KEY" \
-H "Idempotency-Key: support-card-1042" \
-d '{
"to": "201001234567",
"type": "contact",
"contacts": [
{
"name": "خدمة عملاء متجر النور",
"phones": [
"+20 100 302 2759"
],
"email": "support@example.com",
"organization": "متجر النور"
}
]
}'// Node.js 18+ (built-in fetch)
const res = await fetch('https://mursilat.com/api/v1/whatsapp/public/messages', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': process.env.WASLA_API_KEY,
'Idempotency-Key': 'support-card-1042',
},
body: JSON.stringify({
"to": "201001234567",
"type": "contact",
"contacts": [
{
"name": "خدمة عملاء متجر النور",
"phones": [
"+20 100 302 2759"
],
"email": "support@example.com",
"organization": "متجر النور"
}
]
}),
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.message}`);
console.log(data);<?php
$payload = [
'to' => '201001234567',
'type' => 'contact',
'contacts' => [
[
'name' => 'خدمة عملاء متجر النور',
'phones' => [
'+20 100 302 2759',
],
'email' => 'support@example.com',
'organization' => 'متجر النور',
],
],
];
$ch = curl_init('https://mursilat.com/api/v1/whatsapp/public/messages');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'x-api-key: ' . getenv('WASLA_API_KEY'),
'Idempotency-Key: support-card-1042',
],
CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_UNICODE),
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$data = json_decode($response, true);
if ($status >= 400) {
throw new RuntimeException($data['code'] . ': ' . json_encode($data['message']));
}
print_r($data);import os
import requests
payload = {
"to": "201001234567",
"type": "contact",
"contacts": [
{
"name": "خدمة عملاء متجر النور",
"phones": [
"+20 100 302 2759",
],
"email": "support@example.com",
"organization": "متجر النور",
},
],
}
res = requests.post(
"https://mursilat.com/api/v1/whatsapp/public/messages",
headers={
"x-api-key": os.environ["WASLA_API_KEY"],
"Idempotency-Key": "support-card-1042",
},
json=payload,
timeout=30,
)
data = res.json()
if not res.ok:
raise RuntimeError(f"{data.get('code')}: {data.get('message')}")
print(data)على الموبايل: كارت جهة اتصال بزرارين «مراسلة» و«إضافة»؛ والكروت المتعددة بتظهر في فقاعة واحدة.
الاستطلاعات
"type": "poll" — سؤال بـ 2 لـ 12 اختيار مختلفين. والأصوات بترجعلك.
| الخانة | الحدود |
|---|---|
name | الحدودالسؤال، لحد 255 (مطلوب) |
options[] | الحدود2–12، مختلفين، كل واحد لحد 100 |
selectableCount | الحدود1 (الافتراضي) = إجابة واحدة · لحد عدد الاختيارات · 0 = أي عدد إجابات |
curl -X POST "https://mursilat.com/api/v1/whatsapp/public/messages" \
-H "Content-Type: application/json" \
-H "x-api-key: $WASLA_API_KEY" \
-H "Idempotency-Key: survey-7-201001234567" \
-d '{
"to": "201001234567",
"type": "poll",
"name": "أنهي يوم يناسبك للتوصيل؟",
"options": [
"الأحد",
"الاثنين",
"الثلاثاء"
],
"selectableCount": 1,
"reference": "survey-7"
}'// Node.js 18+ (built-in fetch)
const res = await fetch('https://mursilat.com/api/v1/whatsapp/public/messages', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': process.env.WASLA_API_KEY,
'Idempotency-Key': 'survey-7-201001234567',
},
body: JSON.stringify({
"to": "201001234567",
"type": "poll",
"name": "أنهي يوم يناسبك للتوصيل؟",
"options": [
"الأحد",
"الاثنين",
"الثلاثاء"
],
"selectableCount": 1,
"reference": "survey-7"
}),
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.message}`);
console.log(data);<?php
$payload = [
'to' => '201001234567',
'type' => 'poll',
'name' => 'أنهي يوم يناسبك للتوصيل؟',
'options' => [
'الأحد',
'الاثنين',
'الثلاثاء',
],
'selectableCount' => 1,
'reference' => 'survey-7',
];
$ch = curl_init('https://mursilat.com/api/v1/whatsapp/public/messages');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'x-api-key: ' . getenv('WASLA_API_KEY'),
'Idempotency-Key: survey-7-201001234567',
],
CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_UNICODE),
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$data = json_decode($response, true);
if ($status >= 400) {
throw new RuntimeException($data['code'] . ': ' . json_encode($data['message']));
}
print_r($data);import os
import requests
payload = {
"to": "201001234567",
"type": "poll",
"name": "أنهي يوم يناسبك للتوصيل؟",
"options": [
"الأحد",
"الاثنين",
"الثلاثاء",
],
"selectableCount": 1,
"reference": "survey-7",
}
res = requests.post(
"https://mursilat.com/api/v1/whatsapp/public/messages",
headers={
"x-api-key": os.environ["WASLA_API_KEY"],
"Idempotency-Key": "survey-7-201001234567",
},
json=payload,
timeout=30,
)
data = res.json()
if not res.ok:
raise RuntimeError(f"{data.get('code')}: {data.get('message')}")
print(data)الأصوات (poll.vote)
poll.vote
حدث كل مرة حد يصوّت أو يغيّر صوته، للاستطلاعات اللي رقمك بعتها. selectedOptions هو اختياره الحالي ([] = شال صوته). وreference هو اللي انت بعته. الأصوات بتظهر كمان في صندوق رسايل مرسلات.
{
"id": "evt_01J9ZK3Q8V6Y2M4N7P1R5T0W9H",
"type": "poll.vote",
"apiVersion": "2026-10",
"createdAt": "2026-10-10T12:04:00.000Z",
"sessionId": "cm1abc2def3ghi4jkl5mno6p",
"number": "201100302759",
"data": {
"messageId": "3EB0A1B2C3D4E5F60718",
"reference": "survey-7",
"from": {
"phone": "201001234567",
"lid": null,
"name": "منى علي"
},
"selectedOptions": [
"الأحد"
],
"timestamp": "2026-10-10T12:04:00.000Z"
}
}على الموبايل: استطلاع واتساب الأصلي على كل الإصدارات الحديثة.
المناسبات (تجريبي)
تجريبي
موبايلات كتير بتعرض المناسبات في الجروبات والمجتمعات بس، فالمناسبة في محادثة فردية ممكن تظهر «رسالة غير مدعومة» على بعض الموبايلات. جرّب على موبايلك الأول.
| الخانة | الحدود |
|---|---|
name | الحدودلحد 100 (مطلوب) |
description | الحدودلحد 2048 |
startAt | الحدودتاريخ ISO في المستقبل، لحد سنة قدام (مطلوب) |
endAt | الحدودتاريخ ISO بعد startAt |
locationName | الحدودلحد 100 |
extraGuestsAllowed | الحدودboolean |
curl -X POST "https://mursilat.com/api/v1/whatsapp/public/messages" \
-H "Content-Type: application/json" \
-H "x-api-key: $WASLA_API_KEY" \
-H "Idempotency-Key: opening-201001234567" \
-d '{
"to": "201001234567",
"type": "event",
"name": "افتتاح فرعنا الجديد",
"description": "خصم 20% لأول 50 عميل",
"startAt": "2026-11-01T17:00:00.000Z",
"endAt": "2026-11-01T21:00:00.000Z",
"locationName": "متجر النور — مدينة نصر"
}'// Node.js 18+ (built-in fetch)
const res = await fetch('https://mursilat.com/api/v1/whatsapp/public/messages', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': process.env.WASLA_API_KEY,
'Idempotency-Key': 'opening-201001234567',
},
body: JSON.stringify({
"to": "201001234567",
"type": "event",
"name": "افتتاح فرعنا الجديد",
"description": "خصم 20% لأول 50 عميل",
"startAt": "2026-11-01T17:00:00.000Z",
"endAt": "2026-11-01T21:00:00.000Z",
"locationName": "متجر النور — مدينة نصر"
}),
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.message}`);
console.log(data);<?php
$payload = [
'to' => '201001234567',
'type' => 'event',
'name' => 'افتتاح فرعنا الجديد',
'description' => 'خصم 20% لأول 50 عميل',
'startAt' => '2026-11-01T17:00:00.000Z',
'endAt' => '2026-11-01T21:00:00.000Z',
'locationName' => 'متجر النور — مدينة نصر',
];
$ch = curl_init('https://mursilat.com/api/v1/whatsapp/public/messages');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'x-api-key: ' . getenv('WASLA_API_KEY'),
'Idempotency-Key: opening-201001234567',
],
CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_UNICODE),
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$data = json_decode($response, true);
if ($status >= 400) {
throw new RuntimeException($data['code'] . ': ' . json_encode($data['message']));
}
print_r($data);import os
import requests
payload = {
"to": "201001234567",
"type": "event",
"name": "افتتاح فرعنا الجديد",
"description": "خصم 20% لأول 50 عميل",
"startAt": "2026-11-01T17:00:00.000Z",
"endAt": "2026-11-01T21:00:00.000Z",
"locationName": "متجر النور — مدينة نصر",
}
res = requests.post(
"https://mursilat.com/api/v1/whatsapp/public/messages",
headers={
"x-api-key": os.environ["WASLA_API_KEY"],
"Idempotency-Key": "opening-201001234567",
},
json=payload,
timeout=30,
)
data = res.json()
if not res.ok:
raise RuntimeError(f"{data.get('code')}: {data.get('message')}")
print(data)إرسال قائمة
"type": "list" — زرار قائمة بيفتح لحد 10 اختيارات متقسّمة أقسام. كل اختيار ممكن يكون له وصف.
| الخانة | الحدود |
|---|---|
list.buttonText | الحدودلحد 20 — اسم زرار القائمة |
list.sections[] | الحدود1–10 أقسام، title لحد 24 (اختياري) |
rows[] | الحدود1–10 اختيارات إجمالاً · id لحد 64 ومختلف · title لحد 24 · description لحد 72 (اختياري) |
curl -X POST "https://mursilat.com/api/v1/whatsapp/public/messages" \
-H "Content-Type: application/json" \
-H "x-api-key: $WASLA_API_KEY" \
-H "Idempotency-Key: menu-201001234567-1" \
-d '{
"to": "201001234567",
"type": "list",
"body": {
"text": "إزاي نقدر نساعدك؟"
},
"list": {
"buttonText": "اختار",
"sections": [
{
"title": "الخدمات",
"rows": [
{
"id": "prices",
"title": "الأسعار",
"description": "اعرف أسعارنا"
},
{
"id": "hours",
"title": "مواعيد العمل"
}
]
},
{
"title": "الدعم",
"rows": [
{
"id": "agent",
"title": "كلّم موظف",
"description": "حد من الفريق هيرد عليك"
}
]
}
]
},
"reference": "menu-main"
}'// Node.js 18+ (built-in fetch)
const res = await fetch('https://mursilat.com/api/v1/whatsapp/public/messages', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': process.env.WASLA_API_KEY,
'Idempotency-Key': 'menu-201001234567-1',
},
body: JSON.stringify({
"to": "201001234567",
"type": "list",
"body": {
"text": "إزاي نقدر نساعدك؟"
},
"list": {
"buttonText": "اختار",
"sections": [
{
"title": "الخدمات",
"rows": [
{
"id": "prices",
"title": "الأسعار",
"description": "اعرف أسعارنا"
},
{
"id": "hours",
"title": "مواعيد العمل"
}
]
},
{
"title": "الدعم",
"rows": [
{
"id": "agent",
"title": "كلّم موظف",
"description": "حد من الفريق هيرد عليك"
}
]
}
]
},
"reference": "menu-main"
}),
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.message}`);
console.log(data);<?php
$payload = [
'to' => '201001234567',
'type' => 'list',
'body' => [
'text' => 'إزاي نقدر نساعدك؟',
],
'list' => [
'buttonText' => 'اختار',
'sections' => [
[
'title' => 'الخدمات',
'rows' => [
[
'id' => 'prices',
'title' => 'الأسعار',
'description' => 'اعرف أسعارنا',
],
[
'id' => 'hours',
'title' => 'مواعيد العمل',
],
],
],
[
'title' => 'الدعم',
'rows' => [
[
'id' => 'agent',
'title' => 'كلّم موظف',
'description' => 'حد من الفريق هيرد عليك',
],
],
],
],
],
'reference' => 'menu-main',
];
$ch = curl_init('https://mursilat.com/api/v1/whatsapp/public/messages');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'x-api-key: ' . getenv('WASLA_API_KEY'),
'Idempotency-Key: menu-201001234567-1',
],
CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_UNICODE),
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$data = json_decode($response, true);
if ($status >= 400) {
throw new RuntimeException($data['code'] . ': ' . json_encode($data['message']));
}
print_r($data);import os
import requests
payload = {
"to": "201001234567",
"type": "list",
"body": {
"text": "إزاي نقدر نساعدك؟",
},
"list": {
"buttonText": "اختار",
"sections": [
{
"title": "الخدمات",
"rows": [
{
"id": "prices",
"title": "الأسعار",
"description": "اعرف أسعارنا",
},
{
"id": "hours",
"title": "مواعيد العمل",
},
],
},
{
"title": "الدعم",
"rows": [
{
"id": "agent",
"title": "كلّم موظف",
"description": "حد من الفريق هيرد عليك",
},
],
},
],
},
"reference": "menu-main",
}
res = requests.post(
"https://mursilat.com/api/v1/whatsapp/public/messages",
headers={
"x-api-key": os.environ["WASLA_API_KEY"],
"Idempotency-Key": "menu-201001234567-1",
},
json=payload,
timeout=30,
)
data = res.json()
if not res.ok:
raise RuntimeError(f"{data.get('code')}: {data.get('message')}")
print(data)البديل المرقّم بيرقّم الاختيارات بالترتيب عبر كل الأقسام (1…n)، فالرقم اللي العميل يكتبه دايماً بيتطابق.
زرار رابط (CTA)
"type": "cta_url" — زرار واحد بيفتح رابط. مفيش رد متوقع، فمفيش interactive.reply ليه.
| الخانة | الحدود |
|---|---|
cta.title | الحدودلحد 20 |
cta.url | الحدودرابط https:// عام |
curl -X POST "https://mursilat.com/api/v1/whatsapp/public/messages" \
-H "Content-Type: application/json" \
-H "x-api-key: $WASLA_API_KEY" \
-H "Idempotency-Key: order-1042-track" \
-d '{
"to": "201001234567",
"type": "cta_url",
"body": {
"text": "شحنتك في الطريق 🚚"
},
"cta": {
"title": "تتبع الشحنة",
"url": "https://example.com/track/1042"
},
"reference": "order-1042"
}'// Node.js 18+ (built-in fetch)
const res = await fetch('https://mursilat.com/api/v1/whatsapp/public/messages', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': process.env.WASLA_API_KEY,
'Idempotency-Key': 'order-1042-track',
},
body: JSON.stringify({
"to": "201001234567",
"type": "cta_url",
"body": {
"text": "شحنتك في الطريق 🚚"
},
"cta": {
"title": "تتبع الشحنة",
"url": "https://example.com/track/1042"
},
"reference": "order-1042"
}),
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.message}`);
console.log(data);<?php
$payload = [
'to' => '201001234567',
'type' => 'cta_url',
'body' => [
'text' => 'شحنتك في الطريق 🚚',
],
'cta' => [
'title' => 'تتبع الشحنة',
'url' => 'https://example.com/track/1042',
],
'reference' => 'order-1042',
];
$ch = curl_init('https://mursilat.com/api/v1/whatsapp/public/messages');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'x-api-key: ' . getenv('WASLA_API_KEY'),
'Idempotency-Key: order-1042-track',
],
CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_UNICODE),
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$data = json_decode($response, true);
if ($status >= 400) {
throw new RuntimeException($data['code'] . ': ' . json_encode($data['message']));
}
print_r($data);import os
import requests
payload = {
"to": "201001234567",
"type": "cta_url",
"body": {
"text": "شحنتك في الطريق 🚚",
},
"cta": {
"title": "تتبع الشحنة",
"url": "https://example.com/track/1042",
},
"reference": "order-1042",
}
res = requests.post(
"https://mursilat.com/api/v1/whatsapp/public/messages",
headers={
"x-api-key": os.environ["WASLA_API_KEY"],
"Idempotency-Key": "order-1042-track",
},
json=payload,
timeout=30,
)
data = res.json()
if not res.ok:
raise RuntimeError(f"{data.get('code')}: {data.get('message')}")
print(data)البديل بيضيف الرابط في آخر الرسالة (🔗 https://…). وقواعد الروابط بتطبّق عليه: الروابط المختصرة ممنوعة، والأرقام اللي عمرها أقل من 7 أيام مش بتبعت روابط لجهات اتصال جديدة (شوف الحدود وحماية الرقم).
حاجات مش ممكنة من رقم مربوط
الأنواع دي بترجع 400 MESSAGE_TYPE_NOT_SUPPORTED ومعاها details.type وdetails.reason. دي مميزات في تطبيق الموبايل نفسه أو في الـ Cloud API الرسمي، مش في رقم مربوط.
| `type` | `reason` | ليه |
|---|---|---|
live_location | `reason`LINKED_DEVICE_LIMITATION | ليهالموقع المباشر بيتشارك من الموبايل نفسه بس. ابعت دبوس location بداله. |
payment | `reason`NOT_AVAILABLE | ليهواتساب باي مش متاح في مصر ولا السعودية، ولا على الأجهزة المربوطة. |
product · catalog · order | `reason`BUSINESS_CATALOG_ONLY | ليهرسايل الكتالوج محتاجة كتالوج واتساب بيزنس على الموبايل. ابعت صورة + زرار رابط بدالها. |
status · story | `reason`OUT_OF_SCOPE | ليهالحالات مش رسايل محادثة. |
call | `reason`LINKED_DEVICE_LIMITATION | ليهالمكالمات مينفعش تتعمل من جهاز مربوط. |
template | `reason`CLOUD_API_ONLY | ليهقوالب الرسايل موجودة في الـ Cloud API الرسمي بس. |
album | `reason`SEND_ITEMS_SEPARATELY | ليهابعت كل صورة أو فيديو في رسالة لوحدها. |
| أي حاجة تانية | `reason`UNKNOWN_TYPE | ليهمش نوع رسالة. |
رفع الميديا
POST /whatsapp/public/media/upload — صلاحية media، وmultipart/form-data فيه file (لحد 100 ميجا؛ وحد النوع بيتطبق وقت الإرسال). ارفع مرة واحدة وبعدها ابعت "media": { "id": "…" } — مفيد للملفات الكبيرة أو لما تبعت نفس الملف لعملاء كتير.
curl -X POST "https://mursilat.com/api/v1/whatsapp/public/media/upload" \
-H "x-api-key: $WASLA_API_KEY" \
-F "file=@./photo.jpg"
# → { "mediaId": "med_01J9ZK4A2B3C4D5E6F7G8H9J0K", "mimeType": "image/jpeg", "size": 184233,
# "fileName": "photo.jpg", "expiresAt": "2026-10-11T12:00:00.000Z" }| خانة الرد | ملاحظات |
|---|---|
mediaId | ملاحظاتاستخدمه في media.id (لنفس الرقم بس). |
mimeType · size · fileName | ملاحظاتاللي اكتشفناه من الملف. |
expiresAt | ملاحظاتبعد 24 ساعة من الرفع. بعدها 410 MESSAGE_MEDIA_EXPIRED — ارفعه تاني. |
الملفات المرفوعة خاصة (عمرها ما بتبقى روابط عامة) وبتتمسح لوحدها بعد 24 ساعة. Idempotency-Key اختياري هنا.
التفاعل والتعديل والمسح
POST /whatsapp/public/messages/:messageId/react | edit | delete — :messageId هو الرقم اللي رجع من الإرسال (أو رقم رسالة في صندوق مرسلات) على رقم المفتاح بتاعك. صلاحية send، وIdempotency-Key مطلوب. الأفعال دي مش بتبعت message.status.
تفاعل
curl -X POST "https://mursilat.com/api/v1/whatsapp/public/messages/3EB0A1B2C3D4E5F60718/react" \
-H "Content-Type: application/json" \
-H "x-api-key: $WASLA_API_KEY" \
-H "Idempotency-Key: react-3EB0A1B2-1" \
-d '{
"emoji": "👍"
}'// Node.js 18+ (built-in fetch)
const res = await fetch('https://mursilat.com/api/v1/whatsapp/public/messages/3EB0A1B2C3D4E5F60718/react', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': process.env.WASLA_API_KEY,
'Idempotency-Key': 'react-3EB0A1B2-1',
},
body: JSON.stringify({
"emoji": "👍"
}),
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.message}`);
console.log(data);<?php
$payload = [
'emoji' => '👍',
];
$ch = curl_init('https://mursilat.com/api/v1/whatsapp/public/messages/3EB0A1B2C3D4E5F60718/react');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'x-api-key: ' . getenv('WASLA_API_KEY'),
'Idempotency-Key: react-3EB0A1B2-1',
],
CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_UNICODE),
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$data = json_decode($response, true);
if ($status >= 400) {
throw new RuntimeException($data['code'] . ': ' . json_encode($data['message']));
}
print_r($data);import os
import requests
payload = {
"emoji": "👍",
}
res = requests.post(
"https://mursilat.com/api/v1/whatsapp/public/messages/3EB0A1B2C3D4E5F60718/react",
headers={
"x-api-key": os.environ["WASLA_API_KEY"],
"Idempotency-Key": "react-3EB0A1B2-1",
},
json=payload,
timeout=30,
)
data = res.json()
if not res.ok:
raise RuntimeError(f"{data.get('code')}: {data.get('message')}")
print(data)إيموجي واحد (و"" بيشيل تفاعلك). غير كده MESSAGE_REACTION_INVALID. الرد: { messageId, action: "react", emoji }.
تعديل
curl -X POST "https://mursilat.com/api/v1/whatsapp/public/messages/3EB0A1B2C3D4E5F60718/edit" \
-H "Content-Type: application/json" \
-H "x-api-key: $WASLA_API_KEY" \
-H "Idempotency-Key: edit-3EB0A1B2-1" \
-d '{
"text": "أهلاً أ. منى، طلبك رقم 1042 اتشحن وهيوصلك بعد بكرة."
}'// Node.js 18+ (built-in fetch)
const res = await fetch('https://mursilat.com/api/v1/whatsapp/public/messages/3EB0A1B2C3D4E5F60718/edit', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': process.env.WASLA_API_KEY,
'Idempotency-Key': 'edit-3EB0A1B2-1',
},
body: JSON.stringify({
"text": "أهلاً أ. منى، طلبك رقم 1042 اتشحن وهيوصلك بعد بكرة."
}),
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.message}`);
console.log(data);<?php
$payload = [
'text' => 'أهلاً أ. منى، طلبك رقم 1042 اتشحن وهيوصلك بعد بكرة.',
];
$ch = curl_init('https://mursilat.com/api/v1/whatsapp/public/messages/3EB0A1B2C3D4E5F60718/edit');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'x-api-key: ' . getenv('WASLA_API_KEY'),
'Idempotency-Key: edit-3EB0A1B2-1',
],
CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_UNICODE),
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$data = json_decode($response, true);
if ($status >= 400) {
throw new RuntimeException($data['code'] . ': ' . json_encode($data['message']));
}
print_r($data);import os
import requests
payload = {
"text": "أهلاً أ. منى، طلبك رقم 1042 اتشحن وهيوصلك بعد بكرة.",
}
res = requests.post(
"https://mursilat.com/api/v1/whatsapp/public/messages/3EB0A1B2C3D4E5F60718/edit",
headers={
"x-api-key": os.environ["WASLA_API_KEY"],
"Idempotency-Key": "edit-3EB0A1B2-1",
},
json=payload,
timeout=30,
)
data = res.json()
if not res.ok:
raise RuntimeError(f"{data.get('code')}: {data.get('message')}")
print(data)الرسايل النصية اللي رقمك بعتها من الـ API أو مرسلات بس، وخلال 15 دقيقة (409 MESSAGE_EDIT_WINDOW_PASSED و409 MESSAGE_NOT_EDITABLE). النص الجديد بيعدي على نفس قواعد المحتوى. العميل بيشوف «تم التعديل».
مسح
curl -X POST "https://mursilat.com/api/v1/whatsapp/public/messages/3EB0A1B2C3D4E5F60718/delete" \
-H "Content-Type: application/json" \
-H "x-api-key: $WASLA_API_KEY" \
-H "Idempotency-Key: delete-3EB0A1B2-1" \
-d '{
"forEveryone": true
}'// Node.js 18+ (built-in fetch)
const res = await fetch('https://mursilat.com/api/v1/whatsapp/public/messages/3EB0A1B2C3D4E5F60718/delete', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': process.env.WASLA_API_KEY,
'Idempotency-Key': 'delete-3EB0A1B2-1',
},
body: JSON.stringify({
"forEveryone": true
}),
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.message}`);
console.log(data);<?php
$payload = [
'forEveryone' => true,
];
$ch = curl_init('https://mursilat.com/api/v1/whatsapp/public/messages/3EB0A1B2C3D4E5F60718/delete');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'x-api-key: ' . getenv('WASLA_API_KEY'),
'Idempotency-Key: delete-3EB0A1B2-1',
],
CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_UNICODE),
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$data = json_decode($response, true);
if ($status >= 400) {
throw new RuntimeException($data['code'] . ': ' . json_encode($data['message']));
}
print_r($data);import os
import requests
payload = {
"forEveryone": True,
}
res = requests.post(
"https://mursilat.com/api/v1/whatsapp/public/messages/3EB0A1B2C3D4E5F60718/delete",
headers={
"x-api-key": os.environ["WASLA_API_KEY"],
"Idempotency-Key": "delete-3EB0A1B2-1",
},
json=payload,
timeout=30,
)
data = res.json()
if not res.ok:
raise RuntimeError(f"{data.get('code')}: {data.get('message')}")
print(data)forEveryone: true بيمسح رسالتك عند الكل خلال 48 ساعة (409 MESSAGE_DELETE_WINDOW_PASSED و409 MESSAGE_NOT_DELETABLE)؛ والعميل بيشوف «تم حذف هذه الرسالة». وfalse بيمسحها عندك بس (بتختفي من موبايلك ومن صندوق الرسايل).
الردود (interactive.reply)
لما العميل يرد على رسالة أزرار أو قائمة بيوصلك interactive.reply — سواء ضغط زرار، أو اختار من القائمة، أو كتب الرقم، أو كتب اسم الاختيار. reply.source بيقولك إزاي: button · list · number · title.
- أول رد هو اللي بيتحسب. التفاعل بيبقى
ANSWERED؛ وأي رد بعده بيوصل كـmessage.receivedبس. - الردود المكتوبة بتتطابق مع آخر تفاعل مفتوح للعميل على الرقم ده: الأرقام
1…n(إنجليزي أو عربي ١ ٢ ٣) أو اسم الاختيار. - أي نص مش متطابق بيوصل كرسالة عادية
message.received— إحنا مش بنخمّن. - بعد
expiresInMinutesالتفاعل بيبقىEXPIREDومش بيتطابق تاني. - كلمات إيقاف الرسايل (زي «إلغاء» أو “stop”) بتتعامل كطلب إيقاف الأول، فأسماء الاختيارات ماينفعش تكون كلمات إيقاف، ولازم تكون مختلفة عن بعض.
interactive.reply
استخدم reference (أو interactionId) عشان توصل لطلبك، وreply.id للاختيار اللي العميل اختاره.
{
"id": "evt_01J9ZK3Q8V6Y2M4N7P1R5T0W9X",
"type": "interactive.reply",
"apiVersion": "2026-10",
"createdAt": "2026-10-10T12:00:03.000Z",
"sessionId": "cm1abc2def3ghi4jkl5mno6p",
"number": "201100302759",
"data": {
"interactionId": "int_8f2c1d9a7b3e4f56a1b2c3d4",
"messageId": "3EB0A1B2C3D4E5F60718",
"reference": "order-1042",
"from": {
"phone": "201001234567",
"lid": null,
"name": "منى علي"
},
"reply": {
"id": "confirm",
"title": "تأكيد",
"index": 1,
"source": "button"
},
"replyMessageId": "3EB0C9D8E7F6A5B40312",
"timestamp": "2026-10-10T12:00:03.000Z"
}
}الاستعلام عن تفاعل
GET /whatsapp/public/interactions/:interactionId — صلاحية send. مفيد كخطة بديلة لو ويب هوك فاتك.
curl -X GET "https://mursilat.com/api/v1/whatsapp/public/interactions/int_8f2c1d9a7b3e4f56a1b2c3d4" \
-H "x-api-key: $WASLA_API_KEY"// Node.js 18+ (built-in fetch)
const res = await fetch('https://mursilat.com/api/v1/whatsapp/public/interactions/int_8f2c1d9a7b3e4f56a1b2c3d4', {
method: 'GET',
headers: {
'x-api-key': process.env.WASLA_API_KEY,
},
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.message}`);
console.log(data);<?php
$ch = curl_init('https://mursilat.com/api/v1/whatsapp/public/interactions/int_8f2c1d9a7b3e4f56a1b2c3d4');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [
'x-api-key: ' . getenv('WASLA_API_KEY'),
],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$data = json_decode($response, true);
if ($status >= 400) {
throw new RuntimeException($data['code'] . ': ' . json_encode($data['message']));
}
print_r($data);import os
import requests
res = requests.get(
"https://mursilat.com/api/v1/whatsapp/public/interactions/int_8f2c1d9a7b3e4f56a1b2c3d4",
headers={
"x-api-key": os.environ["WASLA_API_KEY"],
},
timeout=30,
)
data = res.json()
if not res.ok:
raise RuntimeError(f"{data.get('code')}: {data.get('message')}")
print(data){
"interactionId": "int_8f2c1d9a7b3e4f56a1b2c3d4",
"messageId": "3EB0A1B2C3D4E5F60718",
"type": "buttons",
"reference": "order-1042",
"status": "ANSWERED",
"options": [
{
"id": "confirm",
"title": "تأكيد",
"index": 1
},
{
"id": "cancel_order",
"title": "إلغاء الطلب",
"index": 2
}
],
"answer": {
"id": "confirm",
"title": "تأكيد",
"index": 1,
"source": "number",
"replyMessageId": "3EB0C9D8E7F6A5B40312",
"answeredAt": "2026-10-10T12:00:03.000Z"
},
"expiresAt": "2026-10-11T12:00:00.000Z",
"createdAt": "2026-10-10T12:00:00.000Z"
}الويب هوك
الويب هوك بيبعت الأحداث لسيرفرك أول ما تحصل: رسايل واردة، تحديثات التسليم، ردود الأزرار، طلبات الإيقاف، وحالة الرقم. الإصدار 2 له شكل ثابت وموثّق لكل حدث.
الإعداد
- الأرقام ← رقمك ← تبويب المطوّرين: احفظ الرابط، اختار الإصدار 2، وحدّد الأحداث اللي عايزها.
- انسخ سرّ التوقيع (بيظهر مرة واحدة — وتقدر تعمل واحد جديد في أي وقت).
- اضغط إرسال حدث تجريبي وشوف النتيجة في سجل الويب هوك.
- رابط عام
https://(أوhttp://) على بورت 80 أو 443. العناوين الداخلية والخاصة مرفوضة. - رد بأي
2xxخلال 10 ثواني — والشغل التقيل اعمله بعد ما ترد. - التحويلات (redirects) مش بنتبعها.
POST /webhooks/mursilat HTTP/1.1
Content-Type: application/json
User-Agent: Wasla-Webhooks/2
X-Wasla-Event: interactive.reply
X-Wasla-Delivery: dlv_01J9ZK3R2B7C8D9E0F1G2H3J4K
X-Wasla-Webhook-Version: 2
X-Wasla-Signature: t=1760097603,v1=3f6c2b5e9a0d4c7f8e1b2a3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f70| خانة الغلاف | الوصف |
|---|---|
id | الوصفمعرّف الحدث الفريد (evt_…). ثابت في كل إعادة محاولة وفي إعادة الإرسال اليدوي — اعتمد عليه لمنع التكرار. |
type | الوصفنوع الحدث، مثلاً interactive.reply. |
apiVersion | الوصف2026-10 |
createdAt | الوصفوقت حصول الحدث (ISO-8601 UTC). رتّب بيه الأحداث. |
sessionId · number | الوصفالحدث يخص أنهي رقم من أرقامك. |
data | الوصفمحتوى الحدث (تحت). |
الأحداث
message.received
عميل بعت رسالة. type واحد من text · image · video · audio · voice · document · sticker · location · contact · reaction · interactive_reply · unsupported. عمرنا ما بنبعت كائن واتساب الخام.
{
"id": "evt_01J9ZK3Q8V6Y2M4N7P1R5T0W9A",
"type": "message.received",
"apiVersion": "2026-10",
"createdAt": "2026-10-10T12:00:00.000Z",
"sessionId": "cm1abc2def3ghi4jkl5mno6p",
"number": "201100302759",
"data": {
"messageId": "3EB0F1E2D3C4B5A69788",
"from": {
"phone": "201001234567",
"lid": null,
"name": "منى علي"
},
"chat": {
"type": "direct"
},
"type": "text",
"text": "ممكن أعرف ميعاد التوصيل؟",
"caption": null,
"media": null,
"location": null,
"replyTo": null,
"timestamp": "2026-10-10T12:00:00.000Z"
}
}message.received(image)
رسايل الميديا فيها media.url: رابط تحميل موقّع صالح 24 ساعة. ورسالة الجروب فيها chat.type: "group".
{
"id": "evt_01J9ZK3Q8V6Y2M4N7P1R5T0W9B",
"type": "message.received",
"apiVersion": "2026-10",
"createdAt": "2026-10-10T12:01:00.000Z",
"sessionId": "cm1abc2def3ghi4jkl5mno6p",
"number": "201100302759",
"data": {
"messageId": "3EB0A9B8C7D6E5F40011",
"from": {
"phone": "201001234567",
"lid": null,
"name": "منى علي"
},
"chat": {
"type": "group",
"groupId": "120363025746123456@g.us",
"groupName": "عملاء المتجر"
},
"type": "image",
"text": null,
"caption": "صورة الإيصال",
"media": {
"id": "cm2media0001",
"mimeType": "image/jpeg",
"size": 184233,
"url": "https://mursilat.com/api/v1/whatsapp/public/media/cm2media0001?exp=1760184060&sig=5d41402abc4b2a76b9719d911017c592"
},
"location": null,
"replyTo": {
"messageId": "3EB0A1B2C3D4E5F60718"
},
"timestamp": "2026-10-10T12:01:00.000Z"
}
}# Signed link from the webhook (valid 24h) — no API key needed:
curl -o receipt.jpg "https://mursilat.com/api/v1/whatsapp/public/media/cm2media0001?exp=1760184060&sig=5d41…"
# After it expires: use your API key (scope: media)
curl -o receipt.jpg -H "x-api-key: $WASLA_API_KEY" "https://mursilat.com/api/v1/whatsapp/public/media/cm2media0001"message.status
تحديثات تسليم الرسايل اللي بعتها (من الـ API ولوحة التحكم): sent · delivered · read · failed (مع error). وreference هو اللي بعته.
{
"id": "evt_01J9ZK3Q8V6Y2M4N7P1R5T0W9C",
"type": "message.status",
"apiVersion": "2026-10",
"createdAt": "2026-10-10T12:00:02.000Z",
"sessionId": "cm1abc2def3ghi4jkl5mno6p",
"number": "201100302759",
"data": {
"messageId": "3EB0A1B2C3D4E5F60718",
"reference": "order-1042",
"to": "201001234567",
"status": "delivered",
"error": null,
"timestamp": "2026-10-10T12:00:02.000Z"
}
}interactive.reply
العميل رد على الأزرار أو القائمة (شوف الردود).
{
"id": "evt_01J9ZK3Q8V6Y2M4N7P1R5T0W9X",
"type": "interactive.reply",
"apiVersion": "2026-10",
"createdAt": "2026-10-10T12:00:03.000Z",
"sessionId": "cm1abc2def3ghi4jkl5mno6p",
"number": "201100302759",
"data": {
"interactionId": "int_8f2c1d9a7b3e4f56a1b2c3d4",
"messageId": "3EB0A1B2C3D4E5F60718",
"reference": "order-1042",
"from": {
"phone": "201001234567",
"lid": null,
"name": "منى علي"
},
"reply": {
"id": "confirm",
"title": "تأكيد",
"index": 1,
"source": "button"
},
"replyMessageId": "3EB0C9D8E7F6A5B40312",
"timestamp": "2026-10-10T12:00:03.000Z"
}
}poll.vote
حد صوّت (أو غيّر صوته) في استطلاع رقمك بعته (شوف الاستطلاعات).
{
"id": "evt_01J9ZK3Q8V6Y2M4N7P1R5T0W9H",
"type": "poll.vote",
"apiVersion": "2026-10",
"createdAt": "2026-10-10T12:04:00.000Z",
"sessionId": "cm1abc2def3ghi4jkl5mno6p",
"number": "201100302759",
"data": {
"messageId": "3EB0A1B2C3D4E5F60718",
"reference": "survey-7",
"from": {
"phone": "201001234567",
"lid": null,
"name": "منى علي"
},
"selectedOptions": [
"الأحد"
],
"timestamp": "2026-10-10T12:04:00.000Z"
}
}contact.opted_out
العميل طلب يوقف الرسايل (كلمة إيقاف). أي إرسال ليه بعد كده بيترفض بـ SAFETY_SUPPRESSED.
{
"id": "evt_01J9ZK3Q8V6Y2M4N7P1R5T0W9D",
"type": "contact.opted_out",
"apiVersion": "2026-10",
"createdAt": "2026-10-10T12:05:00.000Z",
"sessionId": "cm1abc2def3ghi4jkl5mno6p",
"number": "201100302759",
"data": {
"phone": "201001234567",
"lid": null,
"keyword": "إلغاء",
"timestamp": "2026-10-10T12:05:00.000Z"
}
}contact.opted_in
العميل رجع يستقبل رسايل. keyword بيبقى null في التغيير اليدوي.
{
"id": "evt_01J9ZK3Q8V6Y2M4N7P1R5T0W9E",
"type": "contact.opted_in",
"apiVersion": "2026-10",
"createdAt": "2026-10-10T12:06:00.000Z",
"sessionId": "cm1abc2def3ghi4jkl5mno6p",
"number": "201100302759",
"data": {
"phone": "201001234567",
"lid": null,
"keyword": null,
"timestamp": "2026-10-10T12:06:00.000Z"
}
}session.status
حالة اتصال رقمك اتغيّرت: connected · disconnected · banned · logged_out.
{
"id": "evt_01J9ZK3Q8V6Y2M4N7P1R5T0W9F",
"type": "session.status",
"apiVersion": "2026-10",
"createdAt": "2026-10-10T12:07:00.000Z",
"sessionId": "cm1abc2def3ghi4jkl5mno6p",
"number": "201100302759",
"data": {
"status": "disconnected",
"reason": "connection_lost",
"timestamp": "2026-10-10T12:07:00.000Z"
}
}webhook.test
بيتبعت من زرار إرسال حدث تجريبي. دايماً بغلاف الإصدار 2 وبيوصل حتى لو مش ضمن الأحداث اللي اخترتها.
{
"id": "evt_01J9ZK3Q8V6Y2M4N7P1R5T0W9G",
"type": "webhook.test",
"apiVersion": "2026-10",
"createdAt": "2026-10-10T12:08:00.000Z",
"sessionId": "cm1abc2def3ghi4jkl5mno6p",
"number": "201100302759",
"data": {
"message": "This is a test event from Mursilat",
"timestamp": "2026-10-10T12:08:00.000Z"
}
}التحقق من التوقيع
كل طلب فيه هيدر X-Wasla-Signature: t=<unix>,v1=<hex> — t هو الوقت بالثواني (Unix)، وv1 هو HMAC-SHA256(secret, "<t>.<raw body>") بحروف hex صغيرة.
- اقرا جسم الطلب الخام (البايتات زي ما وصلت) — قبل أي تحويل لـ JSON.
- قسّم الهيدر على
,و=عشان تطلعtوv1. - ارفض الطلب لو
tبعيد عن ساعتك أكتر من 5 دقايق (حماية من إعادة الإرسال). - احسب HMAC-SHA256 لـ
t + "." + rawBodyبالسرّ بتاعك، بصيغة hex. - قارنه بـ
v1بمقارنة ثابتة الوقت (constant-time). وبعدها بس حوّل الـ JSON.
// Node.js + Express. Use the RAW body: JSON.parse → JSON.stringify changes bytes and breaks the signature.
import crypto from 'node:crypto';
import express from 'express';
const app = express();
const SECRET = process.env.WASLA_WEBHOOK_SECRET;
const seen = new Set(); // use Redis/DB in production
function verifyWaslaSignature(rawBody, header, secret, toleranceSec = 300) {
if (!header || !secret) return false;
const parts = {};
for (const pair of header.split(',')) {
const i = pair.indexOf('=');
if (i > 0) parts[pair.slice(0, i).trim()] = pair.slice(i + 1).trim();
}
const t = parts.t;
const v1 = parts.v1;
if (!/^\d+$/.test(t || '') || !/^[0-9a-f]{64}$/i.test(v1 || '')) return false;
// Reject old (replayed) or far-future timestamps.
if (Math.abs(Math.floor(Date.now() / 1000) - Number(t)) > toleranceSec) return false;
const expected = crypto
.createHmac('sha256', secret)
.update(t + '.')
.update(rawBody) // the RAW bytes exactly as received, not re-serialized JSON
.digest();
const received = Buffer.from(v1, 'hex');
return received.length === expected.length && crypto.timingSafeEqual(received, expected);
}
app.post('/webhooks/mursilat', express.raw({ type: 'application/json' }), (req, res) => {
if (!verifyWaslaSignature(req.body, req.get('X-Wasla-Signature'), SECRET)) {
return res.status(401).send('invalid signature');
}
const event = JSON.parse(req.body.toString('utf8'));
res.sendStatus(200); // answer fast (within 10s); do the work after
if (seen.has(event.id)) return; // retries reuse the same event id
seen.add(event.id);
if (event.type === 'interactive.reply') {
console.log(event.data.reference, '→', event.data.reply.id);
}
});
app.listen(3000);<?php
// Use the RAW body (php://input), never a re-encoded json_decode() result.
function wasla_verify_signature(string $rawBody, string $header, string $secret, int $toleranceSec = 300): bool
{
$parts = [];
foreach (explode(',', $header) as $pair) {
$kv = explode('=', trim($pair), 2);
if (count($kv) === 2) {
$parts[$kv[0]] = $kv[1];
}
}
$t = $parts['t'] ?? '';
$v1 = $parts['v1'] ?? '';
if ($secret === '' || !ctype_digit($t) || !preg_match('/^[0-9a-f]{64}$/i', $v1)) {
return false;
}
// Reject old (replayed) or far-future timestamps.
if (abs(time() - (int) $t) > $toleranceSec) {
return false;
}
$expected = hash_hmac('sha256', $t . '.' . $rawBody, $secret);
return hash_equals($expected, strtolower($v1)); // constant-time
}
$rawBody = file_get_contents('php://input');
$header = $_SERVER['HTTP_X_WASLA_SIGNATURE'] ?? '';
if (!wasla_verify_signature($rawBody, $header, getenv('WASLA_WEBHOOK_SECRET') ?: '')) {
http_response_code(401);
exit('invalid signature');
}
$event = json_decode($rawBody, true);
http_response_code(200); // answer fast (within 10s)
// Retries reuse the same event id — store it and skip duplicates.
if ($event['type'] === 'interactive.reply') {
error_log($event['data']['reference'] . ' -> ' . $event['data']['reply']['id']);
}# Python 3 + Flask. Use the RAW body (request.get_data()), not request.json re-encoded.
import hashlib
import hmac
import json
import os
import time
from flask import Flask, abort, request
app = Flask(__name__)
SECRET = os.environ["WASLA_WEBHOOK_SECRET"]
def verify_wasla_signature(raw_body: bytes, header: str, secret: str, tolerance_sec: int = 300) -> bool:
parts = {}
for pair in (header or "").split(","):
key, sep, value = pair.strip().partition("=")
if sep:
parts[key] = value
t, v1 = parts.get("t", ""), parts.get("v1", "")
if not secret or not t.isdigit() or len(v1) != 64:
return False
# Reject old (replayed) or far-future timestamps.
if abs(time.time() - int(t)) > tolerance_sec:
return False
expected = hmac.new(secret.encode(), t.encode() + b"." + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, v1.lower()) # constant-time
@app.post("/webhooks/mursilat")
def mursilat_webhook():
raw = request.get_data() # bytes exactly as received
if not verify_wasla_signature(raw, request.headers.get("X-Wasla-Signature", ""), SECRET):
abort(401)
event = json.loads(raw)
# Retries reuse the same event id — store it and skip duplicates.
if event["type"] == "interactive.reply":
print(event["data"]["reference"], "->", event["data"]["reply"]["id"])
return "", 200 # answer fast (within 10s)# Recompute a signature by hand (debugging). body.json = the raw request body, byte for byte.
# Header: X-Wasla-Signature: t=1760097603,v1=<hex>
T=1760097603
printf '%s.' "$T" | cat - body.json | openssl dgst -sha256 -hmac "$WASLA_WEBHOOK_SECRET" -hex
# → compare the hex digest with v1 (and check that T is within 5 minutes of now)أغلب مشاكل «التوقيع غلط» سببها التحقق من جسم اتعاد تحويله (JSON.stringify(req.body)) بدل البايتات الخام، أو middleware بيحوّل الـ JSON قبل التحقق.
إعادة المحاولة والترتيب ومنع التكرار
| المحاولة | إمتى |
|---|---|
| 1 | إمتىفوراً |
| 2 | إمتىبعد 10 ثواني |
| 3 | إمتىبعد دقيقة |
| 4 | إمتىبعد 5 دقايق |
| 5 | إمتىبعد 15 دقيقة |
| 6 | إمتىبعد 30 دقيقة (حوالي 51 دقيقة من الأولى) |
2xx= اتسلّم. أي4xx/5xxتاني أو timeout أو خطأ اتصال بيتعاد.410 Gone= «بطّلوا تبعتوا»: الويب هوك بيتوقف فوراً وبنبلّغك. احفظ الرابط تاني من تبويب المطوّرين عشان يشتغل.X-Wasla-Deliveryثابت في كل محاولات التسليم الواحد؛ وإعادة الإرسال اليدوي ليها واحد جديد (بنفسidالحدث).- لحد 4 طلبات في نفس الوقت لكل رابط، فالأحداث ممكن توصل بترتيب مختلف. رتّب بـ
createdAtوخلّي المعالجة idempotent علىid.
حدث الاختبار
زرار إرسال حدث تجريبي (تبويب المطوّرين) بيبعت webhook.test لرابطك. استخدمه تتأكد إن رابطك بيوصله وإن كود التوقيع سليم. الزرار بيبقى مقفول — والسبب مكتوب جنبه — لو مفيش رابط محفوظ أو الويب هوك متوقف.
سجل التسليم
سجل الويب هوك في تبويب المطوّرين بيحتفظ بكل تسليم 7 أيام: الحالة، المحاولة n/6، كود HTTP، المدة، وميعاد المحاولة الجاية. افتح أي تسليم تشوف المحتوى، وهيدرز الطلب (التوقيع مخفي)، وأول 2 KB من رد سيرفرك، وكل المحاولات — وتقدر تعيد إرساله.
الأخطاء
كل الأخطاء ليها شكل واحد. اعتمد في الكود على code (ثابت)؛ أما message فنص إنجليزي للبشر وممكن يتغيّر. details فيها الخانة، أو في أخطاء «جرّب بعدين» فيها retryAt / retryAfterSeconds.
{
"statusCode": 400,
"code": "INTERACTIVE_TOO_MANY_BUTTONS",
"message": [
"buttons: at most 3 buttons are allowed"
],
"error": "INTERACTIVE_TOO_MANY_BUTTONS",
"details": {
"field": "buttons"
},
"timestamp": "2026-10-10T12:00:00.000Z",
"path": "/api/v1/whatsapp/public/interactive/send"
}| الكود | HTTP | المعنى |
|---|---|---|
INTERACTIVE_INVALID_TYPE | HTTP400 | المعنىtype لازم يكون buttons أو list أو cta_url. |
INTERACTIVE_BODY_REQUIRED | HTTP400 | المعنىbody.text مش موجود. |
INTERACTIVE_TEXT_TOO_LONG | HTTP400 | المعنىالعنوان أو النص أو التذييل أطول من الحد. |
INTERACTIVE_TITLE_TOO_LONG | HTTP400 | المعنىاسم زرار / اختيار / قسم / رابط أطول من الحد. |
INTERACTIVE_TOO_MANY_BUTTONS | HTTP400 | المعنىأكتر من 3 أزرار. |
INTERACTIVE_NO_OPTIONS | HTTP400 | المعنىمفيش أزرار / اختيارات / رابط. |
INTERACTIVE_TOO_MANY_ROWS | HTTP400 | المعنىأكتر من 10 اختيارات في القائمة. |
INTERACTIVE_TOO_MANY_SECTIONS | HTTP400 | المعنىأكتر من 10 أقسام في القائمة. |
INTERACTIVE_DUPLICATE_ID | HTTP400 | المعنىاختيارين ليهم نفس الـ id. |
INTERACTIVE_DUPLICATE_TITLE | HTTP400 | المعنىاختيارين ليهم نفس الاسم (لازم الأسماء تختلف عشان الرد المكتوب يتطابق). |
INTERACTIVE_INVALID_ID | HTTP400 | المعنىid فيه حروف غير A-Z a-z 0-9 _ . : - أو أطول من الحد. |
INTERACTIVE_INVALID_URL | HTTP400 | المعنىرابط الزرار مش رابط https:// عام. |
INTERACTIVE_INVALID_EXPIRY | HTTP400 | المعنىexpiresInMinutes برّه 5–10080. |
INTERACTIVE_INVALID_FALLBACK | HTTP400 | المعنىfallback لازم يكون numbered أو none. |
INTERACTIVE_INVALID_LOCALE | HTTP400 | المعنىlocale لازم يكون ar أو en. |
INTERACTIVE_TITLE_REQUIRED | HTTP400 | المعنىاسم زرار / اختيار / قسم / رابط مش موجود. |
INTERACTIVE_REFERENCE_TOO_LONG | HTTP400 | المعنىreference أطول من 128 حرف. |
INTERACTIVE_RESERVED_TITLE | HTTP400 | المعنىاسم اختيار هو كلمة إيقاف (زي «إلغاء»). |
INTERACTIVE_UNEXPECTED_FIELD | HTTP400 | المعنىخانة مش مناسبة للنوع ده (مثلاً buttons في قائمة). |
MESSAGE_TYPE_REQUIRED · MESSAGE_TYPE_NOT_SUPPORTED | HTTP400 | المعنىtype ناقص، أو مينفعش يتبعت من رقم مربوط (details.reason، شوف مش ممكن). |
MESSAGE_FIELD_REQUIRED · MESSAGE_FIELD_INVALID | HTTP400 | المعنىخانة مطلوبة ناقصة / قيمتها مش صحيحة (details.field وdetails.reason). |
MESSAGE_TEXT_TOO_LONG | HTTP400 | المعنىخانة نص أطول من حدها (details.limit). |
MESSAGE_UNEXPECTED_FIELD | HTTP400 | المعنىخانة تبع نوع تاني، زي caption مع audio أو مع الفيديو الدائري. |
MESSAGE_MENTIONS_GROUP_ONLY | HTTP400 | المعنىالمنشن في الجروبات بس. |
MESSAGE_MEDIA_REQUIRED | HTTP400 | المعنىمفيش media (أو file) لنوع ميديا. |
MESSAGE_MEDIA_NOT_FOUND | HTTP404 | المعنىmedia.id مش معروف (أو اترفع على رقم تاني). |
MESSAGE_MEDIA_EXPIRED | HTTP410 | المعنىالملف المرفوع عدّى عليه 24 ساعة — ارفعه تاني. |
MESSAGE_MEDIA_TOO_LARGE | HTTP413 | المعنىأكبر من حد حجم النوع (details.limitBytes). |
MESSAGE_MEDIA_TYPE_NOT_ALLOWED | HTTP415 | المعنىنوع الملف الحقيقي مش مناسب لنوع الرسالة (details.detected). |
MESSAGE_MEDIA_FETCH_FAILED | HTTP422 | المعنىمقدرناش ننزّل رابط الميديا بأمان (details.reason). |
MESSAGE_STICKER_INVALID | HTTP400 | المعنىالصورة مينفعش تتحول لملصق. |
MESSAGE_VIDEO_NOTE_TOO_LONG | HTTP400 | المعنىالفيديو الدائري أقصاه 60 ثانية. |
MESSAGE_POLL_INVALID | HTTP400 | المعنىdetails.reason: QUESTION_REQUIRED · OPTIONS_COUNT · OPTIONS_DUPLICATE · OPTION_TOO_LONG · SELECTABLE_COUNT. |
MESSAGE_CONTACT_INVALID | HTTP400 | المعنىكارت جهة اتصال مش صحيح (details.reason وdetails.index). |
MESSAGE_LOCATION_INVALID | HTTP400 | المعنىخط العرض أو الطول خارج النطاق. |
MESSAGE_EVENT_INVALID | HTTP400 | المعنىاسم المناسبة أو مواعيدها مش صحيحة (details.reason). |
MESSAGE_REPLY_TARGET_NOT_FOUND | HTTP404 | المعنىreplyTo مش رسالة في المحادثة دي. |
MESSAGE_NOT_FOUND | HTTP404 | المعنىتفاعل/تعديل/مسح: رسالة مش معروفة على الرقم ده. |
MESSAGE_REACTION_INVALID | HTTP400 | المعنىاستخدم إيموجي واحد بالظبط (أو "" عشان تشيله). |
MESSAGE_NOT_EDITABLE · MESSAGE_EDIT_WINDOW_PASSED | HTTP409 | المعنىرسايلك النصية بس، وخلال 15 دقيقة. |
MESSAGE_NOT_DELETABLE · MESSAGE_DELETE_WINDOW_PASSED | HTTP409 | المعنىالمسح عند الكل: رسايلك بس، وخلال 48 ساعة. |
INVALID_RECIPIENT | HTTP400 | المعنىرقم التليفون مش صالح. |
API_KEY_REQUIRED · API_KEY_INVALID | HTTP401 | المعنىx-api-key مش موجود أو غلط. |
API_ACCESS_NOT_IN_PLAN | HTTP403 | المعنىباقتك مش فيها وصول للـ API. |
MESSAGES_QUOTA_EXCEEDED · NO_ACTIVE_PLAN | HTTP403 | المعنىرسايل باقتك خلصت أو مفيش باقة فعّالة. |
API_SCOPE_DISABLED | HTTP403 | المعنىالمفتاح مش معاه الصلاحية المطلوبة. |
IDEMPOTENCY_KEY_REQUIRED | HTTP409 | المعنىابعت هيدر Idempotency-Key. |
IDEMPOTENCY_KEY_REUSED | HTTP409 | المعنىالمفتاح اتستخدم قبل كده مع محتوى مختلف. |
IDEMPOTENCY_IN_PROGRESS | HTTP409 | المعنىأول طلب بالمفتاح ده لسه شغال. |
SESSION_NOT_CONNECTED | HTTP400 | المعنىالرقم مش متصل — اربطه تاني من لوحة التحكم. |
API_TEMPORARY_BAN · TOO_MANY_REQUESTS | HTTP429 | المعنىتخطيت حد الطلبات أو فيه إيقاف مؤقت (شوف retryAfterSeconds). |
SAFETY_DAILY_CAP_REACHED · SAFETY_HOURLY_CAP_REACHED | HTTP429 | المعنىوصلت لحد حماية الرقم لجهات الاتصال الجديدة؛ جرّب تاني في retryAt. |
SAFETY_PACING · SAFETY_NUMBER_BUSY | HTTP429 | المعنىبتبعت بسرعة من الرقم ده؛ جرّب تاني في retryAt. |
SAFETY_CONTENT_BLOCKED | HTTP422 | المعنىقاعدة محتوى: رابط مختصر، أو رابط من رقم جديد لجهة اتصال جديدة، أو نفس النص لعدد كبير من جهات اتصال جديدة (details.reasons). |
SAFETY_SUPPRESSED | HTTP422 | المعنىالعميل طلب إيقاف الرسايل أو موجود في قائمة الاستبعاد. |
SAFETY_NUMBER_PAUSED | HTTP423 | المعنىالرقم متوقف مؤقتاً لحمايته؛ الرد على المحادثات الحديثة لسه شغال. |
SAFETY_NUMBER_BANNED | HTTP403 | المعنىواتساب قيّد الرقم ده. |
الحدود وحماية الرقم
الحدود دي موجودة عشان تقلّل خطر إن واتساب يقيّد رقمك. مفيش أداة تقدر تضمن إن الرقم عمره ما يتحظر — وأهم حاجة إنك تراسل ناس مستنية رسايلك.
حدود الطلبات (الافتراضية)
| الحد | الافتراضي |
|---|---|
| إرسال نص / رسائل تفاعلية | الافتراضي60 في الدقيقة لكل مفتاح ولكل رقم |
| إرسال ميديا | الافتراضي30 في الدقيقة لكل مفتاح ولكل رقم |
| الدفعات السريعة | الافتراضيأكتر من 3 رسايل في 5 ثواني بتدخل طابور (queued: true، تأخير 2–30 ثانية) |
| الحماية من الإساءة | الافتراضيأكتر من 8 رسايل في 5 ثواني ← إيقاف مؤقت (429): 10 دقايق، بعدها ساعة، بعدها 24 ساعة |
درع الرقم (Number Shield)
- رسالة واحدة في نفس الوقت لكل رقم. الرسايل لـجهات اتصال جديدة بتتبعت بفواصل (حوالي 8–25 ثانية مع استراحات قصيرة)؛ والرد على عملاء كتبولك في آخر 24 ساعة بيطلع في 1–3 ثواني.
- الأرقام الجديدة بتتسخّن تدريجياً: عدد جهات الاتصال الجديدة في اليوم بيزيد مع عمر الرقم (الجدول تحت). الرد على المحادثات الحديثة مش بيتحسب.
- لو وصلت لحد، الـ API بيرد بكود
SAFETY_*ومعاهretryAt— جرّب في الوقت ده، متعملش loop. - لو واتساب بيّن إنه بيحدّ الرقم، الرقم بيتوقف شوية (
SAFETY_NUMBER_PAUSED)؛ والرد على المحادثات الحديثة بيفضل شغال.
| عمر الرقم | جهات جديدة / يوم | جهات جديدة / ساعة |
|---|---|---|
| الأيام 1–3 | جهات جديدة / يوم20 | جهات جديدة / ساعة10 |
| الأيام 4–7 | جهات جديدة / يوم50 | جهات جديدة / ساعة15 |
| الأسبوع التاني | جهات جديدة / يوم100 | جهات جديدة / ساعة25 |
| الأسابيع 3–4 | جهات جديدة / يوم200 | جهات جديدة / ساعة40 |
| أكتر من 30 يوم (رقم سليم) | جهات جديدة / يوم400 | جهات جديدة / ساعة60 |
قواعد الروابط والمحتوى
- الروابط المختصرة (bit.ly …) ممنوعة — استخدم الدومين بتاعك.
- الأرقام اللي عمرها أقل من 7 أيام مش بتبعت روابط لجهات اتصال جديدة.
- نفس النص لأكتر من 50 جهة اتصال جديدة في 24 ساعة بيتمنع — خصّص الرسالة لكل عميل.
- العملاء اللي طلبوا إيقاف الرسايل مش بيوصلهم حاجة (
SAFETY_SUPPRESSED).
نصيحة: الأزرار طريقة كويسة تبدأ بيها محادثة — لما العميل يرد، المحادثة بتبقى «حديثة» والردود بعد كده أسرع وخطرها أقل.
إرسال رسالة نصية (قديم)
endpoint قديم (legacy)
لسه مدعوم ومتغيّرش. للربط الجديد: POST /whatsapp/public/messages مع "type": "text" (النص).
POST /whatsapp/public/message/send — صلاحية send.
| الخانة | النوع | ملاحظات |
|---|---|---|
phoneNumber | النوعstring | ملاحظاتمطلوبة. الرقم بالصيغة الدولية من غير +. |
content | النوعstring | ملاحظاتمطلوبة. نص الرسالة. |
reference | النوعstring | ملاحظاتاختيارية، لحد 128. معرّف من عندك بيرجعلك في message.status. |
curl -X POST "https://mursilat.com/api/v1/whatsapp/public/message/send" \
-H "Content-Type: application/json" \
-H "x-api-key: $WASLA_API_KEY" \
-H "Idempotency-Key: order-1042-shipped" \
-d '{
"phoneNumber": "201001234567",
"content": "أهلاً أ. منى، طلبك رقم 1042 اتشحن وهيوصلك بكرة.",
"reference": "order-1042"
}'// Node.js 18+ (built-in fetch)
const res = await fetch('https://mursilat.com/api/v1/whatsapp/public/message/send', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': process.env.WASLA_API_KEY,
'Idempotency-Key': 'order-1042-shipped',
},
body: JSON.stringify({
"phoneNumber": "201001234567",
"content": "أهلاً أ. منى، طلبك رقم 1042 اتشحن وهيوصلك بكرة.",
"reference": "order-1042"
}),
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.message}`);
console.log(data);<?php
$payload = [
'phoneNumber' => '201001234567',
'content' => 'أهلاً أ. منى، طلبك رقم 1042 اتشحن وهيوصلك بكرة.',
'reference' => 'order-1042',
];
$ch = curl_init('https://mursilat.com/api/v1/whatsapp/public/message/send');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'x-api-key: ' . getenv('WASLA_API_KEY'),
'Idempotency-Key: order-1042-shipped',
],
CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_UNICODE),
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$data = json_decode($response, true);
if ($status >= 400) {
throw new RuntimeException($data['code'] . ': ' . json_encode($data['message']));
}
print_r($data);import os
import requests
payload = {
"phoneNumber": "201001234567",
"content": "أهلاً أ. منى، طلبك رقم 1042 اتشحن وهيوصلك بكرة.",
"reference": "order-1042",
}
res = requests.post(
"https://mursilat.com/api/v1/whatsapp/public/message/send",
headers={
"x-api-key": os.environ["WASLA_API_KEY"],
"Idempotency-Key": "order-1042-shipped",
},
json=payload,
timeout=30,
)
data = res.json()
if not res.ok:
raise RuntimeError(f"{data.get('code')}: {data.get('message')}")
print(data){
"success": true,
"message": "Message sent successfully",
"sessionId": "cm1abc2def3ghi4jkl5mno6p",
"phoneNumber": "201001234567",
"messageId": "3EB0A1B2C3D4E5F60718"
}لو بعتّ رسايل كتير ورا بعض، الرسالة ممكن تدخل طابور بدل ما تتبعت فوراً: الرد ساعتها فيه queued: true وestimatedDelayMs. النتيجة النهائية بتوصلك في ويب هوك message.status.
إرسال ميديا (قديم)
endpoint قديم (legacy)
لسه مدعوم ومتغيّرش. للربط الجديد: POST /whatsapp/public/messages مع نوع ميديا (الميديا)، واللي بيضيف الملصقات والرسايل الصوتية والعرض مرة واحدة والـ GIF والفيديو الدائري.
POST /whatsapp/public/media/send — صلاحية media. ابعت JSON فيه mediaUrl عام، أو multipart/form-data فيه file.
| الخانة | النوع | ملاحظات |
|---|---|---|
phoneNumber | النوعstring | ملاحظاتمطلوبة. |
mediaType | النوعstring | ملاحظاتimage · video · document · audio |
mediaUrl | النوعstring | ملاحظاترابط https:// عام (أو ارفع file بداله). |
caption | النوعstring | ملاحظاتاختيارية. |
filename | النوعstring | ملاحظاتاختيارية، للمستندات. |
voiceNote | النوعboolean | ملاحظاتاختيارية. يبعت الصوت كرسالة صوتية. |
reference | النوعstring | ملاحظاتاختيارية، لحد 128. |
curl -X POST "https://mursilat.com/api/v1/whatsapp/public/media/send" \
-H "Content-Type: application/json" \
-H "x-api-key: $WASLA_API_KEY" \
-H "Idempotency-Key: order-1042-invoice" \
-d '{
"phoneNumber": "201001234567",
"mediaType": "document",
"mediaUrl": "https://example.com/invoices/1042.pdf",
"filename": "invoice-1042.pdf",
"caption": "فاتورة طلبك رقم 1042",
"reference": "order-1042"
}'// Node.js 18+ (built-in fetch)
const res = await fetch('https://mursilat.com/api/v1/whatsapp/public/media/send', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': process.env.WASLA_API_KEY,
'Idempotency-Key': 'order-1042-invoice',
},
body: JSON.stringify({
"phoneNumber": "201001234567",
"mediaType": "document",
"mediaUrl": "https://example.com/invoices/1042.pdf",
"filename": "invoice-1042.pdf",
"caption": "فاتورة طلبك رقم 1042",
"reference": "order-1042"
}),
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.message}`);
console.log(data);<?php
$payload = [
'phoneNumber' => '201001234567',
'mediaType' => 'document',
'mediaUrl' => 'https://example.com/invoices/1042.pdf',
'filename' => 'invoice-1042.pdf',
'caption' => 'فاتورة طلبك رقم 1042',
'reference' => 'order-1042',
];
$ch = curl_init('https://mursilat.com/api/v1/whatsapp/public/media/send');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'x-api-key: ' . getenv('WASLA_API_KEY'),
'Idempotency-Key: order-1042-invoice',
],
CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_UNICODE),
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$data = json_decode($response, true);
if ($status >= 400) {
throw new RuntimeException($data['code'] . ': ' . json_encode($data['message']));
}
print_r($data);import os
import requests
payload = {
"phoneNumber": "201001234567",
"mediaType": "document",
"mediaUrl": "https://example.com/invoices/1042.pdf",
"filename": "invoice-1042.pdf",
"caption": "فاتورة طلبك رقم 1042",
"reference": "order-1042",
}
res = requests.post(
"https://mursilat.com/api/v1/whatsapp/public/media/send",
headers={
"x-api-key": os.environ["WASLA_API_KEY"],
"Idempotency-Key": "order-1042-invoice",
},
json=payload,
timeout=30,
)
data = res.json()
if not res.ok:
raise RuntimeError(f"{data.get('code')}: {data.get('message')}")
print(data)curl -X POST "https://mursilat.com/api/v1/whatsapp/public/media/send" \
-H "x-api-key: $WASLA_API_KEY" \
-H "Idempotency-Key: invoice-1042" \
-F "phoneNumber=201001234567" \
-F "mediaType=document" \
-F "filename=invoice-1042.pdf" \
-F "file=@./invoice-1042.pdf"روابط الميديا لازم تكون عامة. العناوين الداخلية والخاصة (localhost و10.x و192.168.x …) مرفوضة.
الانتقال من v1 إلى v2
الإصدار 1 من الويب هوك قديم ومُهمَل (deprecated) وهيتشال بعدين. الأرقام الموجودة بتفضل على v1 لحد ما تغيّرها بنفسك؛ والأرقام الجديدة بتبدأ على v2. هيدر التوقيع واحد في الاتنين.
| v1 (قديم) | v2 | |
|---|---|---|
| الأحداث | v1 (قديم)message.received بس | v2كل الأحداث، وانت تختار |
| المحتوى | v1 (قديم)الشكل القديم برسالة واتساب الخام (rawMessage) | v2غلاف ثابت + data محددة |
| User-Agent | v1 (قديم)Wasla-Webhooks/1.0 | v2Wasla-Webhooks/2 |
| هيدر الإصدار | v1 (قديم)— | v2X-Wasla-Webhook-Version: 2 |
| إعادة المحاولة والسجل | v1 (قديم)— | v26 محاولات على حوالي 51 دقيقة، سجل 7 أيام، إعادة إرسال |
| التوقيع | v1 (قديم)X-Wasla-Signature (نفسه) | v2X-Wasla-Signature (نفسه) |
- خلّي المستقبل عندك يقبل الاتنين: افصل حسب هيدر
X-Wasla-Webhook-Version(لو مش موجود يبقى v1). - اقرا
message.receivedمنdata(fromوtypeوtextوmedia…) بدل الرسالة الخام. - من تبويب المطوّرين غيّر لـ الإصدار 2، اختار الأحداث، واضغط إرسال حدث تجريبي.
- تابع سجل الويب هوك يوم، وبعدها شيل كود v1.
أسئلة شائعة
محتاج توثيق Meta للبيزنس أو Cloud API؟
لأ. مرسلات بتشتغل برقمك أنت المربوط بالـ QR (زي واتساب ويب). وده برضه السبب إن الأزرار ممكن متظهرش على كل الموبايلات — والبديل المرقّم بيغطي ده.
العملاء هيشوفوا الأزرار دايماً؟
مش دايماً. بعض الموبايلات بتعرض الرسايل التفاعلية كنص عادي. ومع البديل المرقّم (الافتراضي) العميل يقدر يرد بكتابة الرقم أو اسم الاختيار، ويوصلك نفس interactive.reply في الحالتين.
لو العميل كتب حاجة تانية؟
بتوصلك كرسالة عادية message.received. والتفاعل بيفضل مفتوح لحد ما يترد عليه أو مدته تخلص.
إزاي أمنع إن الحدث يتنفّذ مرتين؟
احفظ id الحدث وتجاهل اللي شفته قبل كده — إعادة المحاولة وإعادة الإرسال اليدوي بيستخدموا نفس الـ id. وفي الإرسال ابعت دايماً Idempotency-Key.
الويب هوك بتاعي اتوقف. ليه؟
رابطك رد بـ 410 Gone. صلّح الـ endpoint وبعدين احفظ الرابط تاني من تبويب المطوّرين — بيشتغل على طول.
مرسلات بتضمن إن رقمي مش هيتحظر؟
مفيش أداة تقدر تضمن ده. درع الرقم بيقلّل الخطر بالفواصل والتسخين وقواعد المحتوى — وراسل ناس مستنية تسمع منك.
أقدر أبعت أنهي أنواع رسايل؟
نص، صور، فيديو (وكمان كـ GIF أو فيديو دائري)، ملفات صوت، رسايل صوتية، مستندات، ملصقات، مواقع، جهات اتصال، استطلاعات، مناسبات (تجريبي)، أزرار رد، قوائم، وزرار رابط — كلهم من POST /whatsapp/public/messages. الموقع المباشر والمدفوعات ورسايل الكتالوج والطلبات والحالات والمكالمات والقوالب مش ممكنة من رقم مربوط.
كل الموبايلات هتعرض كل الأنواع؟
النص والميديا والملصقات والمواقع وجهات الاتصال والاستطلاعات بتظهر على كل إصدارات واتساب الحديثة. الأزرار والقوائم ممكن تظهر كنص عادي على بعض الموبايلات (والبديل المرقّم بيغطي ده)؛ والعرض مرة واحدة والفيديو الدائري محتاجين تطبيق حديث؛ والمناسبات غالباً بتظهر في الجروبات بس.
روابط الميديا صالحة لحد إمتى؟
24 ساعة. بعدها حمّل بمفتاح الـ API (صلاحية media).
أجرّب إزاي من غير ما أزعج العملاء؟
استخدم إرسال حدث تجريبي للويب هوك، وابعت رسايل الـ API لرقم موبايلك أنت.