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 evento | o webhook é acionado quando ... |
|---|---|
| CHAT | usuário envia uma mensagem no chat |
| NAME_CHANGE | usuário altera seu nome de usuário |
| USER_JOINED | usuário entra no chat |
| USER_PARTED | a última conexão ativa do chat de um usuário é desconectada |
| STREAM_STARTED | uma transmissão RTMP de entrada é detectada |
| STREAM_STOPPED | uma transmissão RTMP de entrada é desconectada (ex.: OBS para) |
| STREAM_TITLE_UPDATED | o título da transmissão é atualizado |
| VISIBILITY-UPDATE | uma mensagem de chat previamente enviada torna-se visível/invisível (definido por um Administrador/Moderador) |
| FEDIVERSE_ENGAGEMENT_FOLLOW | um usuário do Fediverse segue seu servidor |
Como aceitar webhooks
- Visite
/admin/webhooksno seu servidor Owncast. - Clique em
Create Webhook. - Insira a URL pública completa para um endpoint que possa receber este 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.
- Selecione os eventos sobre os quais você deseja ser notificado.
- Salve este novo webhook.
Seu código
- 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. - Cada payload de evento terá uma propriedade
typeque indica qual é o tipo de evento, e um objetoeventDataque inclui propriedades específicas desse evento.
Verificando requisições de 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.
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 cadatype.
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
}
}
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.
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"]
}
}
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é 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 completouser@domain.eventData.imageé a URL para o avatar do seguidor.- Unlike the other events,
eventDatadoes not include astatusobject.
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
- Inicie o Owncast localmente (ex.: via Docker).
- Visite
localhost:8080/admin, autentique-se com Nome de usuário:admine a chave de streaming padrão:abc123. - Navegue até o bloco de menu "Integração" no lado esquerdo, clique em "Webhooks", depois em "Criar Webhook".
- Defina o Endereço do Webhook para apontar para sua aplicação/integração (algo como:
http://localhost:8100/webhooks/incoming). - Selecione os tipos de eventos que você deseja receber.
- Pressione "OK" para salvar o webhook.
- Inicie sua integração/aplicação para escutar no endereço configurado anteriormente.
- Opcionalmente, inicie um proxy de interceptação (ex.: Burp) se quiser inspecionar as mensagens HTTP antes.
- 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.


