Ir para o conteúdo principal

Webhooks

O Owncast suporta Webhooks HTTP para notificar aplicações de terceiros (como chatbots) sobre eventos na transmissão. Em outras palavras: Webhooks enviarão eventos para seu código quando algo acontecer no seu servidor Owncast.

A seguir, uma lista de eventos sobre os quais você pode ser notificado.

Tipo de eventoo webhook é acionado quando ...
CHATusuário envia uma mensagem no chat
NAME_CHANGEusuário altera seu nome de usuário
USER_JOINEDusuário entra no chat
USER_PARTEDa última conexão ativa do chat de um usuário é desconectada
STREAM_STARTEDuma transmissão RTMP de entrada é detectada
STREAM_STOPPEDuma transmissão RTMP de entrada é desconectada (ex.: OBS para)
STREAM_TITLE_UPDATEDo título da transmissão é atualizado
VISIBILITY-UPDATEuma mensagem de chat previamente enviada torna-se visível/invisível (definido por um Administrador/Moderador)
FEDIVERSE_ENGAGEMENT_FOLLOWum usuário do Fediverse segue seu servidor

Como aceitar webhooks

  1. Visite /admin/webhooks no seu servidor Owncast.
  2. Clique em Create Webhook.
  3. Insira a URL pública completa para um endpoint que possa receber 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. Selecione os eventos sobre os quais você deseja ser notificado.
  6. Salve este novo webhook.

Seu código

  1. Em qualquer linguagem, em qualquer tipo de servidor web, crie um endpoint que aceite uma requisição HTTP POST. É aqui que o Owncast enviará os eventos.
  2. Cada payload de evento terá uma propriedade type que indica qual é o tipo de evento, e um objeto eventData que inclui propriedades específicas desse evento.

Verificando requisições 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.

Visão geral dos webhooks

Webhooks utilizam o método HTTP POST para enviar dados a um endpoint. O corpo da requisição do webhook é JSON puro. Portanto, o cabeçalho ContentType da requisição é application/json. Cada corpo de webhook segue uma estrutura JSON simples.

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

onde

  • type fornece informações sobre que tipo de evento é (um dos tipos da tabela acima).
  • eventData fornece mais informações sobre o evento. A estrutura de eventData é 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.

Exemplos do que esperar em eventData para cada tipo de evento estão abaixo.

Exemplos de Webhooks

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.

Observação: o campo user no chat foi introduzido com v0.0.8. Antes do v0.0.8, era usado um campo string simples com o nome 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 é enviado 10 segundos após a última conexão ativa do chat de um usuário ser desconectada. Se o usuário reconectar durante esse período, o evento é cancelado. 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 é um ID de evento de webhook gerado pelo Owncast. Não é o ID do ator do Fediverse nem o ID do pedido de seguimento.
  • eventData.name é o nome de exibição do seguidor.
  • eventData.username é o identificador completo user@domain.
  • eventData.image é a URL para o avatar do seguidor.
  • Unlike the other events, eventData does not include a status object.

clientId vs. user.id

Quando um usuário está conectado de múltiplos dispositivos (ou múltiplos navegadores) ao mesmo tempo com o mesmo nome de usuário, o Owncast diferencia suas sessões com um clientId. Os usuários podem ter múltiplos clientIds — um único clientId representa uma única conexão ao Owncast.

clientId é um número, enquanto user.id pode conter caracteres maiúsculos, minúsculos e numéricos.

Teste webhooks em um ambiente de desenvolvimento local

  1. Inicie o Owncast localmente (ex.: via Docker).
  2. Visite localhost:8080/admin, autentique-se com Nome de usuário: admin e a chave de streaming padrão: abc123.
  3. Navegue até o bloco de menu "Integração" no lado esquerdo, clique em "Webhooks", depois em "Criar Webhook".
  4. Defina o Endereço do Webhook para apontar para sua aplicação/integração (algo como: http://localhost:8100/webhooks/incoming).
  5. Selecione os tipos de eventos que você deseja receber.
  6. Pressione "OK" para salvar o webhook.
  7. Inicie sua integração/aplicação para escutar no endereço configurado anteriormente.
    1. Opcionalmente, inicie um proxy de interceptação (ex.: Burp) se quiser inspecionar as mensagens HTTP antes.
  8. Dispare eventos você mesmo (ex.: escreva uma mensagem no chat, conecte/desconecte seu software de streaming ao Owncast).

Teste webhooks antes de escrever qualquer código

Se você quiser testar como os webhooks funcionam antes de escrever qualquer código, crie um endpoint de teste em RequestCatcher, adicione a URL que ele fornece como um webhook no seu admin e veja as requisições chegando.

Teste webhooks a partir de uma instância de produção do Owncast

Se você já tem uma instância Owncast rodando em produção, acessível pela internet, talvez queira usar ngrok para tunelar requisições HTTP para seu ambiente de desenvolvimento local.


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