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.


Creating a webhook through the console

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());"/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.


Webhook details page: option to manage your webhook, and a historical overview of all webhook messages that THEOlive sent to your endpoint

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 happened
  • type is the type of event that got fired
  • object 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());"/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(; // Fictional code somewhere in your implementation
      case "channel.deleted":
        console.log("Something got deleted");
        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:

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 timestamp t and the JSON body of the request, signed with your secret.

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());"/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)
        .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

channel.creatingOccurs when someone initiated a creation of a channel.
channel.createdOccurs when the creation of a channel is completed.
channel.stoppingOccurs when someone decides to stop a channel.
channel.stoppedOccurs when all procedures to stop a channel are completed.
channel.deletingOccurs when someone decides to delete a channel.
channel.deletedOccurs when the process to delete a channel is completed.
channel.errorOccurs when a channel goes into error state.
channel.deployingOccurs when someone starts a channel and all infrastructure starts deploying.
channel.startingOccurs when all infrastructure is deployed and channel is starting up.
channel.waitingOccurs when channel is ready, but isn't receiving ingest yet.
channel.ingestingOccurs when channel is receiving ingest.
channel.playingOccurs when manifest is ready and channel can playout content.
channel.scheduledOccurs 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.warnOccurs when a warning message for a channel gets received.
channel.log.errorOccurs when an error message for a channel gets received.
channel.log.infoOccurs when an info message for a channel gets received.
channel.updatedOccurs when a channel got updated.
alias.createdOccurs when an new alias got created.
alias.disabledOccurs when an alias got disabled.
alias.enabledOccurs when an alias got enabled.
alias.deletedOccurs when the process to delete an alias is finished.
alias.updatedOccurs when an alias got updated.
webhook.createdOccurs when a webhook got created.
webhook.enabledOccurs when a webhook got enabled.
webhook.disabledOccurs when a webhook got disabled.
webhook.updatedOccurs when a webhook got updated.
webhook.deletedOccurs when a webhook got deleted.
scheduler.createdOccurs when a scheduler got created.
scheduler.updatedOccurs when a scheduler got updated.
scheduler.deletedOccurs when a scheduler got deleted.
scheduler.startingOccurs when a scheduler moves to starting mode: all connected channels will start up, stream will not be shown to your users yet.
scheduler.activeOccurs when a scheduler moves to active mode: the stream is now shown to your users.
scheduler.terminatedOccurs when a schedulers gets terminated.