Webhook events
Every event the UTD Stream engine sends to your backend — real payloads and guaranteed fields.
Verify the signature first
Every request carries a signature keyed with your secret. Verify it before trusting any field — the payload arrives over the network, not from you.
Delivery is retried
A failed delivery is retried 3 times (2s · 4s · 8s). You can build state on these, not just confirm it.
A 4xx reply stops everything
Any 4xx cancels the retries immediately and dead-letters the event. If your backend is not ready, answer 5xx so you still get retried.
Headers
On every delivery, before you parse the body.
| Header | Meaning |
|---|---|
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 and roles
Requests to come up, invitations, and who changed whose role.
stage_requestAn audience member asks to come on stage.
| Field | Meaning |
|---|---|
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.
| Field | Meaning |
|---|---|
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.
| Field | Meaning |
|---|---|
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.
| Field | Meaning |
|---|---|
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.
| Field | Meaning |
|---|---|
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.
| Field | Meaning |
|---|---|
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.
| Field | Meaning |
|---|---|
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" }
}Seats
Who sat, who left, and which seat was locked.
seat_takenA participant takes a seat, or is placed on one.
| Field | Meaning |
|---|---|
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.
| Field | Meaning |
|---|---|
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.
| Field | Meaning |
|---|---|
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.
| Field | Meaning |
|---|---|
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 broadcast
When a broadcast ends, and why.
live_endedA live broadcast ends. `reason` says why — `host_left` means the host disconnected rather than closing it.
| Field | Meaning |
|---|---|
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 battles
Battle start and end. Score updates are realtime only — the settled result arrives with 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.
| Field | Meaning |
|---|---|
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.
| Field | Meaning |
|---|---|
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.
| Field | Meaning |
|---|---|
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" }
}Contributor shape in top_a and top_b
Each entry carries both vocabularies — new and legacy — with the same value. Name and avatar are null when unknown; the identity is never substituted for the name.
{
"user_id": "48",
"identity": "48",
"user_name": "RECHIL STERK",
"name": "RECHIL STERK",
"user_avatar": null,
"avatar_url": null,
"points": 1200,
"rank": 1
}Bereit, mit UTD zu entwickeln?
Erstellen Sie Ihr Konto, laden Sie Ihre Master-Wallet auf und aktivieren Sie die Dienste, die Sie brauchen.