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.

512512

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.

18821882

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());

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.

14251425

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. Currently we only support events on channels. id is the ID of the object, which for now will always be a channel ID
  • 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 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());

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

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