إرسال رسالة واتساب بلغة PHP
ترسل هذه الصفحة رسالة واتساب من PHP بطلب HTTP واحد، ثم تستقبل الرد. وهي تستخدم إضافة cURL الموجودة في PHP نفسها، فلا تحتاج إلى أي حزمة من Composer.
ما الذي تحتاجه
- حساب على Threadwire.
- رقم مربوط. في لوحة التحكم، اضغط «إضافة رقم» وامسح رمز QR بواتساب على الهاتف (الإعدادات ← الأجهزة المرتبطة). ثم انسخ معرّف الرقم من صفحة الرقم.
- مفتاح API. أنشئه من للمطورين ← مفاتيح API ← مفتاح جديد (للمسؤولين فقط).
- PHP مع إضافة cURL. يقرأ المثال المفتاح من متغير بيئة اسمه
THREADWIRE_KEY.
«الرقم» في Threadwire هو رقم واتساب واحد مربوط به. يُربط كجهاز، بالطريقة نفسها التي يُربط بها WhatsApp Web، فهذه ليست الواجهة الرسمية من Meta. صفحة WhatsApp API بدون موافقة Meta تشرح معنى ذلك.
أرسل الرسالة
أرسل الرسالة الأولى إلى نفسك. اكتب «مرحبًا» إلى رقمك المربوط من واتساب على هاتف آخر لك، ثم أرسل إلى ذلك الهاتف. لديك هاتف واحد فقط؟ ضع رقم الهاتف المربوط نفسه في to.
// PHP
$request = curl_init('https://threadwire.net/api/v1/messages');
curl_setopt_array($request, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer '.getenv('THREADWIRE_KEY'), 'Content-Type: application/json', 'Accept: application/json'],
CURLOPT_POSTFIELDS => json_encode(['instance_id' => 'YOUR_INSTANCE_ID', 'to' => 'YOUR_PHONE', 'text' => 'Hello from Threadwire']),
]);
$answer = json_decode(curl_exec($request), true);
if ($answer === null || curl_getinfo($request, CURLINFO_HTTP_CODE) >= 400) {
throw new RuntimeException($answer['message'] ?? 'Threadwire refused the request');
}
$message = $answer['data'];
echo $message['id'].' '.$message['status'].PHP_EOL;
Authorization: مفتاح API الخاص بك كرمز Bearer، ويُرسل مع كل طلب.instance_id: معرّف الرقم الذي نسخته.to: رقم ذلك الهاتف بالصيغة الدولية، مثل201012345678: رمز الدولة، بلا حاجة إلى+، وبلا صفر في البداية.text: نص الرسالة، حتى 4,096 حرفًا.
اقرأ رد الـ API
يصل الرد فورًا بصيغة JSON، والنتيجة تحت data:
202 {"data": {"id": "01K…", "status": "queued", "phone": "201012345678", "body": "Hello from Threadwire", "…": "…"}}
id: احتفظ به.GET /v1/messages/{id}يعيد الرسالة بحالتها في تلك اللحظة.status: القيمةqueuedتعني أن الرسالة قُبلت وهي في الانتظار حتى يأتي دورها. ثم تُرسل كما يرسلها الإنسان.phoneوbody: من تُرسل إليه الرسالة، ونصها.
بعدها تنتقل status إلى sent ثم delivered ثم read، أو إلى failed مع السبب في error.
أنت كتبت أولًا، فهذه الرسالة رد، وتصل خلال ثوانٍ. أما الرسالة الأولى إلى من لم يراسل رقمك من قبل فهي أبطأ الأنواع. هذه الرسائل تُرسل متباعدة، ولها حد يومي، ولا تُرسل ليلًا، فدع الناس يكتبون أولًا. دليل كيف يحمي الإرسال رقمك فيه كل قاعدة بأرقامها.
استقبل الرد على رسالتك
من للمطورين ← الويب هوك (للمسؤولين فقط)، حدّد رابط الاستقبال لديك: عنوان عام يبدأ بـ https://. يُرسل إليه كل رد وكل تغيّر في الحالة بصيغة JSON:
{"id": "evt_01K…", "type": "message.received", "created_at": "2026-10-01T10:00:00+00:00",
"data": {"id": "01K…", "instance_id": "01K…", "phone": "201012345678", "body": "Hi!", "…": "…"}}
يصل الرد كحدث message.received، ويصل تحديث التسليم كحدث message.status.
كل حدث موقّع وفق مواصفة Standard Webhooks. يبدأ سر التوقيع بـ whsec_، ويظهر عند حفظ الرابط. هذه الدالة، من دليل الويب هوك، تتحقق من التوقيع:
// PHP: $id, $timestamp and $signatures are the webhook-id, webhook-timestamp and
// webhook-signature headers ($_SERVER['HTTP_WEBHOOK_ID'] ?? '' and so on); $body
// is file_get_contents('php://input').
function threadwireEventIsGenuine(string $id, string $timestamp, string $signatures, string $body, string $secret): bool
{
// Reject old or future timestamps: a replayed request is not a new event.
if (abs(time() - (int) $timestamp) > 300) {
return false;
}
$key = base64_decode(substr($secret, strlen('whsec_')));
$expected = 'v1,'.base64_encode(hash_hmac('sha256', "{$id}.{$timestamp}.{$body}", $key, true));
foreach (explode(' ', $signatures) as $signature) {
if (hash_equals($expected, $signature)) {
return true;
}
}
return false;
}
أعطها جسم الطلب الخام كما وصل تمامًا، قبل أي تحليل لـ JSON:
// The endpoint: check the raw body first, then read the event.
$body = file_get_contents('php://input');
// No secret set means no event can be trusted: refuse them all.
$secret = getenv('THREADWIRE_WEBHOOK_SECRET');
$genuine = $secret && threadwireEventIsGenuine(
$_SERVER['HTTP_WEBHOOK_ID'] ?? '',
$_SERVER['HTTP_WEBHOOK_TIMESTAMP'] ?? '',
$_SERVER['HTTP_WEBHOOK_SIGNATURE'] ?? '',
$body,
$secret,
);
if (! $genuine) {
http_response_code(401);
exit;
}
http_response_code(200);
$event = json_decode($body, true);
if ($event['type'] === 'message.received') {
error_log($event['data']['phone'].': '.$event['data']['body']);
}
ردّ بأي رمز 2xx خلال 15 ثانية. الحدث الذي يُرسل مرة أخرى يحتفظ بـ webhook-id، فاستخدم هذا المعرّف لتجاهل المكرر. ليس لديك رابط عام بعد؟ اجلب الأحداث نفسها عبر GET /v1/events.
من المفيد أن تعرف
- إعادة المحاولة آمنة مع ترويسة
Idempotency-Key. أضف سطرًا مثل'Idempotency-Key: order-1042-shipped'إلىCURLOPT_HTTPHEADER. إعادة المحاولة بالمفتاح نفسه تعيد الرسالة الأولى بدلًا من إرسال ثانية. - للخطأ
code. الطلب المرفوض يحملmessageبكلمات بسيطة وcodeثابتًا، مثلvalidation_failed. اجعل برنامجك يتصرف حسب$answer['code']، وسجّل نص الخطأ. - أعد محاولة ما يمكن أن ينجح فقط. أعد محاولة
429و502و503، بعدRetry-Afterإن وُجد. أصلح المفتاح أو المعرّف أو الطلب قبل إعادة محاولة401أو403أو404أو422. راجع ما الذي يعيد نظامك محاولته. - الرسالة التي لا يمكن إرسالها ليست خطأ. يُرد عليها بـ
202، ثم تصبحfailedلاحقًا، والسبب فيerror. لا تُعد إرسال رسالة فشلت في حلقة تكرار أبدًا.
التالي
- إرسال الرسائل: الملفات والرسائل الصوتية والاستطلاعات وغيرها، كحقول في الطلب نفسه.
- الويب هوك والأحداث: كل الأحداث، وإعادة المحاولة، والجلب.
- مرجع الـ API: كل نقاط الاتصال، والأخطاء.
- الأدوات وأمثلة الكود: مجموعة Postman، وهذا الطلب بلغات أخرى.