واجهة WhatsApp API
إرسال أول رسالة#
رسالة نصية عادية. جسم الطلب هو نفس هيكل كود Cloud API القياسي — نمرره مباشرة لـ Meta بدون تعديل.
POST/v26.0/{phone-number-id}/messagesAuthorization: Bearer <DEVICE_KEY>
curl -X POST https://api.sybox.io/v26.0/PHONE_NUMBER_ID/messages \
-H "Authorization: Bearer YOUR_DEVICE_KEY" \
-H "Content-Type: application/json" \
-d '{
"messaging_product": "whatsapp",
"to": "15551234567",
"type": "text",
"text": { "body": "Hello from SyBox" }
}'const res = await fetch('https://api.sybox.io/v26.0/PHONE_NUMBER_ID/messages', {
method: 'POST',
headers: {
Authorization: 'Bearer ' + process.env.SYBOX_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify({
messaging_product: 'whatsapp',
to: '15551234567',
type: 'text',
text: { body: 'Hello from SyBox' }
})
});
console.log(res.status, await res.json());import os, requests
r = requests.post(
"https://api.sybox.io/v26.0/PHONE_NUMBER_ID/messages",
headers={"Authorization": "Bearer " + os.environ["SYBOX_KEY"]},
json={
"messaging_product": "whatsapp",
"to": "15551234567",
"type": "text",
"text": {"body": "Hello from SyBox"},
},
timeout=30,
)
print(r.status_code, r.json())$ch = curl_init('https://api.sybox.io/v26.0/PHONE_NUMBER_ID/messages');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('SYBOX_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'messaging_product' => 'whatsapp',
'to' => '15551234567',
'type' => 'text',
'text' => ['body' => 'Hello from SyBox'],
]),
]);
echo curl_exec($ch);using System.Net.Http.Headers;
using System.Text;
using var http = new HttpClient(); // in a real app, keep one HttpClient for the whole app
http.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("SYBOX_KEY"));
string json = """
{
"messaging_product": "whatsapp",
"to": "15551234567",
"type": "text",
"text": { "body": "Hello from SyBox" }
}
""";
var res = await http.PostAsync(
"https://api.sybox.io/v26.0/PHONE_NUMBER_ID/messages",
new StringContent(json, Encoding.UTF8, "application/json"));
Console.WriteLine(await res.Content.ReadAsStringAsync());
الاستجابة#
تستقبل نفس الاستجابة الناتجة من Meta مع نفس كود حالة HTTP — لتستمر مكتبات الكود الحالية في قراءته بسلاسة.
200 OK · application/json {
"messaging_product": "whatsapp",
"contacts": [{ "input": "15551234567", "wa_id": "15551234567" }],
"messages": [{ "id": "wamid.HBgLMTU1NTEyMzQ1NjcVAgARGBI…" }]
}
نظامك داخل SyBox
اعرض بيانات نظامك بجانب المحادثة#
عند فتح محادثة، يستدعي SyBox نقطةَ الوصول الخاصة بنظامك ويعرض ما تُرجِعه بجانب المحادثة مباشرةً — طلبات العميل ورصيده وتذاكره. نُرسل رقم هاتف العميل، أو بريده الإلكتروني في محادثات البريد.
أضف نقطة الاتصال الخاصة بك في الإعدادات › التكاملات › واجهات الـ ERP. كل استدعاء هو طلب POST يحمل مفتاحك في Authorization، والطلب في حقل واحد هو data يحتوي على نص JSON — حتى يتمكن أي طرف من إضافة حقول لاحقاً بدون أن يتأثر الطرف الآخر. يجب الرد خلال 15 ثانية.
POST YOUR_ERP_URL
Authorization: Bearer YOUR_ERP_KEY
X-Sybox-Action: lookup
X-Sybox-Lang: en
X-Sybox-Theme: light
X-Sybox-Agent: 6f1c…
X-Sybox-Number: 104857600000000
Content-Type: application/json
{ "data": "{\"phone\":\"15551234567\",\"email\":\"customer@example.com\",\"ref\":\"\"}" }{
"renderAs": "table",
"content": [
{ "Order": "#10432", "Status": "Shipped", "Total": "$1,250.00", "sybox_ref": "10432" },
{ "Order": "#10388", "Status": "Delivered", "Total": "$310.00", "sybox_ref": "10388" }
]
}
يوضح كل استدعاء أيضاً من الذي يطلب: X-Sybox-Lang (لغة المستخدم — أجب بها)، و X-Sybox-Theme (light أو dark، للرد من نوع html)، و X-Sybox-Agent (معرّف المستخدم)، و X-Sybox-Number (الرقم الذي تجري عليه المحادثة).
نُرسل الهاتف والبريد معاً، وقد يكون أحدهما فارغاً؛ طابِق العميل بأيّهما يُعرّفه في نظامك، وأجِب بـ { renderAs, content }.
renderAs#
حدِّد renderAs ليعرف SyBox كيف يعرض بياناتك — واحدة من:
| renderAs | الوصف |
|---|
table | صفوف وأعمدة. content هو مصفوفة من كائنات بسيطة (أو { rows: [ … ] })، وتصبح مفاتيحها عناوين الأعمدة. |
cards · kpi | مربعات صغيرة، مثل الرصيد أو عدد الطلبات. content هو مصفوفة من { label, value } (أو { cards: [ … ] }). |
feed · list · thread | خط زمني من البطاقات. content هو مصفوفة من { title, text, date, tags, media: [{ type, url, name }] }. |
message | سجل واحد يتم عرضه في بطاقة واحدة على شكل رسالة. |
html | كود HTML الخاص بك، كنص في content. فقط للكود الذي تقوم بإنشائه بنفسك. |
sections | عدة أقسام في رد واحد: content هو { sections: [ { title, renderAs, content } ] }. |
json | الخيار الافتراضي عند عدم تحديد renderAs: يقوم SyBox بعرض content الخاص بك بصيغة JSON منسّقة. |
أضِف sybox_ref إلى أي صفٍّ في جدول أو بطاقة فيصبح قابلاً للنقر — يستدعيك SyBox مجدَّداً بذلك الـ ref لتُرجِع تفاصيل ذلك السجلّ وحده.
بحث المنتجات (X-Sybox-Action: products)#
نفس الرابط يمكنه أيضاً الردّ على عمليات بحث المنتجات. يكتب المستخدم ما يسأل عنه العميل، فيرسله SyBox في الحقل "q"، ويتم عرض ردّك على شكل بطاقات منتجات بجوار المحادثة، مع زر إرسال يضع رابط المنتج في صندوق الكتابة.
POST YOUR_ERP_URL
Authorization: Bearer YOUR_ERP_KEY
X-Sybox-Action: products
# the decoded "data":
{ "q": "wireless keyboard", "phone": "15551234567", "email": "customer@example.com", "ref": "" }{
"content": [
{
"name": "Wireless Keyboard K380",
"price": 449,
"oldPrice": 520,
"currency": "USD",
"available": true,
"stock": 12,
"image": "https://cdn.example.com/k380.jpg",
"link": "https://shop.example.com/p/k380",
"note": "Ships in 24h"
}
]
}
| الحقل | الوصف |
|---|
nameمطلوب | الحقل الوحيد المطلوب. المنتج الذي لا يحمل سوى اسم يظهر كبطاقة مرتّبة أيضاً. |
price · oldPrice · currency | أرقام، لا نصوص منسّقة. أرسل oldPrice فقط عند وجود سعر سابق حقيقي — يتم احتساب نسبة الخصم من الاثنين. |
available · stock | available تكون true/false، و stock عدد اختياري يظهر بجوارها. احذفهما تماماً إن كنت لا تتابع المخزون — الحقل الغائب لا يرسم شيئاً ولا تتم قراءته أبداً على أنه «غير متوفر». |
image | رابط صورة https مباشر. أي صورة تفشل في التحميل يتم استبدالها بخلفية محايدة. |
link | رابط صفحة المنتج. بدونه تكون البطاقة للعرض فقط — زر الإرسال يظهر فقط عندما يوجد ما يمكن إرساله. |
note | سطر حر قصير أسفل السعر — مدة التوصيل، أو نوع، أو شرط. |
لا شيء جديد سوى ما بنيته فعلاً: نفس الرابط، نفس المفتاح، ونفس الغلاف { "data": "…" }. انظر إلى X-Sybox-Action لتفرّق بين بحث المنتجات واستعلام العميل، وأجِب بـ content كمصفوفة مباشرة (أو { items: [ … ] }).
الأحداث
الويب هوك: الأحداث على خادمك#
أضف نقطة الاتصال الخاصة بك في الإعدادات › التكاملات › الويب هوك، واختر الأحداث والأرقام، وسيقوم SyBox بإرسال كل حدث إليها بطلب POST فور حدوثه. يصل حدث واتساب بصيغة الويب هوك الخاصة بـ Meta نفسها، لذلك يعمل الكود المكتوب لـ Meta بدون تغيير.
| الحدث | الوصف |
|---|
messageرسالة واردة | رسالة من العميل. |
statusحالة الرسالة | تم الإرسال، أو تم التسليم، أو تمت القراءة، أو فشل الإرسال — لرسالة أرسلتها أنت. |
reactionتفاعل | قام العميل بالتفاعل مع رسالة. |
doneوضع علامة كمكتمل على محادثة | تم وضع علامة كمكتمل على محادثة: بواسطة مستخدم، أو بواسطة أتمتة، أو تلقائياً بعد صمت العميل. |
labelإضافة تصنيف أو إزالته | تمت إضافة تصنيف إلى محادثة أو إزالته منها. |
contactإنشاء جهة اتصال أو تحديثها | تم إنشاء جهة اتصال، أو تغيّر اسمها أو بريدها الإلكتروني أو حقول جهة الاتصال الخاصة بها. |
dealإتمام صفقة | تم تسجيل صفقة مكتملة في محادثة. |
caseفتح حالة CRM أو إغلاقها | تم فتح حالة CRM أو إغلاقها (تم حلها أو لم يتم حلها أو تم إلغاؤها). |
POST YOUR_WEBHOOK_URL
Content-Type: application/json
X-Hub-Signature-256: sha256=5d1f0c…
x-sybox-signature: sha256=5d1f0c…
x-sybox-event: message
x-sybox-timestamp: 1761400000
{
"object": "whatsapp_business_account",
"entry": [{
"id": "WABA_ID",
"changes": [{
"field": "messages",
"value": {
"messaging_product": "whatsapp",
"metadata": { "display_phone_number": "15550001111", "phone_number_id": "PHONE_NUMBER_ID" },
"contacts": [{ "profile": { "name": "Sara" }, "wa_id": "15551234567" }],
"messages": [{
"from": "15551234567",
"id": "wamid.HBgLMTU1NTEyMzQ1NjcVAgASGBQ…",
"timestamp": "1761400000",
"type": "text",
"text": { "body": "Is my order shipped?" }
}]
}
}]
}]
}
الوسائط. رابط Meta الأصلي يحتاج إلى رمز وصول من Meta لفتحه، لذلك يقوم SyBox باستبداله برابط موقّع لنسخته الخاصة من الملف، صالح لمدة 24 ساعة. ويمكن للرسالة الصوتية أيضاً أن تتضمن نصها في sybox.transcription، عندما يطلبه الويب هوك.
القنوات الأخرى — ماسنجر، وإنستجرام، وتيليجرام، والدردشة المباشرة — تصل بصيغة SyBox الخاصة، لحدث الرسالة فقط:
{
"object": "sybox",
"event": "message",
"channel": "telegram",
"line": "8123456789",
"workspace": "yourworkspace",
"contact": { "key": "tg:6423115118", "name": "Sara" },
"message": {
"id": "812345",
"timestamp": 1761400000,
"type": "image",
"text": "",
"caption": "The broken part",
"media": { "url": "https://api.sybox.io/…", "mime": "image/jpeg" }
}
}
تصل أحداث المحادثات بتنسيق SyBox الموحّد لجميع القنوات. ويحدد الحقل event ما حدث بالضبط:
| الحدث | <code>event</code> |
|---|
| وضع علامة كمكتمل على محادثة | conversation.done |
| إضافة تصنيف أو إزالته | label.added · label.removed |
| إنشاء جهة اتصال أو تحديثها | contact.created · contact.updated |
| إتمام صفقة | deal.closed |
| فتح حالة CRM أو إغلاقها | case.opened · case.closed |
| حدث تجريبي | test |
{
"object": "sybox",
"event": "conversation.done",
"channel": "whatsapp",
"line": "201001234567",
"workspace": "yourworkspace",
"timestamp": 1761400000,
"contact": {
"key": "201112223344",
"name": "Sara",
"phone": "201112223344",
"email": "",
"channel": "whatsapp",
"labels": [{ "id": 3, "name": "VIP" }],
"fields": [{ "id": 2, "key": "city", "name": "City", "value": "Cairo" }]
},
"data": { "by": "Omar", "note": "Refund sent" }
}{
"object": "sybox",
"event": "case.closed",
"workspace": "yourworkspace",
"timestamp": 1761400000,
"case": {
"id": 8124,
"guid": "5f0c1e9a…",
"subject": "Screen replacement not delivered",
"type": 1,
"status": "solved",
"department": "Support"
},
"customer": { "name": "Sara", "phone": "201112223344" }
}
مفتاح التوقيع#
| الترويسة | الوصف |
|---|
X-Hub-Signature-256 | sha256= متبوعاً بقيمة HMAC-SHA256 بصيغة hex لجسم الطلب الخام، باستخدام سر التوقيع الخاص بالويب هوك كمفتاح — نفس الترويسة والطريقة المستخدمة لدى Meta. |
x-sybox-signature | القيمة نفسها، حتى تتمكن نقطة الاتصال التي تستقبل أيضاً طلبات Meta نفسها من التمييز بين طلباتنا وطلباتها. |
x-sybox-event | ما حدث: message أو status أو reaction، أو أحد أحداث المحادثة الموضحة أدناه (conversation.done، label.added…)، أو test. |
x-sybox-timestamp | وقت الإرسال، بالثواني بصيغة Unix. |
x-sybox-signature-rsa | توقيع ثانٍ: rsa-sha256= ثم توقيع RSA-SHA256 (PKCS#1 v1.5) بترميز base64 لـ x-sybox-timestamp ثم نقطة ثم النص الخام للطلب. |
x-sybox-key-id | أيّ من مفاتيحنا قام بإنشاء التوقيع. المفتاح العام موجود على https://api.sybox.io/.well-known/sybox-webhook-key. |
تحقق من التوقيع على البايتات الخام قبل تحليلها، وقارن في زمن ثابت:
const crypto = require('crypto');
// express.raw: the signature is over the bytes as sent, so read them before any JSON parser.
app.post('/sybox/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const sent = Buffer.from(req.get('X-Hub-Signature-256') || '');
const want = Buffer.from('sha256=' + crypto
.createHmac('sha256', process.env.SYBOX_WEBHOOK_SECRET)
.update(req.body)
.digest('hex'));
if (sent.length !== want.length || !crypto.timingSafeEqual(sent, want)) return res.sendStatus(401);
res.sendStatus(200); // answer first, then do the work
const event = JSON.parse(req.body);
const kind = req.get('x-sybox-event'); // message | status | reaction
});$raw = file_get_contents('php://input');
$want = 'sha256=' . hash_hmac('sha256', $raw, getenv('SYBOX_WEBHOOK_SECRET'));
$sent = $_SERVER['HTTP_X_HUB_SIGNATURE_256'] ?? '';
if (!hash_equals($want, $sent)) {
http_response_code(401);
exit;
}
http_response_code(200);
$event = json_decode($raw, true);
$kind = $_SERVER['HTTP_X_SYBOX_EVENT'] ?? ''; // message | status | reactionimport hashlib, hmac, os
from flask import Flask, abort, request
app = Flask(__name__)
@app.post("/sybox/webhook")
def sybox_webhook():
raw = request.get_data()
want = "sha256=" + hmac.new(os.environ["SYBOX_WEBHOOK_SECRET"].encode(), raw, hashlib.sha256).hexdigest()
if not hmac.compare_digest(want, request.headers.get("X-Hub-Signature-256", "")):
abort(401)
event = request.get_json()
kind = request.headers.get("x-sybox-event") # message | status | reaction
return "", 200using System.Security.Cryptography;
using System.Text;
app.MapPost("/sybox/webhook", async (HttpRequest req) =>
{
using var ms = new MemoryStream();
await req.Body.CopyToAsync(ms);
byte[] raw = ms.ToArray();
string secret = Environment.GetEnvironmentVariable("SYBOX_WEBHOOK_SECRET")!;
byte[] mac = HMACSHA256.HashData(Encoding.UTF8.GetBytes(secret), raw);
byte[] want = Encoding.ASCII.GetBytes("sha256=" + Convert.ToHexString(mac).ToLowerInvariant());
byte[] sent = Encoding.ASCII.GetBytes(req.Headers["X-Hub-Signature-256"].ToString());
if (!CryptographicOperations.FixedTimeEquals(want, sent)) return Results.Unauthorized();
string kind = req.Headers["x-sybox-event"]; // message | status | reaction
return Results.Ok();
});
لا تريد الاحتفاظ بسر مشترك على خادمك؟ تحقّق من x-sybox-signature-rsa بمفتاحنا العام بدلاً من ذلك. وفي الحالتين، ارفض أي طابع زمني مرّ عليه أكثر من خمس دقائق.
أجب بأي كود 2xx خلال 15 ثانية. مع أي رد آخر — خطأ، أو انتهاء المهلة، أو إعادة توجيه — تتم إعادة إرسال الحدث بعد 1 و 2 و 4 و 8 و 16 و 32 دقيقة، ثم يتم إهماله. قد يصل الحدث أكثر من مرة: استخدم معرّف الرسالة لتجاهل التكرار. يجب أن يكون العنوان عاماً ويستخدم https.
الويب هوك الذي يفشل 20 مرات متتالية لأكثر من ساعة يتم إيقافه تلقائياً ويتم إبلاغ مسؤول مساحة العمل. يتم الاحتفاظ بالأحداث المنتظرة لمدة 24 ساعة: أعد تفعيله لإرسالها.
للاختبار، اضغط على «إرسال حدث تجريبي» في بطاقة الويب هوك. وتعرض نافذة «عمليات التسليم» في البطاقة نفسها آخر 50 عملية تسليم مع رد الخادم لديك، ويمكنك من خلالها إعادة إرسال أي عملية.
الدردشة المباشرة
الدردشة المباشرة#
ضع دردشة على موقعك الإلكتروني: يكتب الزوار هناك، ويجيب فريقك من SyBox. أضف الموقع في الإعدادات › المراسلة › القنوات، من بطاقة «الدردشة المباشرة»؛ وستعطيك النافذة التي تظهر، «الرمز البرمجي لموقعك»، الرمز البرمجي والمفاتيح الموضحة أدناه. لا يتم عرض المفاتيح السرية الثلاثة إلا مرة واحدة.
| المفتاح | يبدأ بـ | الغرض منه |
|---|
| المفتاح العام | lcpk_ | تضعه في صفحاتك. ونشره آمن: فهو يحدد الموقع فقط، ولا تظهر الدردشة إلا على العناوين الموجودة في «عناوين المواقع الإلكترونية». |
| مفتاح توقيع المستخدمين المسجلين | lcid_ | على خادمك فقط. يقوم بتوقيع كل مستخدم مسجل، حتى لا يتمكن أحد من الكتابة باسم شخص آخر. |
| مفتاح الخادم (لشاشة الدردشة الخاصة بك) | lcsk_ | على خادمك فقط. مخصص فقط للموقع الذي يبني شاشة دردشة خاصة به بدلاً من الدردشة الجاهزة. |
| مفتاح توقيع الردود | lcwh_ | على خادمك فقط. يثبت أن الرد الذي وصل إلى خادمك صادر من SyBox. |
الدردشة الجاهزة لا تحتاج إلا إلى الرمز البرمجي. مفتاح التوقيع مخصص للموقع الذي يحتوي على حسابات للمستخدمين؛ أما مفتاح الخادم ومفتاح توقيع الردود فمخصصان فقط للموقع الذي لديه شاشة دردشة خاصة به.
عند إيقاف موقع، يتم إخفاء الدردشة فوراً. ويستمر قبول استدعاءات خادمك وتخزينها.
أضف الدردشة إلى موقعك#
الصق الرمز البرمجي مرة واحدة في كل صفحة، قبل وسم إغلاق body مباشرة. لا تظهر الدردشة إلا على العناوين الموجودة في «عناوين المواقع الإلكترونية»، ويتم ضبط لونها وموضعها وعنوانها ورسالة الترحيب من SyBox، وليس من الكود.
<script async src="https://api.sybox.io/lc/w.js" data-key="lcpk_YOUR_PUBLIC_KEY"></script>
WordPress: قم بتنزيل إضافتنا من النافذة نفسها. Shopify: في تطبيق SyBox على Shopify، فعّل «الدردشة المباشرة في هذا المتجر»، ثم فعّل SyBox Live Chat ضمن تضمينات التطبيقات (App embeds) — ويتم التعرف على العميل المسجل بدون أي خطوة إضافية منك.
المستخدمون المسجلون: مفتاح التوقيع#
عندما يكون المستخدم مسجلاً الدخول في موقعك، يقوم خادمك بإخبار الدردشة بهويته عن طريق رمز قصير — JWT بخوارزمية HS256 — موقّع بمفتاح التوقيع. بعدها تكون للمستخدم محادثة واحدة باسمه على كل أجهزته، ولا يمكن لأحد فتحها بكتابة معرّف شخص آخر.
- بعد تسجيل الدخول، يقوم خادمك ببناء الرمز من الحقول (claims) التالية.
- تمرر الصفحة الرمز إلى الدردشة باسم
identity، قبل الرمز البرمجي للدردشة. - بعد تسجيل الدخول بدون إعادة تحميل الصفحة، استدعِ
SyBoxChat('identify', token) بدلاً من ذلك.
| الحقل | الوصف |
|---|
subمطلوبstring | معرّف المستخدم لديك، حتى 120 حرفاً. لا يمكن أن يبدأ بـ v_. |
expمطلوبnumber | وقت انتهاء الصلاحية، بالثواني بصيغة Unix — لا يتجاوز يوماً واحداً من الآن. عشر دقائق مدة مناسبة: فلا تتم قراءة الرمز إلا عند فتح الدردشة. |
namestring | الاسم الذي يظهر في المحادثة. |
emailstring | البريد الإلكتروني للمستخدم. |
phonestring | رقم هاتف المستخدم. |
picturestring | عنوان https لصورة المستخدم، حتى 500 حرف. يحتفظ SyBox بنسخته الخاصة منها، ويقوم بجلبها مجدداً عند تسجيل الدخول التالي إذا تغيّر العنوان. |
function b64url(string $s): string { return rtrim(strtr(base64_encode($s), '+/', '-_'), '='); }
$header = b64url(json_encode(['alg' => 'HS256', 'typ' => 'JWT']));
$payload = b64url(json_encode([
'sub' => (string) $user->id,
'name' => $user->name,
'email' => $user->email,
'exp' => time() + 600, // ten minutes
]));
$signature = b64url(hash_hmac('sha256', "$header.$payload", getenv('SYBOX_LC_SIGNING_KEY'), true));
$token = "$header.$payload.$signature";const crypto = require('crypto');
const b64url = (s) => Buffer.from(s).toString('base64url');
const header = b64url(JSON.stringify({ alg: 'HS256', typ: 'JWT' }));
const payload = b64url(JSON.stringify({
sub: String(user.id),
name: user.name,
email: user.email,
exp: Math.floor(Date.now() / 1000) + 600 // ten minutes
}));
const signature = crypto.createHmac('sha256', process.env.SYBOX_LC_SIGNING_KEY)
.update(header + '.' + payload).digest('base64url');
const token = header + '.' + payload + '.' + signature;import base64, hashlib, hmac, json, os, time
def b64url(b: bytes) -> str:
return base64.urlsafe_b64encode(b).rstrip(b"=").decode()
header = b64url(json.dumps({"alg": "HS256", "typ": "JWT"}).encode())
payload = b64url(json.dumps({
"sub": str(user.id),
"name": user.name,
"email": user.email,
"exp": int(time.time()) + 600, # ten minutes
}).encode())
key = os.environ["SYBOX_LC_SIGNING_KEY"].encode()
signature = b64url(hmac.new(key, f"{header}.{payload}".encode(), hashlib.sha256).digest())
token = f"{header}.{payload}.{signature}"using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
static string B64Url(byte[] b) => Convert.ToBase64String(b).TrimEnd('=').Replace('+', '-').Replace('/', '_');
string header = B64Url(JsonSerializer.SerializeToUtf8Bytes(new { alg = "HS256", typ = "JWT" }));
string payload = B64Url(JsonSerializer.SerializeToUtf8Bytes(new {
sub = user.Id.ToString(),
name = user.Name,
email = user.Email,
exp = DateTimeOffset.UtcNow.AddMinutes(10).ToUnixTimeSeconds()
}));
byte[] key = Encoding.UTF8.GetBytes(Environment.GetEnvironmentVariable("SYBOX_LC_SIGNING_KEY"));
string signature = B64Url(HMACSHA256.HashData(key, Encoding.ASCII.GetBytes(header + "." + payload)));
string token = header + "." + payload + "." + signature;
<script>
window.SyBoxChatConfig = { identity: "TOKEN_FROM_YOUR_SERVER", locale: "en" };
</script>
<script async src="https://api.sybox.io/lc/w.js" data-key="lcpk_YOUR_PUBLIC_KEY"></script>
أنشئ الرمز على خادمك فقط. وجود مفتاح التوقيع في صفحة ويب يسمح لأي شخص بالكتابة باسم أي مستخدم من مستخدميك. ولا تضع أبداً معرّف المستخدم وحده في الصفحة بدلاً من الرمز: فهذا يسمح لأي شخص باختيار المحادثة التي يريد قراءتها.
عند تفعيل «المستخدمون المسجلون في موقعي فقط»، لا تظهر الدردشة للزائر الذي ليس لديه رمز.
الزائر الذي تحدث في الدردشة قبل تسجيل الدخول يظهر له سؤال مرة واحدة، في المتصفح نفسه، عمّا إذا كان يريد متابعة تلك المحادثة بحسابه. لا تحتاج إلى فعل أي شيء لذلك.
تحكم في الدردشة من صفحتك#
يضيف الرمز البرمجي للدردشة دالة واحدة إلى صفحتك، وهي SyBoxChat:
SyBoxChat('open'); // open the chat
SyBoxChat('close'); // close it
SyBoxChat('identify', token); // after a sign-in without a page reload
SyBoxChat('locale', 'fr'); // the chat's language
SyBoxChat('on', 'message', function (m) {
// a reply arrived
});
SyBoxChat('on', 'open', function () { /* the chat opened */ });
SyBoxChat('on', 'close', function () { /* the chat closed */ });
// Before the chat's code, every setting is optional:
window.SyBoxChatConfig = {
identity: 'TOKEN_FROM_YOUR_SERVER', // a signed-in user (see above)
name: 'Sara', email: 'sara@example.com', // a visitor without an account: fills the form for them
locale: 'ar',
serviceWorker: 'auto' // 'auto' | '/your-sw.js' | 'site' | 'off'
};
الإشعارات بعد إغلاق موقعك#
أثناء فتح أي صفحة من موقعك، يظهر الرد كإشعار تلقائياً. وللوصول إلى الزائر بعد إغلاق موقعك، تتطلب المتصفحات وجود service worker على نطاقك الخاص: قدّم هذا السطر الواحد كملف /sybox-lc-sw.js في جذر موقعك، بنوع application/javascript.
try { importScripts('https://api.sybox.io/lc/sw.js'); } catch (e) {}
اختر الوضع في SyBox («الإشعارات عندما يكون الموقع مغلقًا») أو في الصفحة باستخدام serviceWorker: "auto" (الملف أعلاه، وهو الافتراضي)، أو مسار خاص بك، أو "site" (ضع السطر نفسه في بداية الـ service worker الخاص بموقعك)، أو "off".
لا تقوم الدردشة أبدًا باستبدال الـ service worker الموجود على موقعك، ولا يحتوي الملف على معالج fetch، لذلك لا يرى أي طلب من طلبات موقعك. وتقوم إضافة WordPress بتقديم الملف نيابة عنك. لا يعرض Safari على iPhone أي إشعارات لموقع ويب عادي.
شاشة الدردشة الخاصة بك: مفتاح الخادم#
الموقع أو التطبيق الذي لديه شاشة دردشة خاصة به يتواصل مع SyBox من خادمه بدلاً من استخدام الدردشة الجاهزة. كل استدعاء يحمل Authorization: Bearer lcsk_…. يحدد خادمك المستخدم بالمعرّف الخاص بك، ويثق به SyBox لأنه يحمل المفتاح.
| الاستدعاء | الوصف |
|---|
POST /v1/livechat/messages | يكتب المستخدم لديك رسالة. يجعل client_id إعادة المحاولة آمنة: يتم حفظ المعرّف نفسه مرة واحدة فقط ("duplicate": 1). |
POST /v1/livechat/upload | ملف، بصيغة multipart form data: file و user_id و client_id. حتى 16 MB: صور، وصوت، وفيديو، و PDF، ونصوص، ومستندات Office. |
GET /v1/livechat/messages?user_id=&since=&before=&limit= | المحادثة: الرسائل بعد since، و agents_read_id، وهو أحدث رسالة من رسائل المستخدم لديك قرأها أحد أفراد فريقك. |
POST /v1/livechat/read | قرأ المستخدم لديك الردود حتى up_to. |
POST /v1/livechat/edit | يعدّل المستخدم لديك رسالته، خلال 15 دقيقة. |
POST /v1/livechat/delete | يحذف المستخدم لديك رسالته لدى الجميع، خلال 24 ساعة. |
POST /v1/livechat/react | تفاعل على أي رسالة؛ وقيمة emoji الفارغة تقوم بإزالته. |
POST /v1/livechat/typing | المستخدم لديك يكتب الآن: أرسل on: 1 كل بضع ثوانٍ، و on: 0 عندما يتوقف. |
curl -X POST https://api.sybox.io/v1/livechat/messages \
-H "Authorization: Bearer $SYBOX_LC_SERVER_KEY" \
-H "Content-Type: application/json" \
-d '{
"user": { "id": "u42", "name": "Sara Ali", "email": "sara@example.com", "locale": "ar" },
"message": { "client_id": "a1b2c3", "text": "Hi, is my order on its way?", "reply_to": "" }
}'{ "ok": 1, "id": 981, "guid": "7c9e…", "duplicate": 0 }{ "user_id": "u42", "up_to": 990 } // read
{ "user_id": "u42", "id": 981, "text": "…" } // edit
{ "user_id": "u42", "id": 981 } // delete
{ "user_id": "u42", "id": 990, "emoji": "👍" } // react
{ "user_id": "u42", "on": 1 } // typing
الرسالة، في السجل وفي الردود:
{
"id": 990,
"guid": "b81d…",
"dir": "out", // "in" = your user wrote it, "out" = your team
"type": "image", // text | image | audio | video | file
"text": "Here is the tracking page",
"media": { "url": "https://api.sybox.io/…", "mime": "image/png", "name": "tracking.png", "w": 1080, "h": 720 },
// a voice note or a video also has "secs"
"status": 3, // on your user's messages: 1 received, 3 read by an agent
"reply_to": "7c9e…",
"client": "a1b2c3", // on your user's messages: the client_id you sent
"react_us": "", // your team's reaction on it
"react_me": "👍", // your user's reaction on it
"at": "2026-10-25T09:41:07Z",
"edited": 0
}
عند طلب سجل مستخدم لم يكتب من قبل، يكون الرد 404 unknown_user: اعرض محادثة فارغة.
روابط الوسائط موقّعة وتعمل لمدة ساعة واحدة؛ اطلبها مرة أخرى للحصول على رابط جديد. لا يقوم SyBox أبداً بجلب ملف من عنوان ترسله: قم برفعه. حتى 120 استدعاء في الدقيقة لكل موقع.
الردود على خادمك: مفتاح توقيع الردود#
أدخل «عنوان خادمك لتلقي الردود» في إعدادات الموقع. عندما يرد فريقك أو يعدّل أو يحذف أو يقرأ، يقوم SyBox بإرسال الحدث إلى هذا العنوان بطلب POST، موقّعاً بمفتاح توقيع الردود.
| الترويسة | الوصف |
|---|
x-sybox-signature | t=TIMESTAMP,v1=SIGNATURE — حيث TIMESTAMP هو الوقت بثواني Unix، و SIGNATURE هو قيمة HMAC-SHA256 بصيغة hex لـ TIMESTAMP.BODY (المحتوى الخام للطلب)، ويتم حسابها بمفتاح توقيع الردود. |
x-sybox-event | message.created أو message.updated أو message.deleted أو messages.read. |
x-sybox-delivery | القيمة نفسها في كل إعادة محاولة للحدث الواحد: استخدمها لتجاهل التكرار. |
يحتوي جسم الطلب على event و line_id و user_id و message — أو message_id في حالة الحذف، و read_id في messages.read: وتكون رسائل المستخدم لديك مقروءة حتى هذا المعرّف.
const crypto = require('crypto');
app.post('/sybox/livechat', express.raw({ type: 'application/json' }), (req, res) => {
const parts = Object.fromEntries((req.get('x-sybox-signature') || '').split(',').map(p => p.split('=')));
const t = Number(parts.t);
if (!t || Math.abs(Date.now() / 1000 - t) > 300) return res.sendStatus(401); // older than five minutes
const want = Buffer.from(crypto.createHmac('sha256', process.env.SYBOX_LC_REPLY_KEY)
.update(t + '.').update(req.body).digest('hex'));
const sent = Buffer.from(parts.v1 || '');
if (sent.length !== want.length || !crypto.timingSafeEqual(sent, want)) return res.sendStatus(401);
res.sendStatus(200);
const delivery = req.get('x-sybox-delivery'); // seen it already? ignore
const evt = JSON.parse(req.body); // { event, line_id, user_id, message | message_id | read_id }
});$raw = file_get_contents('php://input');
parse_str(str_replace(',', '&', $_SERVER['HTTP_X_SYBOX_SIGNATURE'] ?? ''), $sig);
$t = (int) ($sig['t'] ?? 0);
$want = hash_hmac('sha256', $t . '.' . $raw, getenv('SYBOX_LC_REPLY_KEY'));
if (abs(time() - $t) > 300 || !hash_equals($want, $sig['v1'] ?? '')) {
http_response_code(401);
exit;
}
http_response_code(200);
$delivery = $_SERVER['HTTP_X_SYBOX_DELIVERY'] ?? ''; // seen it already? ignore
$evt = json_decode($raw, true);import hashlib, hmac, os, time
from flask import abort, request
@app.post("/sybox/livechat")
def sybox_livechat():
raw = request.get_data()
sig = dict(p.split("=", 1) for p in request.headers.get("x-sybox-signature", "").split(",") if "=" in p)
t = int(sig["t"]) if sig.get("t", "").isdigit() else 0 # a malformed header is a 401, not a crash
want = hmac.new(os.environ["SYBOX_LC_REPLY_KEY"].encode(), f"{t}.".encode() + raw, hashlib.sha256).hexdigest()
if abs(time.time() - t) > 300 or not hmac.compare_digest(want, sig.get("v1", "")):
abort(401)
delivery = request.headers.get("x-sybox-delivery") # seen it already? ignore
evt = request.get_json()
return "", 200using System.Security.Cryptography;
using System.Text;
app.MapPost("/sybox/livechat", async (HttpRequest req) =>
{
using var ms = new MemoryStream();
await req.Body.CopyToAsync(ms);
byte[] raw = ms.ToArray();
var sig = new Dictionary<string, string>();
foreach (var part in req.Headers["x-sybox-signature"].ToString().Split(','))
{
var kv = part.Split('=', 2);
if (kv.Length == 2) sig[kv[0].Trim()] = kv[1].Trim(); // a malformed header is a 401, not a crash
}
if (!long.TryParse(sig.GetValueOrDefault("t"), out long t) ||
Math.Abs(DateTimeOffset.UtcNow.ToUnixTimeSeconds() - t) > 300) return Results.Unauthorized();
byte[] signedBytes = Encoding.ASCII.GetBytes(t + ".").Concat(raw).ToArray();
string replyKey = Environment.GetEnvironmentVariable("SYBOX_LC_REPLY_KEY")!;
byte[] mac = HMACSHA256.HashData(Encoding.UTF8.GetBytes(replyKey), signedBytes);
byte[] want = Encoding.ASCII.GetBytes(Convert.ToHexString(mac).ToLowerInvariant());
byte[] sent = Encoding.ASCII.GetBytes(sig.GetValueOrDefault("v1", ""));
if (!CryptographicOperations.FixedTimeEquals(want, sent)) return Results.Unauthorized();
return Results.Ok();
});
تحقق من التوقيع على جسم الطلب الخام، وارفض أي t أقدم من خمس دقائق، وأجب بكود 2xx بسرعة. مع أي رد آخر تتم إعادة المحاولة بعد 10 ثوانٍ، ثم 30 ثانية، ثم دقيقتين، ثم 10 دقائق، ثم 30 دقيقة، ثم يتم إسقاط الحدث. لا يتصل SyBox إلا بالعناوين العامة، ولا يتبع أبداً إعادة التوجيه.
أكواد الأسباب#
كل رد من الدردشة المباشرة يكون بصيغة JSON — {"ok": 1, …}، أو {"ok": 0, "error": "CODE"} مع كود بدلاً من جملة:
| الكود | الحالة | الوصف |
|---|
unauthorized | 401 | مفتاح الخادم خاطئ أو تم استبداله. |
invalid_json invalid_payload invalid_user_id | 400 | الطلب ليس بالصيغة المتوقعة. |
empty_message no_file empty_file | 400 | لا يوجد نص ولا ملف. |
too_long | 400 | النص المعدّل يتجاوز 4,096 حرفاً. |
not_an_emoji | 400 | emoji ليس رمزاً تعبيرياً. |
not_found | 404 | لا توجد رسالة بهذا المعرّف، أو أنها ليست من رسائل هذا المستخدم. |
too_late | 409 | انتهى الوقت المسموح للتعديل أو الحذف. |
file_type_not_allowed | 400 | هذا النوع من الملفات غير مقبول. |
file_too_large | 413 | حجم الملف أكبر من 16 MB. |
blocked | 403 | قام فريقك بحظر هذا المستخدم. |
unknown_user | 404 | لا توجد محادثة لمعرّف المستخدم هذا حتى الآن. |
rate_limited | 429 | عدد كبير جداً من الاستدعاءات: قلّل السرعة وحاول مرة أخرى. |
maintenance | 503 | جارٍ تحديث SyBox: حاول مرة أخرى بعد Retry-After ثانية. |
server_error | 500 | حدث خطأ من جهتنا. |
استبدال مفتاح#
هل فقدت مفتاحاً، أو تظن أنه تسرّب؟ في بطاقة الموقع، اضغط «مفتاح توقيع جديد» أو «مفتاح خادم جديد» أو «مفتاح توقيع ردود جديد». مفتاح الخادم الجديد يترك المفتاح القديم يعمل لمدة يوم واحد، حتى تتمكن من الانتقال بدون انقطاع؛ أما المفاتيح الأخرى فتتوقف فوراً.