واجهة WidersApps البرمجية
واجهة REST لبيانات WidersApps — جهات الاتصال والمحادثات والطلبات والحجوزات. JSON دخولاً وخروجاً، بغلاف موحّد واحد، ومؤمّنة بمفتاح Bearer تنشئه بنفسك.
العنوان الأساسي
كل نقطة نهاية تقع تحت مسار أساسي واحد يحمل رقم الإصدار. أرسِل كل الطلبات عبر HTTPS.
https://console.widers.net/api/v1
الموارد
الموارد الأساسية والصلاحية التي تحتاجها كل قدرة. هذه مجموعة مختارة للبدء — والمرجع التفاعلي يسرد كل نقطة نهاية تكشفها الواجهة، بتفاصيل الطلب والاستجابة لكلٍّ منها.
| المورد | ما يمكنك فعله | النطاق |
|---|---|---|
| جهات الاتصال | عرض القائمة · عرض واحد · إنشاء · تعديل · حذف | contacts.view · contacts.manage |
| المنتجات | عرض القائمة · عرض واحد · إنشاء وتحديث الحقول الأساسية (الاسم، السعر، الحالة، المخزون — لمزامنة الكتالوج والمخزون) | commerce.view · commerce.manage |
| الطلبات | عرض القائمة · عرض واحد · إنشاء · تقديم الحالة | commerce.view · commerce.manage |
| الرسائل | عرض القائمة · عرض واحد · إرسال | inbox.access |
| المحادثات | عرض القائمة · عرض واحد · إسناد · تحويل · إغلاق · إعادة فتح · تعليم مقروء · كتم · أرشفة · متابعة · ملاحظة · وسم | inbox.access |
| الحجوزات | CRUD كامل + الأوقات المتاحة | calendar.view · calendar.manage |
| الوسوم | عرض الوسوم؛ وإنشاء/إعادة تسمية/حذف وسومك (وسوم النظام للقراءة فقط) | inbox.access · tags.manage |
| الوسائط | رفع ملف والحصول على رابط لإرفاقه عند إرسال رسالة | inbox.access |
| الردود السريعة | عرض وإنشاء الردود السريعة المحفوظة | quick-replies.manage |
| الحملات | عرض الحملات وإحصاءاتها؛ وإيقاف/استئناف/إلغاء حملة جارية | campaigns.view · campaigns.manage |
حالات تسليم الرسائل وقراءتها وفشلها تصل عبر الويب هوك (message.delivered · message.read · message.failed) لا كنقاط نهاية تُستعلَم. وتغيّرات دورة حياة المحادثة (إنشاء · إسناد · إغلاق · وسم · …) ويب هوك أيضاً — اشترك فيها من الإعدادات ← الويب هوك.
إنشاء مفتاح API
أنشئ مفتاحاً من لوحة التحكّم عبر: الإعدادات ← المطوّرون ← مفاتيح API (للمالك فقط). عند الإنشاء تختار:
- اسماً — لتتعرّف عليه وتُبطله لاحقاً.
- النطاقات — لا يمكنك منح إلا النطاقات التي تملكها أنت.
- مباشر أم اختبار — مفتاح الاختبار (Sandbox) لا يمسّ بيانات حقيقية أبداً.
- تاريخ انتهاء اختياري.
يُعرض المفتاح كاملاً مرّة واحدة عند الإنشاء. انسخه حينها واحفظه بأمان — وإن فقدته فأبطِله وأنشئ غيره.
المصادقة
صادِق كل طلب بمفتاح Bearer في ترويسة Authorization. الصق المفتاح تماماً كما عُرض لك، ببادئته:
Authorization: Bearer wa_live_<id>|<token>
مفتاح الاختبار يحمل البادئة wa_test_ بدلاً من ذلك:
Authorization: Bearer wa_test_<id>|<token>
كل مستخدم ينتمي لشركة واحدة فقط، فالمفتاح يحدّد الحساب الذي يعمل عليه — لا وسيط للمستأجر تمرّره.
النطاقات (Scopes)
النطاقات هي أسماء صلاحيات المنصّة نفسها، فلا يقدر المفتاح على أكثر ممّا يقدر عليه مستخدم اللوحة صاحب الصلاحيات ذاتها. النطاقات المتاحة:
| النطاق | الوصف |
|---|---|
contacts.view |
قراءة جهات الاتصال |
contacts.manage |
إدارة جهات الاتصال |
inbox.access |
الرسائل (قراءة وإرسال) |
tags.manage |
إدارة الوسوم |
quick-replies.manage |
إدارة الردود السريعة |
commerce.view |
قراءة الطلبات والمنتجات |
commerce.manage |
إدارة الطلبات والمنتجات |
campaigns.view |
قراءة الحملات |
campaigns.manage |
التحكم بالحملات (إيقاف مؤقت، استئناف، إلغاء) |
calendar.view |
قراءة الحجوزات |
calendar.manage |
إدارة الحجوزات |
يُصدَر المفتاح بالنطاقات التي تحدّدها بالضبط — بلا تعميم، وبما لا يتجاوز ما تملكه أنت.
غلاف الاستجابة
الاستجابة الناجحة تغلّف البيانات داخل مفتاح data، مع كتلة meta اختيارية (تستخدمها القوائم المصفّحة):
{
"data": { ... },
"meta": { "next_cursor": null, "has_more": false }
}
يُرجِع الفشل كائن خطأ موحّداً واحداً:
{
"error": {
"type": "invalid_request_error",
"code": "not_found",
"message": "The requested resource was not found.",
"param": null,
"request_id": "req_01J..."
}
}
رموز الأخطاء
سلسلة code عقد ثابت مقروء آلياً (مستقلّ عن اللغة، لا يُعاد تسميته) لتفرّع منطقك عليه؛ أمّا الرسالة البشرية message فتُترجَم على حدة. أمّا type فهو عائلة الخطأ العامّة.
| الرمز | النوع | HTTP |
|---|---|---|
bad_request |
invalid_request_error |
400 |
validation_failed |
invalid_request_error |
422 |
unauthenticated |
authentication_error |
401 |
forbidden |
authorization_error |
403 |
not_found |
invalid_request_error |
404 |
method_not_allowed |
invalid_request_error |
405 |
not_acceptable |
invalid_request_error |
406 |
conflict |
idempotency_error |
409 |
idempotency_conflict |
idempotency_error |
409 |
payload_too_large |
invalid_request_error |
413 |
rate_limited |
rate_limit_error |
429 |
server_error |
api_error |
500 |
كل خطأ يردّد request_id (نفس قيمة ترويسة X-Request-Id) — اذكره عند الإبلاغ عن مشكلة.
حدود المعدّل
تسمح الواجهة بـ120 طلباً في الدقيقة لكل مفتاح (الطلبات غير المصادَقة تُحدّ بعنوان IP). كل استجابة تحمل الترويسات القياسية:
X-RateLimit-Limit,X-RateLimit-Remaining,X-RateLimit-Reset,Retry-After- تجاوز الحدّ يُرجِع 429 برمز rate_limited؛ تمهّل حتى Retry-After.
عدم التكرار (Idempotency)
أرسِل ترويسة Idempotency-Key مع أي POST أو PUT أو PATCH أو DELETE ليُطبَّق الطلب المُعاد مرّة واحدة على الأكثر. استخدم UUID جديداً لكل عملية منطقية:
Idempotency-Key: 6f9e0b2c-1a3d-4e5f-8a7b-9c0d1e2f3a4b
- إعادة نفس المفتاح والجسم تُرجِع الاستجابة المخزّنة مع Idempotency-Replayed: true.
- تُحفظ المفاتيح لمدّة 24 ساعة.
- إعادة استخدام مفتاح بجسم مختلف، أو والطلب الأول ما زال جارياً، تُرجِع 409 idempotency_conflict.
التصفّح (Pagination)
القوائم تستخدم تصفّحاً مبهماً قائماً على المؤشّر (keyset). مرّر limit (افتراضياً 25، وأقصاه 100) ومؤشّراً cursor مبهماً:
GET https://console.widers.net/api/v1/contacts?limit=25&cursor=<opaque>
تحمل meta في الاستجابة مؤشّر الصفحة التالية وما إن كانت هناك صفوف أخرى. عندما يكون has_more قيمته false يكون next_cursor فارغاً null:
"meta": { "next_cursor": "eyJ...", "has_more": true }
المؤشّر التالف يُرجِع 422 — لا مسحاً كاملاً صامتاً. عامِل المؤشّر ككتلة مبهمة وأعده كما هو حرفياً.
وضع الاختبار (Sandbox)
وضع الاختبار يحمله المفتاح (مفتاح اختبار)، فلا تقلبه أثناء الطلب. طلب الـSandbox لا يمسّ عملاء أو طلبات أو أموالاً حقيقية — بل يصطنع الاستجابات:
- كل استجابة تُعلن وضعها:
X-Widers-Mode: live/X-Widers-Mode: test - مفتاح وضع الاختبار يتوقّف قبل أي أثر فعلي، فلا يُطلق أي Webhook — كل Webhook يصلك يكون من نشاط حقيقي.
- الخدمات ذات الأثر الجانبي تصطنع نداءاتها الخارجية في وضع الاختبار.
الأحداث
تتلقّى نقطتك حدثاً كلّما وقع شيءٌ في الحساب. ولكلّ حدثٍ اسمٌ ثابتٌ بالنقطة — عقدٌ عامّ: مُؤصدَرٌ ولا يُعاد تسميته أبداً. وينتمي كلّ حدثٍ إلى نطاق، فلا يشترك المفتاح في حدثٍ ولا يتلقّاه إلّا إن سمحت به نطاقاته. اشترِك من الإعدادات ← Webhooks.
| الحدث | ما هو | المجموعة | النطاق |
|---|---|---|---|
flow.completed |
اكتمل التدفق | الأتمتة | — |
flow.handoff_requested |
طلب تحويل لموظف | الأتمتة | — |
conversation.csat_received |
وصل تقييم رضا | الأتمتة | — |
campaign.completed |
اكتملت الحملة | الحملات | campaigns.view |
campaign.failed |
فشلت الحملة | الحملات | campaigns.view |
order.created |
طلب جديد | الطلبات | commerce.view |
order.paid |
دفع الطلب | الطلبات | commerce.view |
order.shipped |
شحن الطلب | الطلبات | commerce.view |
order.ready_for_pickup |
الطلب جاهز للاستلام | الطلبات | commerce.view |
order.delivered |
تسليم الطلب | الطلبات | commerce.view |
order.cancelled |
إلغاء الطلب | الطلبات | commerce.view |
order.refunded |
استرداد الطلب | الطلبات | commerce.view |
cart.abandoned |
سلة متروكة | الطلبات | commerce.view |
product.created |
منتج جديد | الطلبات | commerce.view |
product.updated |
تحديث منتج | الطلبات | commerce.view |
invoice.paid |
دُفعت الفاتورة | الفوترة | — |
booking.created |
إنشاء حجز | ويب هوك الحجوزات | calendar.view |
booking.updated |
تعديل حجز | ويب هوك الحجوزات | calendar.view |
booking.cancelled |
إلغاء حجز | ويب هوك الحجوزات | calendar.view |
booking.confirmed |
تأكيد حجز | ويب هوك الحجوزات | calendar.view |
message.received |
وصول رسالة | ويب هوك الرسائل | inbox.access |
message.sent |
إرسال رسالة | ويب هوك الرسائل | inbox.access |
conversation.created |
إنشاء محادثة | ويب هوك الرسائل | inbox.access |
conversation.assigned |
إسناد محادثة | ويب هوك الرسائل | inbox.access |
conversation.transferred |
تحويل محادثة | ويب هوك الرسائل | inbox.access |
conversation.closed |
إغلاق محادثة | ويب هوك الرسائل | inbox.access |
conversation.reopened |
إعادة فتح محادثة | ويب هوك الرسائل | inbox.access |
conversation.muted |
كتم محادثة | ويب هوك الرسائل | inbox.access |
conversation.unmuted |
إلغاء كتم محادثة | ويب هوك الرسائل | inbox.access |
conversation.archived |
أرشفة محادثة | ويب هوك الرسائل | inbox.access |
conversation.unarchived |
استعادة محادثة | ويب هوك الرسائل | inbox.access |
conversation.tagged |
إضافة وسم لمحادثة | ويب هوك الرسائل | inbox.access |
conversation.untagged |
إزالة وسم من محادثة | ويب هوك الرسائل | inbox.access |
message.delivered |
تسليم رسالة | ويب هوك الرسائل | inbox.access |
message.read |
قراءة رسالة | ويب هوك الرسائل | inbox.access |
message.failed |
فشل رسالة | ويب هوك الرسائل | inbox.access |
conversation.unassigned |
إلغاء إسناد محادثة | ويب هوك الرسائل | inbox.access |
contact.created |
إنشاء جهة اتصال | ويب هوك جهات الاتصال | contacts.view |
contact.updated |
تعديل جهة اتصال | ويب هوك جهات الاتصال | contacts.view |
contact.blocked |
حظر جهة اتصال | ويب هوك جهات الاتصال | contacts.view |
contact.unblocked |
رفع الحظر عن جهة اتصال | ويب هوك جهات الاتصال | contacts.view |
contact.tagged |
إضافة وسم لجهة اتصال | ويب هوك جهات الاتصال | contacts.view |
contact.untagged |
إزالة وسم من جهة اتصال | ويب هوك جهات الاتصال | contacts.view |
contact.deleted |
حذف جهة اتصال | ويب هوك جهات الاتصال | contacts.view |
يَسرُد GET /events الأحداثَ التي يجوز لمفتاحك الاشتراك فيها؛ ويُرجِع GET /events/{event}/samples أحدثَ مغلّفات تسليمٍ حقيقيّة لحدثٍ واحد، فتبني وتختبر على شكله الدقيق.
توقيع الـWebhook
كل webhook نرسله إلى نقطتك موقّع بـHMAC مختوم بالوقت في ترويسة X-Widers-Signature، لتتحقّق من الأصالة والحداثة معاً:
X-Widers-Signature: t=1706342400,v1=<hmac_sha256>
- تحقّق مقابل جسم الطلب الخام غير المحلَّل — احسب hmac_sha256(secret, "<t>.<body>").
- ارفض أي طلب تبعد طابعه الزمني أكثر من 300 ثانية عن الآن (دفاعاً عن إعادة التشغيل).
- قارِن بدالّة ثابتة الزمن (hash_equals) تفادياً لهجمات التوقيت.
- أثناء تدوير السرّ تحمل الترويسة أكثر من v1 (السرّ الجديد والسابق). اقبل الطلب إن طابق أيّ v1 — والمُتحقّق أدناه يفعل ذلك.
مُتحقّق جاهز للنسخ (PHP):
<?php
function widers_verify(string $secret, string $rawBody, string $header): bool
{
// Parse "t=<unix>,v1=<hex>[,v1=<hex>…]" — there may be MORE THAN ONE v1 while a secret is rotating.
$t = 0;
$signatures = [];
foreach (explode(',', $header) as $part) {
[$key, $value] = array_pad(explode('=', trim($part), 2), 2, '');
if ($key === 't') {
$t = (int) $value;
} elseif ($key === 'v1') {
$signatures[] = $value;
}
}
if ($t <= 0 || abs(time() - $t) > 300) {
return false; // missing/stale timestamp — outside the 300s replay window
}
$expected = hash_hmac('sha256', $t . '.' . $rawBody, $secret);
foreach ($signatures as $signature) { // accept ANY matching v1, so rotation never drops a delivery
if (hash_equals($expected, $signature)) {
return true;
}
}
return false;
}
الفحص نفسه بلغة Node.js:
const crypto = require('crypto');
function widersVerify(secret, rawBody, header) {
let t = 0;
const sigs = [];
for (const part of header.split(',')) {
const [k, v = ''] = part.trim().split('=');
if (k === 't') t = parseInt(v, 10);
else if (k === 'v1') sigs.push(v);
}
if (!t || Math.abs(Date.now() / 1000 - t) > 300) return false;
const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
return sigs.some((s) => s.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(s), Buffer.from(expected)));
}
وبلغة Python:
import hashlib, hmac, time
def widers_verify(secret: str, raw_body: str, header: str) -> bool:
t, sigs = 0, []
for part in header.split(","):
k, _, v = part.strip().partition("=")
if k == "t":
t = int(v or 0)
elif k == "v1":
sigs.append(v)
if t <= 0 or abs(time.time() - t) > 300:
return False
expected = hmac.new(secret.encode(), f"{t}.{raw_body}".encode(), hashlib.sha256).hexdigest()
return any(hmac.compare_digest(expected, s) for s in sigs)
تسليم الـWebhook
يُسلَّم كلّ حدثٍ عبر POST بهذا المغلّف JSON. فرّع على type، وراعِ api_version لتبقى متوافقاً حين يكتسب المغلّف حقولاً مستقبلاً:
POST https://your-endpoint.example/webhooks
Content-Type: application/json
X-Widers-Signature: t=1706342400,v1=<hmac_sha256>
X-Widers-Delivery: evt_01J...
{
"id": "evt_01J...",
"type": "order.paid",
"api_version": "v1",
"created_at": "2026-10-30T12:00:00+00:00",
"data": { ... }
}
- التسليم «مرّةً على الأقلّ» — قد يصل الحدث نفسه أكثر من مرّة. والمعرّف id في المغلّف (ويتكرّر في ترويسة X-Widers-Delivery) ثابتٌ لكلّ تسليم: نقِّ التكرار به. والترتيب غير مضمون؛ رتّب بحسب created_at عند الحاجة.
- أيّ تسليمٍ لا يُعيد 2xx خلال 10 ثوانٍ — أو يُعيد 4xx/5xx — يُعاد بتراجعٍ أُسّيّ: بعد دقيقة، ثمّ 10 دقائق، ثمّ ساعة، ثمّ 6 ساعات، ثمّ 24 ساعة (ستّ محاولاتٍ إجمالاً).
- بعد 15 تسليماً فاشلاً متتالياً يتعطّل الاشتراك تلقائيّاً — حمايةً لنقطتك ولنا — ويُوسَم في لوحة التحكّم. أصلِح نقطتك ثمّ أعِد تفعيله من هناك؛ وإعادة التفعيل تُصفّر سلسلة الإخفاق.
- أعِد 2xx فور تخزينك الحدث، ثمّ عالِجه لا تزامنيّاً. فالمُعالِج البطيء تنفد مهلته فيُطلق إعاداتٍ لا لزوم لها.
- دوّر سرّ النقطة من لوحة التحكّم وقتما شئت: يبقى السرّ السابق صالحاً خلال فترة سماحٍ مدّتها 24 ساعة (يُرسَل الاثنان، كلٌّ كـv1 مستقلّ)، فتُحدِّث سرّك المخزّن قبل انغلاقها.
أنشئ اشتراكاً من لوحة التحكّم (الإعدادات ← Webhooks) أو عبر الـAPI — ويُعرَض سرّ التوقيع مرّةً واحدةً عند الإنشاء:
curl -X POST https://console.widers.net/api/v1/webhooks \
-H "Authorization: Bearer wa_live_<id>|<token>" \
-H "Content-Type: application/json" \
-d '{"url":"https://your-endpoint.example/webhooks","topics":["order.paid","message.failed"]}'
إطلاق تدفّق
يمكن للتدفّق أن يكشف عنوان Webhook موقّعاً خاصّاً به — أضِف مُحفّز «Webhook وارد» في باني التدفّقات ليُولّده. أرسِل JSON بـPOST إليه فيعمل التدفّق على جهة الاتصال المُستنتَجة من الحمولة، وتُتاح له تلك الحمولة بياناتٍ.
POST https://console.widers.net/flow/webhook/<token>
صادِق بسرّ المُحفّز بأيّ الطريقتين — اختَر الأسهل على مُنادِيك:
- كتوكن حامل (مُوصى به): Authorization: Bearer <secret> — أو ترويسة X-Widers-Token. السرّ هو بيان الاعتماد، كمفتاح API؛ لا شيء يُحسَب.
- أو بنفس توقيع الـwebhooks الصادرة: X-Widers-Signature: t=<unix>,v1=<hmac_sha256(secret, "t.body")> — فيعمل مُوقِّعٌ/مُتحقِّقٌ واحدٌ في الاتّجاهين.
curl -X POST https://console.widers.net/flow/webhook/<token> \
-H "Authorization: Bearer <trigger_secret>" \
-H "Content-Type: application/json" \
-d '{"phone":"+9665XXXXXXXX","name":"..."}'
الاستجابات: 202 عند النجاح؛ و422 إن تعذّر استنتاج جهة اتصالٍ أو محادثةٍ من الحمولة؛ و401 لسرٍّ أو توقيعٍ خاطئ؛ و404 لمُحفّزٍ مجهولٍ أو مُعطَّل؛ و413 أو 415 أو 429 لجسمٍ ضخمٍ أو غير JSON أو لكثرة الطلبات.
طلبك الأول
أسرع تحقّقٍ من أنّ المفتاح يعمل: يُرجِع GET /me الحسابَ والاتّصال الذي يعمل عليه المفتاح. ولا يتطلّب أيّ نطاق، فهو المناداة الأولى المثلى:
curl https://console.widers.net/api/v1/me \
-H "Authorization: Bearer wa_live_<id>|<token>" \
-H "Accept: application/json"
ثمّ اسرد جهات اتصالك بطلب واحد مصادَق:
curl https://console.widers.net/api/v1/contacts \
-H "Authorization: Bearer wa_live_<id>|<token>" \
-H "Accept: application/json"
حجوزات التقويم (مصادقة قديمة)
- يصادِق بصلاحيات Sanctum القديمة، لا بالنطاقات أعلاه:
bookings.read/bookings.write - يُرجِع غلاف Laravel الافتراضي، لا شكل data/error:
{"data":[...]} - لا تصفّح بالمؤشّر، ولا وضع اختبار، ولا دعم لعدم التكرار — قائمته غير مصفّحة ومحدودة بسقف صارم.
سجلّ التغييرات
إضافاتٌ متوافقةٌ مع السابق فقط — قد تظهر حقولٌ جديدة، فحلّل بتسامح. وأيّ تغييرٍ كاسرٍ يصدر تحت api_version جديد مع إبقاء v1 جارياً، ويُعلَن عن أيّ إهمالٍ هنا مسبقاً.
2026-10-30— الإرسال من قناةٍ محدّدة: تقبل نقطة الإرسال مُعرّف connection، ويَسرُد GET /connections أرقامك المتّصلة وحالتها.2026-10-30— كتالوج الأحداث: يَسرُد GET /events الأحداثَ التي يجوز للمفتاح الاشتراك فيها، ويُرجِع GET /events/{event}/samples مغلّفاتٍ حقيقيّةً حديثة.2026-10-30— صارت مغلّفات الـwebhook تحمل حقل api_version، ويقبل مُحفّز التدفّق الوارد نفس توقيع t=,v1= للـwebhooks الصادرة.2026-10-30— تتعطّل اشتراكات الـwebhook تلقائيّاً بعد إخفاقات تسليمٍ متكرّرة، ويمكن إعادة تفعيلها من لوحة التحكّم.2026-10-30— CORS: صارت ترويسات حدود المعدّل وعدم التكرار مكشوفةً لعملاء المتصفّح.
مستعدّ لاستكشاف كل نقطة نهاية؟ راجِع مرجع الـAPI