Vai al contenuto principale

Webhooks (Webhook)

Owncast supporta i Webhook HTTP per notificare applicazioni di terze parti (come chatbot) sugli eventi dello stream. In altre parole: i webhook invieranno eventi al tuo codice quando accadono cose sul tuo server Owncast.

Di seguito è riportato l'elenco degli eventi per i quali puoi ricevere notifiche.

Tipo di eventoil webhook viene attivato quando ...
CHATun utente invia un messaggio in chat
NAME_CHANGEun utente cambia il proprio nome utente
USER_JOINEDun utente si unisce alla chat
USER_PARTEDl'ultima connessione attiva in chat di un utente si disconnette
STREAM_STARTEDviene rilevato uno stream RTMP in ingresso
STREAM_STOPPEDuno stream RTMP in ingresso si disconnette (es. OBS si ferma)
STREAM_TITLE_UPDATEDil titolo dello stream viene aggiornato
VISIBILITY-UPDATEun messaggio di chat inviato in precedenza diventa visibile/invisibile (impostato da un amministratore/moderatore)
FEDIVERSE_ENGAGEMENT_FOLLOWun utente del Fediverse segue il tuo server

Come accettare i webhook

  1. Visita /admin/webhooks sul tuo server Owncast.
  2. Clicca Create Webhook.
  3. Inserisci l'URL pubblico completo di un endpoint in grado di ricevere questo webhook.
  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. Seleziona gli eventi di cui desideri essere notificato.
  6. Salva questo nuovo webhook.

Il tuo codice

  1. In qualsiasi linguaggio, su qualsiasi tipo di server web, crea un endpoint che accetti una richiesta HTTP POST. Qui Owncast invierà gli eventi.
  2. Ogni payload di evento avrà una proprietà type che indica di quale tipo di evento si tratta, e un oggetto eventData che include proprietà specifiche di quell'evento.

Verifica delle richieste webhook

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.

Panoramica sui webhook

I webhook utilizzano il metodo HTTP POST per inviare dati a un endpoint. Il corpo della richiesta del webhook è in semplice JSON. Quindi l'header ContentType della richiesta è application/json. Il corpo di ogni webhook segue una semplice struttura JSON.

{
"type": "",
"eventData": {}
}

dove

  • type fornisce informazioni sul tipo di evento (uno dei tipi della tabella sopra).
  • eventData fornisce maggiori informazioni sull'evento. La struttura di eventData è diversa per ogni type.

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.

Esempi di eventData da aspettarsi per ogni tipo di evento sono riportati di seguito.

Esempi di webhook

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.

Nota: il campo user nella chat è stato introdotto con v0.0.8. Prima di v0.0.8 era usato un semplice campo stringa chiamato author.

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 viene inviato 10 secondi dopo che l'ultima connessione attiva in chat di un utente si disconnette. Se l'utente si riconnette durante questo intervallo, l'evento viene annullato. 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 è un ID dell'evento webhook generato da Owncast. Non è l'ID dell'attore del Fediverse né l'ID della richiesta di follow.
  • eventData.name è il nome visualizzato del follower.
  • eventData.username è l'handle completo user@domain.
  • eventData.image è l'URL all'avatar del follower.
  • Unlike the other events, eventData does not include a status object.

clientId vs. user.id

Quando un utente è connesso da più dispositivi (o più browser) contemporaneamente con lo stesso nome utente, Owncast differenzia le loro sessioni con un clientId. Gli utenti possono avere più clientIds - un singolo clientId rappresenta una singola connessione a Owncast.

clientId è un numero, mentre user.id può contenere caratteri maiuscoli, minuscoli e numerici.

Testare i webhook in un ambiente di sviluppo locale

  1. Avvia Owncast localmente (es. via docker).
  2. Visita localhost:8080/admin, autentica con Nome utente: admin e la chiave di streaming predefinita: abc123.
  3. Vai al blocco di menu "Integration" sul lato sinistro, clicca "Webhooks", poi "Create Webhook".
  4. Imposta l'indirizzo del Webhook per puntare alla tua applicazione/integrazione (qualcosa come: http://localhost:8100/webhooks/incoming).
  5. Seleziona i tipi di eventi che vuoi ricevere.
  6. Premi "OK" per salvare il webhook.
  7. Avvia la tua integrazione/applicazione per l'ascolto sull'indirizzo configurato in precedenza.
    1. Opzionalmente, avvia un proxy di intercettazione (es. Burp) se vuoi ispezionare i messaggi HTTP in anticipo.
  8. Genera eventi tu stesso (es. scrivi un messaggio in chat, connetti/disconnetti il tuo software di streaming a Owncast).

Testa i webhook prima di scrivere codice

Se vuoi testare come funzionano i webhook prima di scrivere codice, crea un endpoint di test su RequestCatcher, aggiungi l'URL fornito come webhook nel pannello di amministrazione e guarda le richieste in arrivo.

Testa i webhook da un'istanza di Owncast in produzione

Se hai già un'istanza di Owncast in esecuzione in produzione, raggiungibile dal web, potresti voler usare ngrok per instradare le richieste HTTP verso il tuo ambiente di sviluppo locale.


Improve this page

See something missing or incorrect? Edit this page and improve the documentation for everyone.

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