Ir al contenido principal

Webhooks

Owncast admite HTTP Webhooks para notificar a aplicaciones de terceros (como chatbots) sobre eventos en la transmisión. En otras palabras: los webhooks enviarán eventos a su código cuando ocurran cosas en su servidor Owncast.

A continuación se muestra una lista de eventos sobre los que puede recibir notificaciones.

Tipo de eventoel webhook se activa cuando ...
CHATun usuario envía un mensaje de chat
NAME_CHANGEun usuario cambia su nombre de usuario
USER_JOINEDun usuario se une al chat
USER_PARTEDla última conexión activa de un usuario se desconecta
STREAM_STARTEDse detecta una transmisión RTMP entrante
STREAM_STOPPEDuna transmisión RTMP entrante se desconecta (por ejemplo, OBS se detiene)
STREAM_TITLE_UPDATEDel título de la transmisión se actualiza
VISIBILITY-UPDATEun mensaje de chat enviado anteriormente se vuelve visible/invisible (configurado por un administrador/moderador)
FEDIVERSE_ENGAGEMENT_FOLLOWun usuario de Fediverse sigue su servidor

Cómo aceptar webhooks

  1. Visite /admin/webhooks en su servidor owncast.
  2. Haga clic en Crear Webhook.
  3. Introduzca la URL pública completa a un endpoint que pueda recibir este 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. Seleccione los eventos de los que desea recibir notificaciones.
  6. Guarde este nuevo webhook.

Su código

  1. En cualquier idioma, en cualquier tipo de servidor web, cree un endpoint que acepte una solicitud HTTP POST. Aquí es donde Owncast enviará eventos.
  2. Cada carga de evento tendrá una propiedad type que indica qué tipo de evento es y un objeto eventData que incluye propiedades específicas de ese evento.

Verificando las solicitudes de 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.

Webhooks de alto nivel

Los webhooks utilizan el método HTTP POST para enviar datos a un endpoint. El cuerpo de la solicitud del webhook es un JSON simple. Por lo tanto, el encabezado ContentType de la solicitud es application/json. Cada cuerpo de webhook sigue una estructura JSON simple.

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

donde

  • type da información sobre el tipo de evento que es (uno de los tipos de la tabla anterior).
  • eventData proporciona más información sobre el evento. La estructura de eventData es diferente para cada 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.

Ejemplos de lo que se puede esperar en eventData para cada tipo de evento se indican a continuación.

Ejemplos de 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: el campo user en el chat fue introducido con v0.0.8. Antes de v0.0.8 se utilizaba un campo de cadena simple con el nombre 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 se envía 10 segundos después de que se desconecta la última conexión activa de un usuario al chat. Si el usuario se reconecta durante ese tiempo, el evento se cancela. 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 es un ID de evento de webhook generado por Owncast. No es el ID de actor de Fediverse ni el ID de solicitud de seguimiento.
  • eventData.name es el nombre de visualización del seguidor.
  • eventData.username es el user@domain completo.
  • eventData.image es la URL del avatar del seguidor.
  • Unlike the other events, eventData does not include a status object.

clientId vs. user.id

Cuando un usuario está conectado desde múltiples dispositivos (o múltiples navegadores) al mismo tiempo con el mismo nombre de usuario, Owncast diferencia entre sus sesiones con un clientId. Los usuarios pueden tener múltiples clientIds: un solo clientId representa una única conexión a Owncast.

clientId es un número, mientras que user.id puede contener caracteres en mayúsculas, minúsculas y numéricos.

Pruebe webhooks en un entorno de desarrollo local

  1. Inicie Owncast localmente (por ejemplo, a través de docker).
  2. Visite localhost:8080/admin, autentíquese con el nombre de usuario: admin y la clave de transmisión predeterminada: abc123.
  3. Navegue al bloque de menú "Integración" en el lado izquierdo, haga clic en "Webhooks" y luego en "Crear Webhook".
  4. Establezca la dirección del Webhook para apuntar a su aplicación/integración (algo como: http://localhost:8100/webhooks/incoming).
  5. Seleccione los tipos de eventos que desea recibir.
  6. Presione "OK" para guardar el webhook.
  7. Inicie su integración/aplicación escuchando en la dirección configurada anteriormente.
    1. Opcionalmente, inicie un proxy de interceptación (por ejemplo, Burp) si desea inspeccionar los mensajes HTTP de antemano.
  8. Active eventos usted mismo (por ejemplo, escriba un mensaje en el chat, conecte/desconecte su software de transmisión a Owncast).

Pruebe webhooks antes de escribir cualquier código

Si desea probar cómo funcionan los webhooks antes de escribir código, cree un endpoint de prueba en RequestCatcher, y agregue la URL que le proporciona como un webhook en su administrador y vea cómo llegan las solicitudes.

Pruebe webhooks desde una instancia de producción de Owncast

Si ya tiene una instancia de Owncast ejecutándose en producción, escuchando en la web, es posible que desee usar ngrok para tunelar solicitudes HTTP a su entorno de desarrollo local.


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