Passer au contenu principal

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énementle webhook se déclenche quand ...
CHATun utilisateur envoie un message de chat
NAME_CHANGEun utilisateur change son nom d'utilisateur
USER_JOINEDun utilisateur rejoint le chat
USER_PARTEDla dernière connexion active d'un utilisateur se déconnecte
STREAM_STARTEDun flux RTMP entrant est détecté
STREAM_STOPPEDun flux RTMP entrant se déconnecte (par exemple, OBS s'arrête)
STREAM_TITLE_UPDATEDle titre du flux est mis à jour
VISIBILITY-UPDATEun message de chat précédemment envoyé devient visible/invisible (défini par un administrateur/modérateur)
FEDIVERSE_ENGAGEMENT_FOLLOWun utilisateur du Fediverse suit votre serveur

Comment accepter les webhooks

  1. Visitez /admin/webhooks sur votre serveur owncast.
  2. Cliquez sur Créer un Webhook.
  3. Indiquez l'URL publique complète d'un point de terminaison capable de recevoir ce 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. Sélectionnez les événements pour lesquels vous souhaitez être informé.
  6. Enregistrez ce nouveau webhook.

Votre code

  1. 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.
  2. Chaque charge utile d'événement aura une propriété type qui indique de quel type d'événement il s'agit, et un objet eventData qui inclut des propriétés spécifiques à cet événement.

Vérification des requêtes 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.

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": {}
}

  • 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 eventData est différente pour chaque 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.

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
}
}
  • 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.

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"]
}
}
  • 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 est 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.name est le nom affiché du follower.
  • eventData.username est le handle complet user@domain.
  • eventData.image est l'URL de l'avatar du follower.
  • Unlike the other events, eventData does not include a status object.

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

  1. Démarrez Owncast localement (par exemple via docker).
  2. Visitez localhost:8080/admin, authentifiez-vous avec le nom d'utilisateur : admin et la clé de streaming par défaut : abc123.
  3. Accédez au bloc de menu "Intégration" sur le côté gauche, cliquez sur "Webhooks", puis sur "Créer un Webhook".
  4. Définissez l'adresse de webhook pour pointer vers votre application/intégration (quelque chose comme : http://localhost:8100/webhooks/incoming).
  5. Sélectionnez les types d'événements que vous souhaitez recevoir.
  6. Appuyez sur "OK" pour enregistrer le webhook.
  7. Démarrez votre intégration/application à l'écoute sur l'adresse configurée précédemment.
    1. En option, démarrez un proxy d'interception (par exemple Burp) si vous souhaitez inspecter les messages HTTP au préalable.
  8. 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.

Contributors to this documentation
Gabe KangasGabe Kangastaintedcyphertaintedcypher
T
Tournesol
D
Dev Gupta
L
Lili
mtabrizmtabriz
R
Raffael Rehberger