Zum Hauptinhalt springen

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.

EreignistypWebhook wird ausgelöst, wenn ...
CHATBenutzer sendet eine Chatnachricht
NAME_CHANGEBenutzer ändert ihren Benutzernamen
USER_JOINEDBenutzer tritt dem Chat bei
USER_PARTEDDie letzte aktive Chatverbindung eines Benutzers wird getrennt
STREAM_STARTEDEin eingehender RTMP-Stream wird erkannt
STREAM_STOPPEDEin eingehender RTMP-Stream wird getrennt (z.B. OBS stoppt)
STREAM_TITLE_UPDATEDDer Titel des Streams wird aktualisiert
VISIBILITY-UPDATEEine zuvor gesendete Chatnachricht wird sichtbar/unsichtbar (von einem Administrator/Moderator festgelegt)
FEDIVERSE_ENGAGEMENT_FOLLOWEin Fediverse-Benutzer folgt Ihrem Server

So akzeptieren Sie Webhooks

  1. Besuchen Sie /admin/webhooks auf Ihrem Owncast-Server.
  2. Klicken Sie auf Webhook erstellen.
  3. Geben Sie die vollständige öffentliche URL zu einem Endpunkt ein, der diesen Webhook empfangen kann.
  4. 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.
  5. Wählen Sie die Ereignisse aus, über die Sie benachrichtigt werden möchten.
  6. Speichern Sie diesen neuen Webhook.

Ihr Code

  1. Erstellen Sie in jeder Sprache, auf jedem Webserver einen Endpunkt, der eine HTTP-POST-Anfrage akzeptiert. Hier wird Owncast Ereignisse senden.
  2. Jede Ereignis-Payload hat eine type-Eigenschaft, die angibt, um welchen Ereignistyp es sich handelt, und ein eventData-Objekt, das spezifische Eigenschaften dieses Ereignisses enthält.

Webhook-Anfragen verifizieren

Signed webhooks require Owncast v0.3.0

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:

  1. Parse t and s from the header.
  2. Reject the request if t differs from the current time by more than 300 seconds. This blocks replayed deliveries.
  3. Compute HMAC-SHA256(secret, "<t>." + body), where body is the exact raw request body. Don't re-serialize the JSON, since any formatting difference changes the signature.
  4. Hex-encode the result and compare it to s using 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 eventData ist für jeden type unterschiedlich.

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
}
}
  • body is the message rendered to sanitized HTML. Markdown is converted and emoji shortcodes are replaced with <img> tags.
  • rawBody is 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"]
}
}
  • ids is a list of IDs of messages that had their visibility changed.
  • visible is the new visibility of those messages.
  • user is always null for this event.

FEDIVERSE_ENGAGEMENT_FOLLOW

Webhook server URLs require Owncast v0.3.0

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.id ist eine von Owncast generierte Webhook-Ereignis-ID. Es ist nicht die Fediverse-Schauspieler-ID oder die Follow-Anforderungs-ID.
  • eventData.name ist der Anzeigename des Followers.
  • eventData.username ist der vollständige user@domain-Handle.
  • eventData.image ist die URL zum Avatar des Followers.
  • Unlike the other events, eventData does not include a status object.

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

  1. Starten Sie Owncast lokal (z.B. über Docker).
  2. Besuchen Sie localhost:8080/admin, authentifizieren Sie sich mit Benutzername: admin und dem Standard-Streaming-Key: abc123.
  3. Navigieren Sie zum Menüblock "Integration" auf der linken Seite, klicken Sie auf "Webhooks", dann auf "Webhook erstellen".
  4. Setzen Sie die Webhook-Adresse auf Ihre Anwendung/Integration (etwas wie: http://localhost:8100/webhooks/incoming).
  5. Wählen Sie die Typen von Ereignissen aus, die Sie empfangen möchten.
  6. Drücken Sie "OK", um den Webhook zu speichern.
  7. Starten Sie Ihre Integration/Anwendung, die auf der zuvor konfigurierten Adresse lauscht.
    1. Optional können Sie einen Abfang-Proxy (z.B. Burp) starten, wenn Sie die HTTP-Nachrichten vorher inspizieren möchten.
  8. 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.

Contributors to this documentation
Gabe KangasGabe Kangastaintedcyphertaintedcypher
T
Tournesol
D
Dev Gupta
L
Lili
mtabrizmtabriz
R
Raffael Rehberger