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 evento | il webhook viene attivato quando ... |
|---|---|
| CHAT | un utente invia un messaggio in chat |
| NAME_CHANGE | un utente cambia il proprio nome utente |
| USER_JOINED | un utente si unisce alla chat |
| USER_PARTED | l'ultima connessione attiva in chat di un utente si disconnette |
| STREAM_STARTED | viene rilevato uno stream RTMP in ingresso |
| STREAM_STOPPED | uno stream RTMP in ingresso si disconnette (es. OBS si ferma) |
| STREAM_TITLE_UPDATED | il titolo dello stream viene aggiornato |
| VISIBILITY-UPDATE | un messaggio di chat inviato in precedenza diventa visibile/invisibile (impostato da un amministratore/moderatore) |
| FEDIVERSE_ENGAGEMENT_FOLLOW | un utente del Fediverse segue il tuo server |
Come accettare i webhook
- Visita
/admin/webhookssul tuo server Owncast. - Clicca
Create Webhook. - Inserisci l'URL pubblico completo di un endpoint in grado di ricevere questo webhook.
- 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.
- Seleziona gli eventi di cui desideri essere notificato.
- Salva questo nuovo webhook.
Il tuo codice
- In qualsiasi linguaggio, su qualsiasi tipo di server web, crea un endpoint che accetti una richiesta HTTP
POST. Qui Owncast invierà gli eventi. - Ogni payload di evento avrà una proprietà
typeche indica di quale tipo di evento si tratta, e un oggettoeventDatache include proprietà specifiche di quell'evento.
Verifica delle richieste webhook
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.
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 ognitype.
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
}
}
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.
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"]
}
}
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.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 completouser@domain.eventData.imageè l'URL all'avatar del follower.- Unlike the other events,
eventDatadoes not include astatusobject.
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
- Avvia Owncast localmente (es. via docker).
- Visita
localhost:8080/admin, autentica con Nome utente:admine la chiave di streaming predefinita:abc123. - Vai al blocco di menu "Integration" sul lato sinistro, clicca "Webhooks", poi "Create Webhook".
- Imposta l'indirizzo del Webhook per puntare alla tua applicazione/integrazione (qualcosa come:
http://localhost:8100/webhooks/incoming). - Seleziona i tipi di eventi che vuoi ricevere.
- Premi "OK" per salvare il webhook.
- Avvia la tua integrazione/applicazione per l'ascolto sull'indirizzo configurato in precedenza.
- Opzionalmente, avvia un proxy di intercettazione (es. Burp) se vuoi ispezionare i messaggi HTTP in anticipo.
- 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.


