Webhooks
Owncast prend en charge les Webhooks HTTP pour notifier les applications tierces (comme les chatbots) des événements sur le flux. En d'autres termes : les Webhooks enverront des événements à votre code lorsque des choses se produisent sur votre serveur Owncast.
Ce qui suit est une liste des événements pour lesquels vous pouvez être informé.
| Type d'événement | le webhook se déclenche quand ... |
|---|---|
| CHAT | un utilisateur envoie un message de chat |
| NAME_CHANGE | un utilisateur change son nom d'utilisateur |
| USER_JOINED | un utilisateur rejoint le chat |
| USER_PARTED | la dernière connexion active d'un utilisateur se déconnecte |
| STREAM_STARTED | un flux RTMP entrant est détecté |
| STREAM_STOPPED | un flux RTMP entrant se déconnecte (par exemple, OBS s'arrête) |
| STREAM_TITLE_UPDATED | le titre du flux est mis à jour |
| VISIBILITY-UPDATE | un message de chat précédemment envoyé devient visible/invisible (défini par un administrateur/modérateur) |
| FEDIVERSE_ENGAGEMENT_FOLLOW | un utilisateur du Fediverse suit votre serveur |
Comment accepter les webhooks
- Visitez
/admin/webhookssur votre serveur owncast. - Cliquez sur
Créer un Webhook. - Indiquez l'URL publique complète d'un point de terminaison capable de recevoir ce 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.
- Sélectionnez les événements pour lesquels vous souhaitez être informé.
- Enregistrez ce nouveau webhook.
Votre code
- Dans n'importe quel langage, sur n'importe quel type de serveur web, créez un point de terminaison qui accepte une requête HTTP
POST. C'est ici qu'Owncast enverra les événements. - Chaque charge utile d'événement aura une propriété
typequi indique de quel type d'événement il s'agit, et un objeteventDataqui inclut des propriétés spécifiques à cet événement.
Vérification des requêtes 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.
Webhooks de haut niveau
Les webhooks utilisent la méthode HTTP POST pour pousser des données vers un point de terminaison. Le corps de la requête du webhook est du JSON brut.
Ainsi, l'en-tête ContentType pour la requête est application/json. Chaque corps de webhook suit une structure JSON simple.
{
"type": "",
"eventData": {}
}
où
- type donne des informations sur quel type d'événement il s'agit (l'un des types dans le tableau ci-dessus).
- eventData donne plus d'informations sur l'événement. La structure de
eventDataest différente pour chaquetype.
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.
Des exemples de ce à quoi s'attendre pour chaque type d'événement se trouvent ci-dessous.
Exemples 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
}
}
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.
Note : le champ user dans le chat a été introduit avec v0.0.8. Avant v0.0.8, un simple champ chaîne avec le nom author était utilisé.
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 est envoyé 10 secondes après la dernière connexion active d'un utilisateur se déconnecte. Si l'utilisateur se reconnecte pendant ce temps, l'événement est annulé. 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.idest un ID d'événement webhook généré par Owncast. Ce n'est pas l'ID d'acteur Fediverse ou l'ID de demande de suivi.eventData.nameest le nom affiché du follower.eventData.usernameest le handle completuser@domain.eventData.imageest l'URL de l'avatar du follower.- Unlike the other events,
eventDatadoes not include astatusobject.
clientId vs. user.id
Lorsqu'un utilisateur est connecté depuis plusieurs appareils (ou plusieurs navigateurs) en même temps avec le même nom d'utilisateur, Owncast différencie ses sessions avec un clientId. Les utilisateurs peuvent avoir plusieurs clientIds - un seul clientId représente une seule connexion à Owncast.
clientId est un numéro, tandis que user.id peut contenir des caractères majuscules, minuscules et numériques.
Tester les webhooks dans un environnement de développement local
- Démarrez Owncast localement (par exemple via docker).
- Visitez
localhost:8080/admin, authentifiez-vous avec le nom d'utilisateur :adminet la clé de streaming par défaut :abc123. - Accédez au bloc de menu "Intégration" sur le côté gauche, cliquez sur "Webhooks", puis sur "Créer un Webhook".
- Définissez l'adresse de webhook pour pointer vers votre application/intégration (quelque chose comme :
http://localhost:8100/webhooks/incoming). - Sélectionnez les types d'événements que vous souhaitez recevoir.
- Appuyez sur "OK" pour enregistrer le webhook.
- Démarrez votre intégration/application à l'écoute sur l'adresse configurée précédemment.
- En option, démarrez un proxy d'interception (par exemple Burp) si vous souhaitez inspecter les messages HTTP au préalable.
- Déclenchez vous-même des événements (par exemple, écrivez un message dans le chat, connectez/déconnectez votre logiciel de streaming à Owncast).
Tester les webhooks avant d'écrire du code
Si vous souhaitez tester comment fonctionnent les webhooks avant d'écrire du code, créez un point de terminaison de test sur RequestCatcher, et ajoutez l'URL qu'il vous donne en tant que webhook dans votre administration et voyez les requêtes passer.
Tester les webhooks depuis une instance de production d'Owncast
Si vous avez déjà une instance d'Owncast fonctionnant en production, écoutant le web, vous voudrez peut-être utiliser ngrok pour acheminer les requêtes HTTP vers votre environnement de développement local.
Improve this page
See something missing or incorrect? Edit the English version of this page or help improve translations.


