ربط مجلس بمنصتك
عندك منصة تعليمية أو نظام فيه محاضرات ومقابلات؟ افتح اجتماعات مجلس من عندك، واعرضها جوه صفحاتك، واختار التسجيل يكون يدوي أو أوتوماتيك، وخد نسخة من التسجيل وقائمة الحضور لنظامك.
المفتاح بيطلع من فريق مجلس لحد دلوقتي: كلّمنا وقولنا اسم منصتك وموقعها. الاجتماعات بتتحسب على باقة الحساب اللي التطبيق متربط بيه (المدة وعدد الأشخاص ودقايق التسجيل).
إزاي بيشتغل
- سيرفرك بيعمل اجتماع للمحاضرة بالمفتاح (مرة واحدة، ولو بعتها تاني بنفس
externalIdبيرجعلك نفس الاجتماع). - لما مدرّس أو طالب يدوس «ادخل»، سيرفرك بيطلب له رابط دخول باسمه ودوره. الرابط بيشتغل مرة واحدة ولمدة قصيرة.
- صفحتك بتعرض الرابط جوه iframe بالسكربت بتاعنا، أو تفتحه في تاب جديد.
- بيوصلك على رابط الأحداث: الاجتماع بدأ وخلص، مين دخل ومين خرج (بالـ id بتاعه عندك)، والتسجيل لما يجهز.
المفتاح
كل الطلبات على http://localhost:5370/api/v1، والمفتاح في الهيدر:
Authorization: Bearer mk_xxxxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
أو X-Api-Key: mk_…. المفتاح الغلط أو الموقوف بيرجع 401.
اعمل اجتماع
curl -X POST http://localhost:5370/api/v1/meetings \
-H "Authorization: Bearer $MAJLIS_KEY" \
-H "Content-Type: application/json" \
-d '{
"externalId": "lecture-1042",
"title": "رياضيات — الوحدة التالتة",
"startsAt": "2026-11-02T17:00:00Z",
"durationMinutes": 90,
"waitingRoom": true,
"muteOnEntry": true,
"recording": "auto"
}'
الرد:
{
"id": "kdw-mpqz-tra",
"externalId": "lecture-1042",
"title": "رياضيات — الوحدة التالتة",
"status": "scheduled", // scheduled | live | ended
"recording": "auto", // off | manual | auto
"participantsNow": 0,
...
}
id هو اللي بتستخدمه في باقي الطلبات. كل الحقول اختيارية؛ recording لو مش مبعوت بياخد
الافتراضي بتاع تطبيقك. تقدر تعدّل بـ PATCH، وتمسح بـ DELETE،
وتنهي اجتماع شغّال للكل بـ POST …/end.
روابط الدخول
رابط لكل شخص، بيتعمل لحظة ما يدوس «ادخل»:
curl -X POST http://localhost:5370/api/v1/meetings/kdw-mpqz-tra/join-links \
-H "Authorization: Bearer $MAJLIS_KEY" \
-H "Content-Type: application/json" \
-d '{ "externalUserId": "student-381", "name": "سارة أحمد", "role": "participant" }'
{ "url": "http://localhost:5370/j/3f1c…", "expiresAt": "2026-11-02T17:05:00Z" }
role:hostللمدرّس (يتحكم في الاجتماع ويدخّل اللي في الانتظار) أوparticipant.- الرابط بيشتغل مرة واحدة، ولمدة
ttlSeconds(من 30 ثانية لحد ساعة، الافتراضي 5 دقايق) لحد ما يتفتح. - الاسم بيظهر في الاجتماع زي ما بعته، والشخص مش محتاج حساب في مجلس.
externalUserIdبيرجعلك في الحضور وفي الأحداث، علشان تربط كل حاجة بالطالب عندك.
اعرضه في صفحتك
ضيف موقعك (زي https://academy.example) في «المواقع المسموح لها» بتاعة التطبيق،
وبعدين في الصفحة:
<div id="lecture"></div>
<script src="http://localhost:5370/js/majlis-embed.js"></script>
<script>
// joinUrl: the "url" your server got from join-links, rendered into the page.
const meeting = Majlis.embed({ url: joinUrl, container: '#lecture', height: 640 });
meeting.on('joined', e => console.log('in as', e.displayName));
meeting.on('recording-started', () => showBadge('بيتسجّل'));
meeting.on('ended', () => location.href = '/lectures');
meeting.on('kicked', e => alert(e.reason));
// meeting.leave(); meeting.toggleMic(); meeting.toggleCamera(); meeting.destroy();
</script>
الأحداث في الصفحة: ready، waiting، joined، participant-joined،
participant-left، recording-started، recording-stopped، left،
ended، kicked، error، و* لكلهم.
جوه الـ iframe مفيش لوجو مجلس ولا رابط دعوة ولا خروج لصفحات مجلس.
موقع مش في القايمة بيتمنع يعرض الاجتماع.
من غير سكربت؟ اعرض {url}?embed=true في iframe فيه
allow="camera; microphone; display-capture; fullscreen; autoplay"،
أو افتح الرابط زي ما هو في تاب جديد.
التسجيل ونسخته
off | مفيش تسجيل، ومحدش يقدر يبدأه. |
manual | المدرّس يبدأ ويوقف التسجيل من جوه الاجتماع. |
auto | التسجيل بيبدأ لوحده أول ما أول شخص يدخل، ومحدش يقدر يوقفه. لو وقع بيتعاد مرة لوحده. |
لما التسجيل يجهز بيوصلك حدث recording.ready فيه downloadUrl (شغّال 24 ساعة).
نزّل النسخة لسيرفرك، وبعدين قولنا إنها وصلتك:
curl -X POST http://localhost:5370/api/v1/recordings/{recordingId}/received -H "Authorization: Bearer $MAJLIS_KEY"
لو تطبيقك مظبوط على «امسح بعد ما المنصة تاخد نسختها»، التسجيل بيتمسح من عندنا ساعتها.
وتقدر تمسحه بنفسك بـ DELETE /api/v1/recordings/{recordingId}،
أو تجيب روابط جديدة في أي وقت بـ GET …/meetings/{id}/recordings.
التسجيلات بتفضل محفوظة طول ما الباقة شغّالة، وشهر بعد ما تخلص.
الأحداث (Webhooks)
لو حطيت «رابط الأحداث» للتطبيق، بيوصله POST بالشكل ده:
Majlis-Event: participant.joined
Majlis-Delivery: 6c0f… // same id if it is sent again: use it to ignore repeats
Majlis-Signature: t=1793638800,v1=5d41…
{
"id": "6c0f…",
"event": "participant.joined",
"createdAt": "2026-11-02T17:01:12Z",
"meeting": { "id": "kdw-mpqz-tra", "externalId": "lecture-1042" },
"data": { "externalUserId": "student-381", "name": "سارة أحمد", "joinedAt": "…", "sessionId": "…" }
}
| الحدث | في data |
|---|---|
meeting.started | sessionId, startedAt |
meeting.ended | sessionId, startedAt, endedAt, peakParticipants |
participant.joined | externalUserId, name, joinedAt, sessionId |
participant.left | externalUserId, name, joinedAt, leftAt, minutes, sessionId |
recording.started / ready / failed | recordingId, status, durationSeconds, sizeBytes, downloadUrl, error |
رد بأي 2xx خلال 10 ثواني. لو سيرفرك واقع بنعيد بعد 30 ثانية، ودقيقتين، و10، و30، وساعة،
وبعدين أطول، لحد يوم كامل. وتأكد من التوقيع قبل ما تصدّق الحدث: HMAC-SHA256 لـ {t}.{body}
بـ «سر توقيع الأحداث» بتاع تطبيقك، وارفض اللي t بتاعه أقدم من 5 دقايق.
C#
static bool IsFromMajlis(string body, string header, string secret)
{
var parts = header.Split(',').Select(p => p.Split('=', 2)).ToDictionary(p => p[0], p => p[1]);
var t = long.Parse(parts["t"]);
if (Math.Abs(DateTimeOffset.UtcNow.ToUnixTimeSeconds() - t) > 300) return false;
var expected = Convert.ToHexString(HMACSHA256.HashData(
Encoding.UTF8.GetBytes(secret), Encoding.UTF8.GetBytes($"{t}.{body}"))).ToLowerInvariant();
return CryptographicOperations.FixedTimeEquals(
Encoding.UTF8.GetBytes(expected), Encoding.UTF8.GetBytes(parts["v1"]));
}
Node.js
const crypto = require('crypto');
function isFromMajlis(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(',').map(p => p.split('=')));
if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false;
const expected = crypto.createHmac('sha256', secret).update(`${parts.t}.${rawBody}`).digest('hex');
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}
استخدم الـ body زي ما وصل بالظبط (raw)، مش بعد ما تقراه JSON وترجعه نص.
الحضور
curl http://localhost:5370/api/v1/meetings/kdw-mpqz-tra/attendance -H "Authorization: Bearer $MAJLIS_KEY"
[
{ "externalUserId": "student-381", "name": "سارة أحمد", "isGuest": true,
"joinedAt": "…", "leftAt": "…", "minutes": 52, "sessionId": "…" }
]
كل دخول سطر لوحده: لو الطالب خرج ورجع هتلاقي له سطرين.
كل العناوين
POST | /api/v1/meetings |
GET | /api/v1/meetings/{id} |
PATCH | /api/v1/meetings/{id} |
DELETE | /api/v1/meetings/{id} |
POST | /api/v1/meetings/{id}/end |
POST | /api/v1/meetings/{id}/join-links |
GET | /api/v1/meetings/{id}/attendance |
GET | /api/v1/meetings/{id}/recordings |
POST | /api/v1/recordings/{recordingId}/received |
DELETE | /api/v1/recordings/{recordingId} |
الأخطاء
أي رفض بيرجع { "error": "…" } فيه السبب (بالإنجليزي، للمطوّر)، مع الكود:
401 | المفتاح غلط أو التطبيق موقوف. |
404 | الاجتماع أو التسجيل مش موجود، أو تبع تطبيق تاني. |
409 | الاجتماع خلص، أو التسجيل لسه ما جهزش. |
422 | قيمة مش مقبولة (زي recording أو role غلط). |