أحداث الويبهوك
كل حدث يبعته محرّك UTD Stream لباكندك — بحمولته الحقيقية وحقوله المضمونة.
تحقّق من التوقيع أولاً
كل طلب بيحمل توقيعًا بسرّك. تحقّق منه قبل ما تثق في أي حقل — الحمولة جاية من الشبكة، مش من عندك.
التسليم مضمون بإعادة محاولة
فشل التسليم بيتعاد 3 مرات (2s · 4s · 8s). يعني تقدر تبني عليها حالة، مش تأكيدًا بس.
ردّك بـ4xx بيوقف كل شيء
أي رد 4xx بيلغي إعادة المحاولة فورًا ويروح الحدث للرسائل الميتة. لو باكندك مش جاهز، ردّ 5xx عشان تاخد إعادة محاولة.
الترويسات
على كل تسليم، من غير ما تفتح الحمولة.
| الترويسة | المعنى |
|---|---|
X-UTD-Stream-Signaturestring | sha256=<hmac> over the raw body, keyed with your callback secret. Verify it before trusting anything. |
X-UTD-Stream-Eventstring | The event name, so you can route without parsing the body first. |
X-UTD-Stream-Attemptstring | 1 on the first delivery, 2..4 on retries. Use it to tell a retry from a duplicate event. |
X-UTD-Stream-App-Idstring | Your app id, when the project has one. |
المنصّة والأدوار
طلبات الصعود، الدعوات، ومن غيّر دور مين.
stage_requestAn audience member asks to come on stage.
| الحقل | المعنى |
|---|---|
room.namestring | Your room id, exactly as you created it. |
room.sidstring | The media server's own room id. Changes if the room is recreated. |
participant.identitystring | Who asked. |
{
"event": "stage_request",
"room": { "name": "114", "sid": "RM_Lfzv9uoLpyMs" },
"participant": { "identity": "48" }
}stage_joinedA participant is put on stage — by a host, or by accepting an invitation.
| الحقل | المعنى |
|---|---|
room.namestring | Your room id, exactly as you created it. |
room.sidstring | The media server's own room id. Changes if the room is recreated. |
participant.identitystring | Who joined the stage. |
participant.added_bystring | Who put them there. |
{
"event": "stage_joined",
"room": { "name": "114", "sid": "RM_Lfzv9uoLpyMs" },
"participant": { "identity": "48", "added_by": "148" }
}stage_leftA participant leaves the stage, or a host removes them.
| الحقل | المعنى |
|---|---|
room.namestring | Your room id, exactly as you created it. |
room.sidstring | The media server's own room id. Changes if the room is recreated. |
participant.identitystring | Who left the stage. |
participant.removed_bystring | Who removed them. Equals identity when they left on their own. |
{
"event": "stage_left",
"room": { "name": "114", "sid": "RM_Lfzv9uoLpyMs" },
"participant": { "identity": "48", "removed_by": "148" }
}stage_invitation_sentA host invites someone to the stage.
| الحقل | المعنى |
|---|---|
room.namestring | Your room id, exactly as you created it. |
room.sidstring | The media server's own room id. Changes if the room is recreated. |
participant.identitystring | Who was invited. |
invitation.idstring | Invitation id — carried by the accept / decline / expire events. |
invitation.inviter_identitystring | Who sent it. |
invitation.resentboolean | true when this re-sends an invitation that was still open. |
{
"event": "stage_invitation_sent",
"room": { "name": "114", "sid": "RM_Lfzv9uoLpyMs" },
"participant": { "identity": "48" },
"invitation": { "id": "inv_3f2a…", "inviter_identity": "148", "resent": false }
}stage_invitation_acceptedThe invited participant accepts. A stage_joined follows.
| الحقل | المعنى |
|---|---|
room.namestring | Your room id, exactly as you created it. |
room.sidstring | The media server's own room id. Changes if the room is recreated. |
participant.identitystring | Who accepted. |
invitation.idstring | The invitation this answers. |
{
"event": "stage_invitation_accepted",
"room": { "name": "114", "sid": "RM_Lfzv9uoLpyMs" },
"participant": { "identity": "48" },
"invitation": { "id": "inv_3f2a…" }
}stage_invitation_declinedThe invited participant declines.
| الحقل | المعنى |
|---|---|
room.namestring | Your room id, exactly as you created it. |
room.sidstring | The media server's own room id. Changes if the room is recreated. |
participant.identitystring | Who declined. |
invitation.idstring | The invitation this answers. |
{
"event": "stage_invitation_declined",
"room": { "name": "114", "sid": "RM_Lfzv9uoLpyMs" },
"participant": { "identity": "48" },
"invitation": { "id": "inv_3f2a…" }
}participant_role_changedA room owner or admin changes someone's role.
| الحقل | المعنى |
|---|---|
room.namestring | Your room id, exactly as you created it. |
room.sidstring | The media server's own room id. Changes if the room is recreated. |
participant.identitystring | Whose role changed. |
role.previousstring | Role before the change. |
role.currentstring | Role after the change. |
role.changed_bystring | Who made the change. |
role.reasonstring | Why. `owner` means an authorised person did it deliberately. |
{
"event": "participant_role_changed",
"room": { "name": "114", "sid": "RM_Lfzv9uoLpyMs" },
"participant": { "identity": "48" },
"role": { "previous": "audience", "current": "admin", "changed_by": "148", "reason": "owner" }
}المقاعد
من قعد، من قام، وأي مقعد اتقفل.
seat_takenA participant takes a seat, or is placed on one.
| الحقل | المعنى |
|---|---|
room.namestring | Your room id, exactly as you created it. |
room.sidstring | The media server's own room id. Changes if the room is recreated. |
participant.identitystring | Who is on the seat. |
seat.indexnumber | Zero-based seat position. |
{
"event": "seat_taken",
"room": { "name": "402", "sid": "RM_5QnMVtk5tgAs" },
"participant": { "identity": "48" },
"seat": { "index": 3 }
}seat_leftA seat is vacated.
| الحقل | المعنى |
|---|---|
room.namestring | Your room id, exactly as you created it. |
room.sidstring | The media server's own room id. Changes if the room is recreated. |
participant.identitystring | Who left the seat. |
seat.indexnumber | The seat that is now free. |
{
"event": "seat_left",
"room": { "name": "402", "sid": "RM_5QnMVtk5tgAs" },
"participant": { "identity": "48" },
"seat": { "index": 3 }
}seat_lockedA host locks a seat so nobody can take it.
| الحقل | المعنى |
|---|---|
room.namestring | Your room id, exactly as you created it. |
room.sidstring | The media server's own room id. Changes if the room is recreated. |
seat.indexnumber | The locked seat. |
seat.locked_bystring | Who locked it. |
{
"event": "seat_locked",
"room": { "name": "402", "sid": "RM_5QnMVtk5tgAs" },
"seat": { "index": 3, "locked_by": "148" }
}seat_unlockedA host unlocks a seat.
| الحقل | المعنى |
|---|---|
room.namestring | Your room id, exactly as you created it. |
room.sidstring | The media server's own room id. Changes if the room is recreated. |
seat.indexnumber | The unlocked seat. |
seat.unlocked_bystring | Who unlocked it. |
{
"event": "seat_unlocked",
"room": { "name": "402", "sid": "RM_5QnMVtk5tgAs" },
"seat": { "index": 3, "unlocked_by": "148" }
}البث المباشر
نهاية البث وسببها.
live_endedA live broadcast ends. `reason` says why — `host_left` means the host disconnected rather than closing it.
| الحقل | المعنى |
|---|---|
room.namestring | Your room id, exactly as you created it. |
room.sidstring | The media server's own room id. Changes if the room is recreated. |
participant.identitystring | The host the broadcast belonged to. |
participant.reasonstring | Why it ended. `host_left` when the host's connection dropped. |
{
"event": "live_ended",
"room": { "name": "114", "sid": "RM_Lfzv9uoLpyMs" },
"participant": { "identity": "148", "reason": "host_left" }
}معارك PK
بداية المعركة ونهايتها. تحديثات النقاط لحظية بلا ويبهوك — النتيجة المعتمدة بتيجي مع pk_ended.
pk_startedA PK battle begins. Score updates are realtime only — there is no webhook per point; the settled tally arrives with pk_ended.
| الحقل | المعنى |
|---|---|
battle.battle_idstring | Battle id. Use it to correlate with pk_ended. |
battle.statusstring | `active` while it runs. |
battle.room_a / room_bstring | The two room ids. There is no `side_a` / `team_1` / `a` — the sides are a and b, spelled as these fields. |
battle.host_a / host_bstring | Each side's host identity. Never null. |
battle.host_a_name / host_b_namestring | null | Display name, or null when unknown — never the identity substituted in. |
battle.host_a_avatar / host_b_avatarstring | null | Avatar url, or null. |
battle.score_a / score_bnumber | Points per side. 0 at the start. |
battle.top_a / top_barray | Top three supporters per side, highest first. Always an array — [] when nobody has sent a gift, never null. |
battle.duration_secondsnumber | Planned length. |
battle.winnerstring | null | null while active. |
battle.end_reasonstring | null | null while active. |
battle.started_at / ends_at / ended_atstring | null | ISO-8601 UTC. ended_at is null while active. |
battle.metadataobject | null | Whatever you attached when creating the battle. |
{
"event": "pk_started",
"battle": {
"battle_id": "pk_70b45421a5b7380d5b35e9a6",
"status": "active",
"room_a": "91",
"host_a": "46",
"host_a_name": "UP TO DATE",
"host_a_avatar": null,
"room_b": "42",
"host_b": "48",
"host_b_name": "RECHIL STERK",
"host_b_avatar": null,
"score_a": 0,
"score_b": 0,
"top_a": [],
"top_b": [],
"duration_seconds": 300,
"winner": null,
"end_reason": null,
"started_at": "2026-09-22T18:35:53.000Z",
"ends_at": "2026-09-22T18:40:53.000Z",
"ended_at": null,
"metadata": null
}
}pk_endedThe battle finishes. Same shape as pk_started, with the settled result filled in.
| الحقل | المعنى |
|---|---|
battle.statusstring | No longer `active`. |
battle.winnerstring | null | The winning side, or null on a draw. |
battle.end_reasonstring | Why it ended — e.g. `ended_by_host`, `host_left`. |
battle.ended_atstring | ISO-8601 UTC. |
battle.score_a / score_bnumber | Final tally. This is the authoritative result. |
{
"event": "pk_ended",
"battle": {
"battle_id": "pk_a6dae4bb78791c0803f4724c",
"status": "ended",
"room_a": "91",
"host_a": "46",
"room_b": "42",
"host_b": "48",
"score_a": 0,
"score_b": 0,
"top_a": [],
"top_b": [],
"winner": null,
"end_reason": "ended_by_host",
"ended_at": "2026-09-22T18:35:46.000Z"
}
}pk_invitedOne host invites another to a battle, before it starts.
| الحقل | المعنى |
|---|---|
battleobject | Same battle object as pk_started, with status not yet active. |
{
"event": "pk_invited",
"battle": { "battle_id": "pk_70b4…", "status": "pending", "room_a": "91", "room_b": "42" }
}شكل الداعم في top_a و top_b
كل عنصر بيحمل المفردتين — الجديدة والقديمة — بنفس القيمة. الاسم والأفاتار بيبقوا null لما يكونوا مجهولين، والمعرّف عمره ما بيتحط مكان الاسم.
{
"user_id": "48",
"identity": "48",
"user_name": "RECHIL STERK",
"name": "RECHIL STERK",
"user_avatar": null,
"avatar_url": null,
"points": 1200,
"rank": 1
}