Real-time updates with Webhooks
This guide describes how you can leverage THEOlive webhooks to receive real-time updates about your THEOlive components. You don't need to query for updates yourself anymore: we send you information when certain events happen. This guide will first explain what webhooks are, how you can create and manage them via the THEOlive console and/or API, and finally describe how you can secure your webhooks.
1. How it works
When creating a webhook, you pass an endpoint URL that you control to THEOlive. This endpoint will be used by THEOlive to send real-time updates when certain events happen. It's important that this an HTTPS endpoint which is reachable by THEOlive.
You can choose on which events THEOlive has to send a message to your endpoint. For example: you can choose to get notified when a channel reaches the "playing" mode, but also when it got deleted, stopped, created, etc. A full list of available events can be found at the bottom of this page.
The main advantage of webhooks is that THEOlive informs you in real-time when an event happens, and you don't have to pull our API continuously to check if there are any updates on an object.
2. Create and manage your webhooks
You can create a webhook through the THEOlive API, or via the management console. In this document we will mainly describe the console approach, the full API reference for the actions and methods can be found here.
To create a webhook, click on "Webhooks" on the sidebar, followed by "Create". A webhook expects a name, a valid HTTPS endpoint, an optional description and a list of events it should listen and act on. A full list of events can be found at the bottom of this page. You can also select to listen to all possible events. When using the API, you can pass events: ["*"]
when a webhook should listen to all events.
When such an event happens, THEOlive will try to send a request to your endpoint. It's important that you inform us as soon as possible that you received the request with a status 200 code: requests that take longer than 3 seconds will be terminated by THEOlive and marked as failed. When a webhook has too many failed attempts, THEOlive will disable the webhook automatically. Below, we show a small code example of an Express app where we inform THEOlive we have received the request, before we do all other actions:
const express = require("express");
const port = 3000;
var app = express();
app.use(express.json());
app.post("/webhooks", (req, res) => {
res.send("ok"); // let us know ASAP you received our request
// your app-specific implementation like updating your database etc.
});
app.listen(port, () => {
console.log(`Example app listening on port ${port}`);
});
Newly created webhooks will be disabled by default
When you create a webhook, it won't be active yet. THEOlive does this so you can test things out before we actually start firing events to your endpoint. When you think you are fully ready to receive webhook mesages from THEOlive, you can enable the webhook through the console, or via the /webhooks/webhook-id/enable endpoint.
When a webhook is created, you can update or delete it through the API or management console. At the details page you can also see the history of all message that have been sent to your endpoint, and if they've failed or not.
3. How to act on event
THEOlive sends a JSON body along with the request which contains all necessary information. This JSON body has the following format:
{
"created": 1661435007,
"type": "channel.playing",
"object": {
"type": "channel",
"id": "<my-channel-id>"
},
"livemode": true
}
created
is a Unix timestamp when the event happenedtype
is the type of event that got firedobject
contains two properties:type
is the type of the object,id
the ID of it.livemode
: a boolean to indicate if the request was a test or not
In your implementation, you can use this data to act on it accordingly. In the example below, we will act differently on receiving a channel.playing
and channel.deleted
event:
const express = require("express");
const port = 3000;
var app = express();
app.use(express.json());
app.post("/webhooks", (req, res) => {
res.send("ok"); // let us now ASAP you received our request
const data = req.body;
switch(data.type) {
case "channel.playing":
setChannelActiveInMyDatabase(data.object.id); // Fictional code somewhere in your implementation
case "channel.deleted":
console.log("Something got deleted");
default:
console.log("No code for this type implemented yet");
}
});
app.listen(port, () => {
console.log(`Example app listening on port ${port}`);
});
4. Add security to your webhooks
THEOlive makes use of webhook signatures to verify that the webhook request was coming from us, and not from a server that was acting like THEOlive. On every request THEOlive will send a THEOlive-Signature
header that you can use to check if it was really THEOlive that sent the message.
How does the verification work?
Upon the creation of a webhook, you'll receive a secret
that looks like theosec_some-random-characters
. This is a key you can use in a later stage to do the necessary security checks.
When receiving a webhook request from THEOlive, the THEOlive-Signature
will always get sent with it and looks similar to the following:
t=1659622850,h=6bbe0553922a31ea48cb3af9903616c3a8b65351434653251949480f2a91c037
This is a string that you have to de-structure yourself to get the value of t
and h
:
t
is the Unix timestamp when the request got made. THEOlive adds this to prevent replay attacks.h
is a hexadecimal HMAC SHA-256 hash, which is a combination of the timestampt
and the JSON body of the request, signed with yoursecret
.
As a user, you have to try to recreate the hash h
yourself: you take t
, add a stringified version of your JSON body after it, and hash it with the secret
you received upon webhook creation. If the result equals to h
, you can be sure the request was made by THEOlive. If not, someone tried to act like THEOlive.
Full code sample
const express = require("express");
const crypto = require("crypto");
const port = 3400;
var app = express();
app.use(express.json());
app.post("/webhooks", (req, res) => {
res.send("ok"); // let us now ASAP you received our request
const secret = "theosec_myverylittlesecret"; // secret received on creation, can be retrieved anytime at the THEOlive console
const data = req.body; // body of the request
// 1. Grab THEOlive-Signature from header
const theoHeader = req.header("THEOlive-Signature"); // t=1659622850,h=6bbe0553922a31ea48cb3af9903616c3a8b65351434653251949480f2a91c037
// 2. Split string: you'll receive a timestamp (t) and hash (h)
const timestampPart = theoHeader.split(",")[0]; // t=1659622850
const hashPart = theoHeader.split(",")[1]; // h=6bbe0553922a31ea48cb3af9903616c3a8b65351434653251949480f2a91c037
// 3. Get the values for both the timestamp and hash
const timestampValue = timestampPart.split("=")[1]; // 1659622850
const hashValue = hashPart.split("=")[1]; // 6bbe0553922a31ea48cb3af9903616c3a8b65351434653251949480f2a91c037
// 4. Prepare the hash string: stringify the request body
const dataAsString = JSON.stringify(data); // {"created":1659622849,"data":{"id":"9uiwh5owynp3ympsxqtjxa3zd","type":"channel"},"type":"channel.created","livemode":false}
const stringToBeHashed = `${timestampValue}.${dataAsString}`; // 1659622850.{"created":1659622849,"data":{"id":"9uiwh5owynp3ympsxqtjxa3zd","type":"channel"},"type":"channel.created","livemode":false}
// 5. Make a HMAC SHA-256 hash, using your secret as a key
const hashToCheck = crypto
.createHmac("sha256", secret)
.update(stringToBeHashed)
.digest("hex"); // 6bbe0553922a31ea48cb3af9903616c3a8b65351434653251949480f2a91c037
// 6. Check if both hashes are the same
if (hashToCheck === hashValue) {
// all good, continue with code
} else {
// data not coming from THEOlive, throw an error
}
});
app.listen(port, () => {
console.log(`Example app listening on port ${port}`);
});
5. List of possible events
Type | Description |
---|---|
channel.creating | Occurs when someone initiated a creation of a channel. |
channel.created | Occurs when the creation of a channel is completed. |
channel.stopping | Occurs when someone decides to stop a channel. |
channel.stopped | Occurs when all procedures to stop a channel are completed. |
channel.deleting | Occurs when someone decides to delete a channel. |
channel.deleted | Occurs when the process to delete a channel is completed. |
channel.error | Occurs when a channel goes into error state. |
channel.deploying | Occurs when someone starts a channel and all infrastructure starts deploying. |
channel.starting | Occurs when all infrastructure is deployed and channel is starting up. |
channel.waiting | Occurs when channel is ready, but isn't receiving ingest yet. |
channel.ingesting | Occurs when channel is receiving ingest. |
channel.playing | Occurs when manifest is ready and channel can playout content. |
channel.scheduled | Occurs when a channel has a scheduled start time due to a scheduler. The channel itself is ready, but the stream will be available for the viewers at scheduler start time. |
channel.log.warn | Occurs when a warning message for a channel gets received. |
channel.log.error | Occurs when an error message for a channel gets received. |
channel.log.info | Occurs when an info message for a channel gets received. |
channel.updated | Occurs when a channel got updated. |
alias.created | Occurs when an new alias got created. |
alias.disabled | Occurs when an alias got disabled. |
alias.enabled | Occurs when an alias got enabled. |
alias.deleted | Occurs when the process to delete an alias is finished. |
alias.updated | Occurs when an alias got updated. |
webhook.created | Occurs when a webhook got created. |
webhook.enabled | Occurs when a webhook got enabled. |
webhook.disabled | Occurs when a webhook got disabled. |
webhook.updated | Occurs when a webhook got updated. |
webhook.deleted | Occurs when a webhook got deleted. |
scheduler.created | Occurs when a scheduler got created. |
scheduler.updated | Occurs when a scheduler got updated. |
scheduler.deleted | Occurs when a scheduler got deleted. |
scheduler.starting | Occurs when a scheduler moves to starting mode: all connected channels will start up, stream will not be shown to your users yet. |
scheduler.active | Occurs when a scheduler moves to active mode: the stream is now shown to your users. |
scheduler.terminated | Occurs when a schedulers gets terminated. |
Updated over 1 year ago