Перейти к основному содержимому

Вебхуки

Owncast поддерживает HTTP-вебхуки для уведомления сторонних приложений (например, чат-ботов) о событиях в трансляции. Проще говоря: вебхуки будут отправлять события в ваш код, когда что-то происходит на вашем сервере Owncast.

Ниже приведён список событий, о которых вы можете получать уведомления.

Тип событиявебхук срабатывает, когда ...
CHATпользователь отправляет сообщение в чат
NAME_CHANGEпользователь изменяет своё имя пользователя
USER_JOINEDпользователь подключается к чату
USER_PARTEDпоследнее активное подключение пользователя к чату отключается
STREAM_STARTEDобнаружен входящий RTMP-поток
STREAM_STOPPEDвходящий RTMP-поток отключается (например, OBS останавливается)
STREAM_TITLE_UPDATEDназвание трансляции обновлено
VISIBILITY-UPDATEранее отправленное сообщение в чате становится видимым/невидимым (установлено администратором/модератором)
FEDIVERSE_ENGAGEMENT_FOLLOWпользователь Fediverse подписывается на ваш сервер

Как принимать вебхуки

  1. Перейдите по пути /admin/webhooks на вашем сервере Owncast.
  2. Нажмите Create Webhook.
  3. Укажите полный публичный URL конечной точки, которая может принимать этот вебхук.
  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. Выберите события, о которых вы хотите получать уведомления.
  6. Сохраните этот новый вебхук.

Ваш код

  1. На любом языке и на любом типе веб-сервера создайте конечную точку, которая принимает HTTP POST запрос. Сюда Owncast будет отправлять события.
  2. В теле каждого события будет свойство type, указывающее тип события, и объект eventData с конкретными свойствами этого события.

Проверка запросов вебхука

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.

Общие сведения о вебхуках

Вебхуки используют метод HTTP POST для отправки данных на конечную точку. Тело запроса вебхука — простой JSON. Следовательно, заголовок ContentType для запроса — application/json. Тело каждого вебхука имеет простую JSON-структуру.

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

где

  • type содержит информацию о том, какого рода событие это (один из типов из таблицы выше).
  • eventData содержит дополнительную информацию о событии. Структура eventData различается для каждого 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.

Ниже приведены примеры eventData, ожидаемые для каждого типа события.

Примеры вебхуков

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.

Примечание: поле user в чате было введено в версии v0.0.8. До v0.0.8 использовалось простое строковое поле с именем 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 отправляется через 10 секунд после отключения последнего активного подключения пользователя к чату. Если пользователь повторно подключится в это время, событие отменяется. 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 — это идентификатор события вебхука, сгенерированный Owncast. Это не идентификатор участника Fediverse и не идентификатор запроса на подписку.
  • eventData.name — отображаемое имя подписчика.
  • eventData.username — полный идентификатор в формате user@domain.
  • eventData.image — это URL аватара подписчика.
  • Unlike the other events, eventData does not include a status object.

clientId против user.id

Когда пользователь одновременно подключается с нескольких устройств (или из нескольких браузеров) под одним и тем же именем пользователя, Owncast различает их сессии с помощью clientId. У пользователя может быть несколько clientId — один clientId представляет одно подключение к Owncast.

clientId — число, тогда как user.id может содержать заглавные, строчные и цифровые символы.

Тестирование вебхуков в локальной среде разработки

  1. Запустите Owncast локально (например, через docker).
  2. Перейдите по адресу localhost:8080/admin, аутентифицируйтесь с именем пользователя: admin и стандартным ключом потоковой передачи: abc123.
  3. Откройте блок меню «Integration» слева, нажмите «Webhooks», затем «Create Webhook».
  4. Установите Webhook Address, указывающий на ваше приложение/интеграцию (например: http://localhost:8100/webhooks/incoming).
  5. Выберите типы событий, которые вы хотите получать.
  6. Нажмите «OK», чтобы сохранить вебхук.
  7. Запустите ваше приложение/интеграцию, чтобы оно прослушивало ранее настроенный адрес.
    1. При желании запустите прокси-перехватчик (например, Burp), если вы хотите предварительно просмотреть HTTP-сообщения.
  8. Вызовите события самостоятельно (например, отправьте сообщение в чат, подключите/отключите ваше стриминговое ПО от Owncast).

Тестируйте вебхуки перед написанием кода

Если вы хотите проверить, как работают вебхуки, прежде чем писать код, создайте тестовую конечную точку на RequestCatcher, добавьте предоставленный URL в качестве вебхука в админке и наблюдайте проходящие запросы.

Тестирование вебхуков на рабочем экземпляре Owncast

Если у вас уже есть экземпляр Owncast, работающий в продакшене и доступный в интернете, вы можете использовать ngrok для туннелирования HTTP-запросов в вашу локальную среду разработки.


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