ウェブフック(Webhooks)?
Owncastは、ストリーム上のイベントについてサードパーティのアプリケーション(チャットボットなど)に通知するためのHTTPウェブフックをサポートしています。 言い換えると:Owncastサーバーで何かが起こると、ウェブフックがイベントをあなたのコードに送信します。
以下は通知を受け取ることのできるイベントの一覧です。
| イベントタイプ | ウェブフックがトリガーされるとき... |
|---|---|
| チャット | ユーザーがチャットメッセージを送信したとき |
| 名前変更 | ユーザーがユーザー名を変更したとき |
| ユーザー参加 | ユーザーがチャットに参加したとき |
| ユーザー退出 | ユーザーの最後のアクティブなチャット接続が切断されたとき |
| 配信開始 | 着信RTMPストリームが検出されたとき |
| 配信停止 | 着信RTMPストリームが切断されたとき(例: OBSが停止した場合) |
| 配信タイトル更新 | 配信のタイトルが更新されたとき |
| 表示状態更新 | 以前送信されたチャットメッセージの表示/非表示が切り替えられたとき(管理者/モデレーターによって設定) |
| Fediverseフォロー | Fediverseユーザーがあなたのサーバーをフォローしたとき |
ウェブフックを受け取る方法
- Owncastサーバーで
/admin/webhooksにアクセスしてください。 Create Webhookをクリックしてください。- このウェブフックを受信できるエンドポイントの、公開された完全なURLを入力してください。
- 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.
- 通知を受けたいイベントを選択してください。
- この新しいウェブフックを保存してください。
あなたのコード
- 任意の言語、任意の種類のウェブサーバーで、HTTPの
POSTリクエストを受け付けるエンドポイントを作成します。 ここがOwncastがイベントを送信する場所です。 - 各イベントペイロードには、どのイベントタイプかを示す
typeプロパティと、そのイベントの特定のプロパティを含むeventDataオブジェクトが含まれます。
ウェブフックリクエストの検証
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.
ウェブフックの概要
ウェブフックはHTTP POSTメソッドを利用してデータをエンドポイントにプッシュします。 ウェブフックのリクエストボディはプレーンなJSONです。
したがってリクエストのContentTypeヘッダーはapplication/jsonです。 各ウェブフックボディはシンプルなJSON構造に従います。
{
"type": "",
"eventData": {}
}
ここで
- type はどの種類のイベントかを示します(上の表のタイプのうちの1つ)。
- 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
}
}
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.
注: チャットの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"]
}
}
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はOwncastによって生成されたウェブフックイベントのIDです。 これはFediverseのアクターIDやフォローリクエストIDではありません。eventData.nameはフォロワーの表示名です。eventData.usernameは完全なuser@domainハンドルです。eventData.imageはフォロワーのアバターへのURLです。- Unlike the other events,
eventDatadoes not include astatusobject.
clientId vs. user.id
同じユーザー名で複数のデバイス(または複数のブラウザ)から同時に接続している場合、OwncastはセッションをclientIdで区別します。 ユーザーは複数のclientIdを持つことができます。単一のclientIdはOwncastへの単一の接続を表します。
clientIdは数値ですが、user.idは大文字、小文字、数字の文字を含むことができます。
ローカル開発環境でウェブフックをテストする
- ローカルでOwncastを起動します(例: docker経由)。
localhost:8080/adminにアクセスし、ユーザー名adminとデフォルトのストリーミングキーabc123で認証します。- 左側の「Integration」メニューブロックに移動し、「Webhooks」をクリックしてから「Create Webhook」をクリックします。
- Webhookアドレスをアプリケーション/統合先(例:
http://localhost:8100/webhooks/incoming)に設定します。 - 受信したいイベントの種類を選択します。
- 「OK」を押してウェブフックを保存します。
- 先ほど設定したアドレスで統合/アプリケーションを起動して受信を待ちます。
- 必要に応じて、HTTPメッセージを事前に確認したい場合はインターセプトプロキシ(例: Burp)を起動します。
- 自分でイベントを発生させます(例: チャットにメッセージを書く、ストリーミングソフトをOwncastに接続/切断する)。
コードを書く前にウェブフックをテストする
コードを書く前にウェブフックの動作を確認したい場合は、RequestCatcherでテストエンドポイントを作成し、管理画面にそのURLをウェブフックとして登録してリクエストが届くのを確認してください。
本番環境のOwncastインスタンスからウェブフックをテストする
既に本番環境でインターネットに公開されているOwncastインスタンスがある場合、HTTPリクエストをローカル開発環境にトンネルするためにngrokを利用すると便利です。
Improve this page
See something missing or incorrect? Edit this page and improve the documentation for everyone.


