Webhooks
Owncast unterstützt HTTP-Webhooks, um Drittanbieteranwendungen (wie Chatbots) über Ereignisse im Stream zu benachrichtigen. Mit anderen Worten: Webhooks senden Ereignisse an Ihren Code, wenn auf Ihrem Owncast-Server Dinge geschehen.
Das Folgende ist eine Liste von Ereignissen, über die Sie benachrichtigt werden können.
| Ereignistyp | Webhook wird ausgelöst, wenn ... |
|---|---|
| CHAT | Benutzer sendet eine Chatnachricht |
| NAME_CHANGE | Benutzer ändert ihren Benutzernamen |
| USER_JOINED | Benutzer tritt dem Chat bei |
| USER_PARTED | Die letzte aktive Chatverbindung eines Benutzers wird getrennt |
| STREAM_STARTED | Ein eingehender RTMP-Stream wird erkannt |
| STREAM_STOPPED | Ein eingehender RTMP-Stream wird getrennt (z.B. OBS stoppt) |
| STREAM_TITLE_UPDATED | Der Titel des Streams wird aktualisiert |
| VISIBILITY-UPDATE | Eine zuvor gesendete Chatnachricht wird sichtbar/unsichtbar (von einem Administrator/Moderator festgelegt) |
| FEDIVERSE_ENGAGEMENT_FOLLOW | Ein Fediverse-Benutzer folgt Ihrem Server |
So akzeptieren Sie Webhooks
- Besuchen Sie
/admin/webhooksauf Ihrem Owncast-Server. - Klicken Sie auf
Webhook erstellen. - Geben Sie die vollständige öffentliche URL zu einem Endpunkt ein, der diesen Webhook empfangen kann.
- Keep or replace the pre-filled webhook secret. Owncast uses it to sign every delivery, and you'll use it to verify them. You can reveal or copy it later from the webhook list.
- Wählen Sie die Ereignisse aus, über die Sie benachrichtigt werden möchten.
- Speichern Sie diesen neuen Webhook.
Ihr Code
- Erstellen Sie in jeder Sprache, auf jedem Webserver einen Endpunkt, der eine HTTP-
POST-Anfrage akzeptiert. Hier wird Owncast Ereignisse senden. - Jede Ereignis-Payload hat eine
type-Eigenschaft, die angibt, um welchen Ereignistyp es sich handelt, und eineventData-Objekt, das spezifische Eigenschaften dieses Ereignisses enthält.
Webhook-Anfragen verifizieren
Owncast 0.3.0 signs every webhook delivery. Earlier releases send unsigned requests with no signature header.
Every webhook has a secret, created with the webhook in the admin. Each delivery includes an owncast-signature header:
owncast-signature: t=1718400000.s=5f8a1c...
t is the Unix timestamp when the request was signed. s is a hex-encoded HMAC-SHA256 signature.
To verify a delivery:
- Parse
tandsfrom the header. - Reject the request if
tdiffers from the current time by more than 300 seconds. This blocks replayed deliveries. - Compute
HMAC-SHA256(secret, "<t>." + body), wherebodyis the exact raw request body. Don't re-serialize the JSON, since any formatting difference changes the signature. - Hex-encode the result and compare it to
susing a constant-time comparison.
A Node.js example:
const crypto = require("crypto");
function verifyWebhook(signatureHeader, rawBody, secret) {
const parts = {};
for (const part of signatureHeader.split(".")) {
const [key, value] = part.trim().split("=");
if (key === "t" || key === "s") parts[key] = value;
}
if (!parts.t || !parts.s) return false;
// Reject replays outside a 5 minute window.
if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false;
const expected = crypto
.createHmac("sha256", secret)
.update(`${parts.t}.${rawBody}`)
.digest("hex");
if (parts.s.length !== expected.length) return false;
return crypto.timingSafeEqual(Buffer.from(parts.s), Buffer.from(expected));
}
Verification is optional. If you skip it, treat your endpoint as something anyone on the internet could call.
Hochlevelige Webhooks
Webhooks verwenden die Methode HTTP POST, um Daten an einen Endpunkt zu übertragen. Der Anfragekörper des Webhooks ist einfaches JSON.
Daher ist der ContentType-Header für die Anfrage application/json. Jeder Webhook-Körper folgt einer einfachen JSON-Struktur.
{
"type": "",
"eventData": {}
}
wo
- type gibt Informationen darüber, um welches Ereignis es sich handelt (einer der Typen aus der obigen Tabelle).
- eventData gibt weitere Informationen zu dem Ereignis. Die Struktur von
eventDataist für jedentypeunterschiedlich.
Every eventData also includes a status object describing the current stream state and a serverURL string identifying the server that sent the event. The one exception is FEDIVERSE_ENGAGEMENT_FOLLOW, which carries serverURL but no status.
Beispiele, was für jedes Ereignis zu erwarten ist, befinden sich unten.
Webhook-Beispiele
CHAT
{
"type": "CHAT",
"eventData": {
"status": {
"lastConnectTime": "2021-08-12T07:45:03.986220954Z",
"lastDisconnectTime": null,
"versionNumber": "0.2.5",
"streamTitle": "",
"viewerCount": 3,
"overallMaxViewerCount": 7,
"sessionMaxViewerCount": 4,
"online": true
},
"serverURL": "https://stream.example.com",
"user": {
"id": "qSRQpeM7R",
"displayName": "lazyDaisy",
"displayColor": 182,
"createdAt": "2021-08-12T07:51:37.470812684Z",
"previousNames": ["lazyDaisy"],
"nameChangedAt": "2022-09-19T12:33:59.42313245+02:00",
"isBot": false,
"authenticated": false
},
"timestamp": "2021-08-12T07:53:12.061982913Z",
"body": "\u003cp\u003ehello world \u003cimg class=\"emoji\" alt=\":beerparrot:\" title=\":beerparrot:\" src=\"/img/emoji/beerparrot.gif\"\u003e\u003c/p\u003e",
"rawBody": "hello world :beerparrot:",
"id": "j-rXteG7R",
"clientId": 2,
"visible": true
}
}
bodyis the message rendered to sanitized HTML. Markdown is converted and emoji shortcodes are replaced with<img>tags.rawBodyis the original message text exactly as the user typed it.
Hinweis: Das Feld user im Chat wurde mit v0.0.8 eingeführt. Vor v0.0.8 wurde ein einfaches String-Feld mit dem Namen author verwendet.
NAME_CHANGE
{
"type": "NAME_CHANGE",
"eventData": {
"status": {
"lastConnectTime": "2021-08-12T07:45:03.986220954Z",
"lastDisconnectTime": null,
"versionNumber": "0.2.5",
"streamTitle": "",
"viewerCount": 3,
"overallMaxViewerCount": 7,
"sessionMaxViewerCount": 4,
"online": true
},
"serverURL": "https://stream.example.com",
"id": "GsxeK6MIg",
"timestamp": "2022-09-19T12:33:59.423278816+02:00",
"user": {
"id": "qSRQpeM7R",
"displayName": "NotSoLazyDaisy",
"displayColor": 182,
"createdAt": "2021-08-12T07:51:37.470812684Z",
"previousNames": ["lazyDaisy"],
"nameChangedAt": "2022-09-19T12:33:59.423278816+02:00",
"isBot": false,
"authenticated": false
},
"newName": "NotSoLazyDaisy"
}
}
USER_JOINED
{
"type": "USER_JOINED",
"eventData": {
"status": {
"lastConnectTime": "2021-08-12T07:45:03.986220954Z",
"lastDisconnectTime": null,
"versionNumber": "0.2.5",
"streamTitle": "",
"viewerCount": 3,
"overallMaxViewerCount": 7,
"sessionMaxViewerCount": 4,
"online": true
},
"serverURL": "https://stream.example.com",
"id": "wAgcTeM7g",
"timestamp": "2021-08-12T08:19:28.921355401Z",
"user": {
"id": "yFgco6M7R",
"displayName": "laughing-cray",
"displayColor": 257,
"createdAt": "2021-08-12T08:19:28.759651178Z",
"previousNames": ["laughing-cray"],
"nameChangedAt": "0001-01-01T00:00:00Z",
"isBot": false,
"authenticated": false
}
}
}
USER_PARTED
USER_PARTED wird 10 Sekunden nach der Trennung der letzten aktiven Chatverbindung eines Benutzers gesendet. Wenn der Benutzer in dieser Zeit wieder verbindet, wird das Ereignis abgebrochen. Disabling visible join and part messages only hides the message in chat. The webhook is still sent.
{
"type": "USER_PARTED",
"eventData": {
"status": {
"lastConnectTime": "2021-08-12T07:45:03.986220954Z",
"lastDisconnectTime": null,
"versionNumber": "0.2.5",
"streamTitle": "",
"viewerCount": 3,
"overallMaxViewerCount": 7,
"sessionMaxViewerCount": 4,
"online": true
},
"serverURL": "https://stream.example.com",
"id": "Ws4gTeM7R",
"timestamp": "2021-08-12T08:20:01.061982913Z",
"user": {
"id": "yFgco6M7R",
"displayName": "laughing-cray",
"displayColor": 257,
"createdAt": "2021-08-12T08:19:28.759651178Z",
"previousNames": ["laughing-cray"],
"nameChangedAt": "0001-01-01T00:00:00Z",
"isBot": false,
"authenticated": false
}
}
}
STREAM_STARTED
{
"type": "STREAM_STARTED",
"eventData": {
"id": "WtokptnVR",
"name": "Owncast",
"serverURL": "https://stream.example.com",
"status": {
"lastConnectTime": "2022-09-19T12:30:26.97907142+02:00",
"lastDisconnectTime": null,
"versionNumber": "0.2.5",
"streamTitle": "",
"viewerCount": 0,
"overallMaxViewerCount": 7,
"sessionMaxViewerCount": 0,
"online": true
},
"streamTitle": "",
"summary": "Welcome to your new Owncast server! This description can be changed in the admin. Visit https://owncast.online/docs/configuration/ to learn more.",
"timestamp": "2022-09-19T12:30:26.97907142+02:00"
}
}
STREAM_STOPPED
{
"type": "STREAM_STOPPED",
"eventData": {
"id": "YP-aptn4g",
"name": "Owncast",
"serverURL": "https://stream.example.com",
"status": {
"lastConnectTime": "2022-09-19T12:30:26.97907142+02:00",
"lastDisconnectTime": "2022-09-19T12:40:21.205872269+02:00",
"versionNumber": "0.2.5",
"streamTitle": "",
"viewerCount": 0,
"overallMaxViewerCount": 7,
"sessionMaxViewerCount": 2,
"online": false
},
"streamTitle": "",
"summary": "Welcome to your new Owncast server! This description can be changed in the admin. Visit https://owncast.online/docs/configuration/ to learn more.",
"timestamp": "2022-09-19T12:40:21.205872269+02:00"
}
}
STREAM_TITLE_UPDATED
{
"type": "STREAM_TITLE_UPDATED",
"eventData": {
"id": "DmeikEf4Rz",
"name": "New Owncast Server",
"serverURL": "https://stream.example.com",
"status": {
"lastConnectTime": null,
"lastDisconnectTime": "2024-10-24T22:35:05Z",
"versionNumber": "0.1.3",
"streamTitle": "Test stream title change",
"viewerCount": 0,
"overallMaxViewerCount": 7,
"sessionMaxViewerCount": 2,
"online": false
},
"streamTitle": "Test stream title change",
"summary": "This is a new live video streaming server powered by Owncast.",
"timestamp": "2023-03-27T21:50:10.121391094-07:00"
}
}
VISIBILITY-UPDATE
{
"type": "VISIBILITY-UPDATE",
"eventData": {
"status": {
"lastConnectTime": "2022-09-19T12:30:26.97907142+02:00",
"lastDisconnectTime": null,
"versionNumber": "0.2.5",
"streamTitle": "",
"viewerCount": 3,
"overallMaxViewerCount": 7,
"sessionMaxViewerCount": 4,
"online": true
},
"serverURL": "https://stream.example.com",
"id": "zqGupt7VR",
"timestamp": "2022-09-19T12:44:28.225779601+02:00",
"user": null,
"visible": false,
"ids": ["-Zzltt74g", "rvd2ppn4g"]
}
}
idsis a list of IDs of messages that had their visibility changed.visibleis the new visibility of those messages.useris alwaysnullfor this event.
FEDIVERSE_ENGAGEMENT_FOLLOW
Owncast 0.3.0 adds the serverURL field to this event. Earlier releases send only id, timestamp, name, username, and image.
{
"type": "FEDIVERSE_ENGAGEMENT_FOLLOW",
"eventData": {
"id": "AqilY4hDR",
"timestamp": "2026-04-13T19:17:12.528099886Z",
"name": "Test Follower",
"username": "testfollower@fake-mastodon.example.com",
"image": "https://fake-mastodon.example.com/avatars/testfollower.png",
"serverURL": "https://stream.example.com"
}
}
eventData.idist eine von Owncast generierte Webhook-Ereignis-ID. Es ist nicht die Fediverse-Schauspieler-ID oder die Follow-Anforderungs-ID.eventData.nameist der Anzeigename des Followers.eventData.usernameist der vollständigeuser@domain-Handle.eventData.imageist die URL zum Avatar des Followers.- Unlike the other events,
eventDatadoes not include astatusobject.
clientId vs. user.id
Wenn sich ein Benutzer von mehreren Geräten (oder mehreren Browsern) zur gleichen Zeit mit demselben Benutzernamen verbindet, unterscheidet Owncast zwischen ihren Sitzungen mit einer clientId. Benutzer können mehrere clientIds haben - eine einzelne clientId entspricht einer einzelnen Verbindung zu Owncast.
clientId ist eine Zahl, während user.id Groß-, Kleinbuchstaben und Ziffern enthalten kann.
Webhooks in einer lokalen Entwicklungsumgebung testen
- Starten Sie Owncast lokal (z.B. über Docker).
- Besuchen Sie
localhost:8080/admin, authentifizieren Sie sich mit Benutzername:adminund dem Standard-Streaming-Key:abc123. - Navigieren Sie zum Menüblock "Integration" auf der linken Seite, klicken Sie auf "Webhooks", dann auf "Webhook erstellen".
- Setzen Sie die Webhook-Adresse auf Ihre Anwendung/Integration (etwas wie:
http://localhost:8100/webhooks/incoming). - Wählen Sie die Typen von Ereignissen aus, die Sie empfangen möchten.
- Drücken Sie "OK", um den Webhook zu speichern.
- Starten Sie Ihre Integration/Anwendung, die auf der zuvor konfigurierten Adresse lauscht.
- Optional können Sie einen Abfang-Proxy (z.B. Burp) starten, wenn Sie die HTTP-Nachrichten vorher inspizieren möchten.
- Triggern Sie die Ereignisse selbst (z.B. schreiben Sie eine Nachricht in den Chat, verbinden/trennen Sie Ihre Streaming-Software von Owncast).
Webhooks testen, bevor Sie Code schreiben
Wenn Sie testen möchten, wie Webhooks funktionieren, bevor Sie Code schreiben, erstellen Sie einen Testendpunkt bei RequestCatcher, und fügen Sie die URL, die Sie bekommen, als Webhook in Ihrem Administrator hinzu und sehen Sie die Anfragen durchkommen.
Webhooks aus einer Produktionsinstanz von Owncast testen
Wenn Sie bereits eine Owncast-Instanz in der Produktion betreiben, die mit dem World Wide Web verbunden ist, möchten Sie vielleicht ngrok verwenden, um HTTP-Anfragen an Ihre lokale Entwicklungsumgebung zu tunneln.
Improve this page
See something missing or incorrect? Edit the English version of this page or help improve translations.


