Building a Low-Code Flex Activity Monitor with Twilio Event Streams and Slack

September 24, 2021
Written by
Adam King
Twilion
Reviewed by

Building a Low-Code Flex Activity Monitor with Twilio Event Streams and Slack

At Signal 2020, Twilio announced Event Streams - an API that allows developers to subscribe to a stream of interactions from Twilio in the form of well-defined Events. Since then, the team has been hard at work adding more and more event types from within the Twilio platform. Not only that, but Event Streams went into Public Beta in April 2021, adding a Webhook sink type on top of the AWS Kinesis sink type.

The availability of Event Streams, and the many event types which can be streamed to your application, has opened up myriad opportunities to use Event data from Twilio in new and interesting ways.

In this guide, we will create a Slack App which will post to a channel in Slack every time one of our Flex / TaskRouter workers changes their selected Activity status in Flex.

To do this, we will be doing the following:

  1. Using Twilio Event Streams (Public Beta) to subscribe to events occurring in the TaskRouter workspace.
  2. Creating a custom middleware to subscribe to the event stream and process the messages. For this basic example, we will be using Twilio Functions, although you can use the backend of your choice.
  3. Sending the message into a Slack workspace using the Incoming Webhook integration method.

All of the above will be asynchronous, and will be used in a ‘push only’ model. This means that instead of polling for data we will receive an Event when a status change occurs, which we will process and pass on to Slack.

App architecture diagram. Flow starts with a worker changing their TaskRouter activity, through TaskRouter, Event Stream, Serverless Function to a Slack channel, which is consumed by channel subscribers on Slack.

Requirements

You will need:

Create a placeholder function

Before you can configure an Event Stream, you need a destination to send Events to. For this example, I've used Twilio Functions - a serverless environment which allows us to create an event driven webhook without needing to worry about infrastructure.

You can follow this guide to create a new Service using Twilio Functions. I called my environment ‘event-stream-consumer’ but you can give it a name of your choice.

Once your Functions Service is created, add a new Protected Function using the ‘Add Function’ button at the top of the interface. Set a path for your Function's URL. This can be anything, and won't be made public so /webhook is fine. In the code editor you will be given some boilerplate code, but you can remove this for now and replace it with the simpler version below:

exports.handler = function(context, event, callback) {
 return callback(null);
};

This code will cause the function to reply with a 200 OK response whenever it gets a request from Twilio. Because the function is protected, it will give a 403 Unauthorized if you try to hit it from anywhere else (requests from Twilio are signed using the X-Twilio-Signature header). The empty 200 isn’t particularly useful to us, but by deploying this service we will get a URL to our service which we can use when configuring the Event Stream. We will come back to this function later to add the code to post a message into Slack.

Once the service is deployed by Saving our function and hitting the Deploy All button, we will be able to grab the URL for our new event consumer by selecting ‘Copy URL’ underneath the code editor. If you named your service event-stream-consumer it will look something like  https://event-stream-consumer-XXXX.twil.io/webhook.

Configuring a Twilio Event Stream

First create a Twilio Event Stream. Event Streams are a neat way to asynchronously push information about what is happening inside of Twilio products, out to your other applications.

Unlike the standard product level webhooks, which are ideal for use-cases where your application needs to reply immediately, Event Streams can be used when having an instant response is not required. In this case, as long as the messages are delivered to Slack within a timely manner, it doesn’t matter how many milliseconds it takes to get them there, so Event Streams are a good choice.

Configuring Event Streams can be done via API, or via UI in the Twilio Console. For the purposes of this blog I’ll be using the UI, but you could configure the same using the Twilio REST API or even by the Twilio CLI if you wanted to.

If you haven’t used Event Streams before, you will want to pin it to your Console. To do this, select “Explore Products” on the left hand menu bar. This can be found under the “Develop” tab.

You will find Event Streams under the Developer Tools section.

Screenshot of the Twilio console showing the Event Streams item under Developer Tools.

Select the pin icon to keep Event Streams permanently on your sidebar.

Once pinned, head over  to the Event Streams menu item, and select "Create new sink". Give your Sink a description and select a sink type. For this project choose the Webhook sink. If you are doing event processing on AWS you could choose AWS Kinesis instead. The description is yours to choose but make it something you will recognise in the future.

Twilio console screenshot showing "description" and "type" for the event sink

Next provide the webhook endpoint: here you should use the webhook URL created earlier on when we created our Function.

 

Console screenshot showing webhook sink details as described in prose.

Select POST as the HTTP method. Event streams can batch multiple events together into a single request to the webhook. It's simpler to handle each Event individually so disable this option by setting Batch to False.

Handling Events one at a time is usually simpler, but you might consider batching if the number of Events generated is so high that the rate of HTTP requests becomes hard to cope with.

screenshot showing "congratulations" for creating an event sink and a button for "Create Subscription"

Your Sink has been created. Click 'Create subscription', give your subscription a name, and scroll down to the event types.

For this example, we're only interested in TaskRouter events, and specifically only the Worker activity.update event. Event Streams allows us to select desired event types at a very granular level, so we are able to select only this type of event to be streamed to the webhook.

 

It is possible to subscribe to more than one event type at once, for example this app could be extended to post a message when a new worker is created in TaskRouter by adding the created event type.

A note on Schema Versions

From time to time, Twilio might alter the schema of a particular type of event. In order to not break compatibility with your application, when we do this, we create a new version of the Schema for that particular event. That means you can opt-in to the changes by changing to the new schema version, or you can stay on the original one.

You can find out more about this, along with information about all of the different event types that TaskRouter supports, by checking out the TaskRouter Event Types documentation.

I’ve selected Schema version 2 here, which at the time of writing is the most up to date.

Once you hit "Create Subscription", events will start flowing into our function. The only problem…. that function doesn’t actually do anything yet; we need to revisit it to add the functionality to call Slack. Before we can do that we need to configure Slack.

Configuring Slack

In order to add an Inbound Webhook to a Channel, you will need to be a Slack Administrator. It is possible to create your own Slack workspace if you need one for development purposes.

Whilst logged into your Workspace as an Administrator, navigate to https://api.slack.com/apps/

Screenshot of the slack console

Press ‘Create New App’ to get started, select ‘From Scratch’ as the type, and give your Slack app a name, and select your workspace from the dropdown menu.

Configure the Slack Incoming Webhook by selecting ‘Incoming Webhooks’ from the menu, and toggling the feature on.

You will now be able to create your own webhook on Slack to post messages to by clicking the ‘Add New Webhook to Workspace’ option.

When you select this option, you will be able to choose which Slack channel you want your messages to be posted into. I created a Slack channel specifically for this purpose in my instance.

Slack console, choosing which channel the bot should post into

Once you click Allow and Slack configures the new webhook, you will be able to see the Webhook URL listed at the bottom of the screen.

Slack console showing where to find the webhook URL

Make a note of the webhook URL, and head back to the Twilio Console to finish off the Function from earlier on.

Keep your webhook URL a secret, as it contains the token which allows your bot to post into your Slack instance.

Back to the Function

Firstly, add the URL for the Slack Webhook, as an Environment Variable called SLACK_WEBHOOK within the Function Service. This will allow you to use it in code from the context variable.

Twilio console screenshot showing setting an environment variable.

Secondly, we need a way to make a POST to the Slack webhook whenever the Function is triggered by an Event from Event Streams. To do this, add an NPM dependency called Axios.

Add Axios as an NPM dependency using the Dependencies screen. The latest version at time of writing of Axios is 0.21.4 - you can always check for newer versions here.

Twilio Functions dependencies console screenshot showing Axios added at 0.21.4

Now it is time to update the code in the webhook endpoint Function:

const axios = require("axios");

// This function will be called every time the Event Stream sends an Event
exports.handler = function(context, event, callback) {

   // We only want the first event in the payload.
   // Because we selected Batch=false, there will only be 1 anyway.
   var eventData = event[0].data;

   // We are only sending one event type, but lets filter out anything
   // else just in case.
   if (eventData.name == 'WorkerActivityUpdate') {

       // Post the message to the Slack webhook.
       axios.post(context.SLACK_WEBHOOK, {
               text: eventData.payload.event_description
           })
           .then(function() {
               // Yay! It worked!
               console.log("Message posted to Slack");
               return callback(null)
           })
           .catch(function() {
               // If we couldn't post to Slack for some reason,
               // return 200 anyway so that we continue to process events
               console.log("Something went wrong posting to Slack")
               return callback(null)
           });

   } else {
       // Handle non-Worker Activity events gracefully so that
       // Event Stream doesn't keep retrying them.
       console.log("Unrecognised Event Type. Skipping.");
       return callback(null);
   }
};

In short, this code parses through the event passed in from the Event Stream, performs some checks on it, and then uses it to build a HTTP POST over to Slack at the URL defined in our environment variable, as SLACK_WEBHOOK.

Don’t forget to deploy the latest version of the Function before we test things out.

Functions are a great way to handle small to medium workloads like this one.

If you have lots of events, for example if you are subscribing to lots of different event types in TaskRouter, you should read about the invocation limits on Functions.

What happens if the Function fails?

In this particular case, I am ignoring errors in my function, i.e. if the message cannot be posted to Slack, we return a HTTP 200 continue regardless. This was a decision based on it not being mission critical that these messages get through. If your function returns HTTP status 500 the event will be re-sent. Failed events will be held for up to 24 hours or 5 million events.

Testing the Integration

I have been using a Flex project, so I can test this out easily by changing my selected Activity in Flex. If you are using TaskRouter without Flex, you can test this by changing the status of your worker either through your integration, via API, or via the Twilio Console.

Animated gif showing a slack message arriving when a worker changes their activity status in Flex. Job done.

You can now see that when the Worker Activity changes, the Event Stream notifies our Function, which in turn posts an update to Slack.

All of this was achieved without any server infrastructure, and with only a few lines of code.

So what comes next?

Notifying a channel when a worker changes their activity in TaskRouter is a good example to demonstrate how powerful Event Streams can be when it comes to writing custom integrations with 3rd party applications.

There are lots of other Event Types to choose from, and there are many combinations of events which might be able to provide insight to your business when they occur.

Maybe you want to post to Slack when a new Worker is added? Or perhaps you want to post to Slack when a call ends which has taken longer than 30 minutes to resolve? Maybe you want to ping an Account Manager every time their customer has called into the Contact Centre?

Hopefully this guide has provided you with some ideas on how you could start to connect the dots of this type of application in a low-code fashion, and demonstrated the power of Event Streams.

If you're building with Twilio, be it Task Router, Flex, Event Streams or anything else, we'd love to hear about it. Find us on Twitter at @TwilioDevs, in our Community Forums, or at a developer event soon.