How to run an AWS Lambda function when a broadcast starts, stops or changes

In this guide we will look at how to programmatically react to broadcast changes, by listening to webhook events.

We will use AWS Lambda to run our JavaScript snippets on every event. The code would be very similar on Google Cloud, Heroku or your custom VPS and the concepts are the same regardless of your choice of programming language.

Create a function

Let's first create a function. Log in to the AWS console, select Lambda, then press Create function.

Choose Author from scratch, give the function a descriptive name.

IAM execution role

Next, create and assign a suitable IAM role to our functions. This role determines which AWS services and resources the function is allowed to access. If our function was supposed to store files on S3 for example, we would assign read/write access to a suitable S3 bucket through this IAM role.

There are a few templates available. At minimumn we should probably allow logging via CloudWatch and at the moment we don't need anything else.

Unfortunately the logging-only template from the docs is not available in the template dropdown list in the create function form. Hence, instead of choosing Create a new role from one or more templates, choose Create a custom role.

This opens a new window with a new form, which happens to be pre-populated with the logging rules we want.

Amazon suggests we name this role lambda_basic_execution, which sounds quite appropriate: Whenever we start working on a new Lambda function, we can choose to start with this lambda_basic_execution role and switch to a more specialized role when our function's needs grow.

When we submit the Create a custom role form, we are returned back to the Create function form, with our new role selected.

Let's continue by pressing the Create function button!

This brings up the main page for our function, including an inline code editor.

First run

Let's see what happens if we run our function in its current state. Press the Test button in the upper right corner.

A Lambda function execution expects some input - a trigger event - which includes a JSON payload. The test button asks us to create a sample event the first time we press it. At this point it does not matter what we send: let's just assign a name to our test event and use the default values.

Press the test button again, this time with our test event selected.

A loader appears and after a few seconds we should get a success message.

We can see that the sample code provided by Amazon returns a payload in the form that API Gateway expects: an HTTP status code and an HTTP body. This will be useful later.

Logging

Next, let's try out CloudWatch logging, to get some insight into the internal state of our function when it runs.

For example, we can log the event variable and see what it typically contains. Scroll down to the code editor and update the function code as follows:

exports.handler = async (event) => {
    console.log('Received event', event);
    const response = {
        statusCode: 200,
        body: JSON.stringify('Hello from Lambda!'),
    };
    return response;
};

Then press the Save button in the top right corner.

Then press Test again and scroll back to the top.

The green Execution result button has an excerpt from the Lambda function's CloudWatch logs towards the bottom and we should see our event logged there, which is indeed the exact payload that we defined earlier!

These logs are persisted and you can access them after the fact, which is useful when our Lambda function is triggered through other means than the test button. Scroll to the top and switch from the Configuration tab to the Monitoring tab, then click View logs in CloudWatch.

This brings up a list of log streams. A new stream is created each time we save a new version of our function, hence we currently have two streams in this example:

Click the newest stream and note that our test event is indeed visible here too.

Responding to HTTP requests via API Gateway

Now that we have a basic understanding of how Lambda works, let's try to respond to an HTTP request. Webhook events are just incoming HTTP requests after all.

In the Google Cloud tutorial, the function automatically got a public URL by just choosing Trigger: HTTP, but on AWS a bit more configuration is required.

A Lambda function can be invoked in a variety of ways, as illustrated by the Designer tool located above the code editor. API Gateway is the appropriate tool for dealing with HTTP requests directly.

In the designer, add API Gateway as a trigger. In the Configure trigger box that appears, choose Create a new API and select Open as security model.

Then press Add.

Security

API Gateway can manage a database of API keys for you and this can be a good choice sometimes. However, API Gateway only supports keys provided as a header, not as a part of the URL and the webhook interface only allows us to specify a URL where to post events.

Hence, we need to choose Open, to let traffic without a valid x-api-key header through to our Lambda function. On the other hand, our API is exposed on a hard-to-guess url, which has a similar effect as an API key provided as part of the url would, as long as you don't share the API url with anyone but Bambuser's webhook config.

The interface now alerts you to the fact that you need to press the Save button again. Do that.

A few moments later, our function gets assigned a public URL, yay!

Try opening it in your browser.

If everything went as planned, you should see the output from our function, Hello from Lambda! on your screen.

And if we jump back into CloudWatch, we get some insight into the kind of event payload API Gateway produces each time an HTTP request invokes our function.

A POST request works too!

curl -i -XPOST -H 'Content-type: application/json' -d '{"message": "a custom message"}' https://7sbo100m09.execute-api.us-west-2.amazonaws.com/default/bambuser-webhook-receiver

And we can see through CloudWatch that our provided POST body JSON payload is received through the event object, but not automatically deserialized.

This is actually pretty close to how webhooks operate, not much more to it: When a broadcast is created or updated, Bambuser's webhook servers will send a POST request with a JSON payload to all registered listener urls.

Hooking up Bambuser webhooks

We now have the tools we need to receive a webhook event.

To get some real-world data, let's register our function as a listener on Bambuser dashboard's Developer Page.

Next, click "Test" in Bambuser Dashboard and check the CloudWatch logs again.

Voilá!

That payload was not too exciting though... Let's create a real broadcast and see what happens. Use Bambuser's mobile app or the "go live" button in Bambuser Dashboard or any other broadcasting approach you prefer.

That's more like it!

During a typical broadcast lifecycle you can expect one add action and several update actions. When a live broadcast ends you should see at least one update where type has the value archived.

You now have the ability to perform any custom action you like in near-realtime, perhaps maintaining a database, sending emails to followers or cross-posting a link on Twitter or Slack.

Be sure to check out the REST API:s as well, which lets you query the broadcast database on demand as well as update and remove content.