Twitch EventSub - The Direct Approach to Getting Started With It
Tags: tutorial, crystal
Published: 2021-06-02 05:51:08

# Start Here

You want to write something to react to Twitch Events. You look at the docs, and maybe it is a little confusing. You see a table of contents that looks like this:

![image](https://www.therelicans.com/remoteimages/uploads/articles/viziatamr19m2wilwzyf.png)

Authentication

Twitch API

EventSub

PubSub

If you click around a little, you might get more confused. There are a lot of details, and there seems to be more than one way to do things, at least in some cases, and it is easy to lose track of the thread of things amidst all of the information.

You might look at PubSub, and you might think, "Wow! This seems very simple and straightforward!"

You would be right. It is. Then you might discover that it only provides access to a small percentage of the Twitch events, and things that you really want, like *channel.follow* notifications, are not available.

Eventually, you look more at EventSub. This is the future. This is what everyone should be using:

![image](https://www.therelicans.com/remoteimages/uploads/articles/6zlhgyi6o0jq0mxt8ydm.png)
 
"Awesome!" you think. "Let's dive in and....wait, how do I use this?"

To use it, you have to subscribe to events. And to subscribe to events, you have to have authorization. And to be authorized...there are no links there, but in the left menu there is an *Authentication* link, so you click there, and...

OK, more steps. Registering the app. Then getting a token. Only there are 5 different kinds. Which one is needed? OK, and how do I get that? And how do I use it once I have it?

You get the picture. The details are all there, in the documentation, but it is a maze of twisty little passages, and you may spend a lot of time flipping from one page to another to piece it all together.

Fear not. I've done the flipping. I've got your back.

## Step 1 -- You need to register your app

First things first -- app registration. In order for your app to interact with eventsub, Twitch wants to know about it as a unique entity. So, you need to register it.

Go to [https://dev.twitch.tv/console](https://dev.twitch.tv/console). On the right of the page, there should be a section labeled *Applications*. Click on the ![Register Your Application](https://www.therelicans.com/remoteimages/uploads/articles/19wipr547908xlyz5xbh.png) button.

In the form that is on the next page, you have to provide a few elements of information.

![image](https://www.therelicans.com/remoteimages/uploads/articles/9z5mh56ughnbn5iucs0i.png)

Give your app a name.

![image](https://www.therelicans.com/remoteimages/uploads/articles/khtrtdmx6amnuu4178oq.png)

Unless you know with certainty what this is or will be, just put `https://localhost` in, and press the *Add* button.

![image](https://www.therelicans.com/remoteimages/uploads/articles/eus40lenybem427qwctx.png)

Finally, select an appropriate category for your app, and then press the *Create* button.

You will be taken to the Apps Console, where you will see something like this:

![image](https://www.therelicans.com/remoteimages/uploads/articles/alyja1onc0c2t28qdg4o.png)

The next step is to click the *Manage* button on your newly created app. You will be taken back to a page that looks just like the one where you created the app, except that it has a few extra bits of information at the bottom:

![image](https://www.therelicans.com/remoteimages/uploads/articles/an9trucskqpgfj8xcdtv.png)

The Client ID is public information. There is no need to hide that. The application secret, however, is, well, secret. It will be generated when you press the ![image](https://www.therelicans.com/remoteimages/uploads/articles/l0nc65vc6yr9wg94a1pm.png) button. Take note of the string of characters that are revealed when you press that button, and save it somewhere else for later use. You can not see it again once you leave the page, so if you lose the secret, you will have to regenerate a new one, which expires the old one.

See? This is easy so far.

The next thing that you need to do is to generate an access token for your application, using the *Client-ID* and *Client-Secret* that you have just generated.

## Step 2 -- Generate your access token

The next step is to generate your access token. There are five different types of access token, but the one that is needed for EventSub is the [OAuth Client Credentials Flow](https://dev.twitch.tv/docs/authentication/getting-tokens-oauth/#oauth-client-credentials-flow) token type.

This type of token is an Application Access Token, intended only for server-to-server API requests, which is exactly what is needed for EventSub activities.

To get your very own shiny, new Application Access Token, you need to make a POST request to the Twitch API. The documentation detailing what is needed is in the link above, but it has the potential to be a little bit confusing.

HTTP *GET* requests pass extra parameters within the query string of the URL.

`GET /foo?param1=abc&param2=123`

HTTP *POST* requests typically pass parameters within the body of the HTTP request. The examples show a *POST* being done with the data in the URL as a Query String.

![image](https://www.therelicans.com/remoteimages/uploads/articles/q7gmbibds4t9s4g4dzfy.png)
 
While this is not illegal per the HTTP spec, it is not typical, nor is it required when working with the Twitch API.

If you want to generate an access key manually, you can do this using `curl`:

```bash
curl -d client_id=$TWITCH_CLIENT_ID \
     -d client_secret=$TWITCH_CLIENT_SECRET \
     -d grant_type=client_credentials \ 
     https://id.twitch.tv/oauth2/token
```

If your *Client-ID* and your *Client-Secret* are stored in a couple of environment variables, *TWITCH_CLIENT_ID* and *TWITCH_CLIENT_SECRET*, the above should work from most unix-like command lines. 

What is returned is one of two things. If either your *Client-ID* is invalid, there will be an error like this:

```json
{"status":400,"message":"invalid client"}
```

If the *Client-ID* is valid, but the *Client-Secret* is invalid, the error will look like this:

```json
{"status":403,"message":"invalid client secret"}
```

If both are valid, the result will be returned in JSON something like this:

```json
{"access_token":"q3b5n90ua7du0mgpwl149ge2yf90r0","expires_in":4776914,"token_type":"bearer"}
```

The value for the `access_token` key is your golden ticket. It is what permits you to access the rest of the EventSub API.

If you want or need your software to be able to generate an access key at will, though, you will need to issue the request and receive the response programmatically. The details of this may vary considerably depending on your programming language, but maybe I can help with a few examples:

### Ruby

```ruby
require "uri"
require "net/http"
require "json"

uri = URI("https://id.twitch.tv/oauth2/token")
response = Net::HTTP.post(
             uri, 
             {"client_id" => CLIENT_ID,
             "client_secret" => CLIENT_SECRET,
             "grant_type" => "client_credentials"}.to_json,
             {"Content-Type" => "application/json"})
access_code = JSON.parse(response.body)["access_token"]
```

### Javascript

The Javascript example assumes the use of *Fetch*.

```javascript
const Url = "https://id.twitch.tv/oauth2/token"
const Data = {
  client_id: CLIENT_ID,
  client_secret: CLIENT_SECRET,
  grant_type: "client_credentials"
}
const Params = {
  headers: { "Content-Type": "application/json" },
  body: Data,
  method: "POST"
}

let access_token = ""
fetch(Url, Params)
  .then(response => {access_token = response.json()["access_token"]}
```

### Crystal

```crystal
require "http/client"
require "json"

response = HTTP::Client.post(
  url: "https://id.twitch.tv/oauth2/token",
  headers: HTTP::Headers{ "Content-Type" => "application/json" },
  body: {
    "client_id" => CLIENT_ID,
    "client_secret" => CLIENT_SECRET,
    "grant_type" => "client_credentials"}.to_json)

access_token = JSON.parse(response.body)["access_token"].as_s
```

### Bash

The Bash example assumes that the [jq](https://stedolan.github.io/jq/) utility is installed.

```bash
DATA=`curl -d client_id=CLIENT_ID \
           -d client_secret=CLIENT_SECRET \
           -d grant_type=client_credentials \
           -s https://id.twitch.tv/oauth2/token`
ACCESS_TOKEN=echo $DATA | jq .access_token
```

Once you have a valid *access token*, the world is your oyster. The rest of the Twitch EventSub API is accessible.

## A Quick Note About the EventSub API URL

The sections that follow provide examples for how to perform each of the EventSub management actions, and an astute reader will note that the URL for each of the sections is the same. All EventSub API actions operate through the same API URL:

`https://api.twitch.tv/helix/eventsub/subscriptions`'

What differentiates the different types of actions that can be performed with EventSub requests are the HTTP verbs that are used to perform the request and the payload that accompanies it.

## Listing Subscriptions

The EventSub API provides a mechanism to see what subscriptions a client currently has and their status. This is important, because there is a limited number of subscriptions allowed per client (10000), and even failed subscription requests count against that limit. This makes it important to monitor all current subscriptions so that failed or unneeded subscriptions can be deleted.

To access a list of subscriptions, a *GET* request must be issues to *https://api.twitch.tv/helix/eventsub/subscriptions*. This request should provide *Client-ID* and *Authorization* parameters, where the value of the *Authorization* parameter is the access token generated earlier, with `Bearer ` prepended to it:

```
Authorization: Bearer deadbeefdeadbeef
```

With a valid access token, the response will be a JSON payload with a `data` field containing a list of subscriptions, along with some fields showing the limit on the number of subscriptions, as well as how many total subscriptions the client has.

A full specification for this API request can be found at [https://dev.twitch.tv/docs/api/reference#get-eventsub-subscriptions](https://dev.twitch.tv/docs/api/reference#get-eventsub-subscriptions).

## Creating a Subscription

This is the most complex operations when dealing with EventSub, as subscription creation also involves a verification step that allows Twitch to validate that the callback that was given in the subscription request is owned by the client that requested it, as well as a signature validation to allow the client to verify that the Twitch verification request is itself valid.

#### Step 1 -- Request a subscription

A subscription request is initiated by sending a *POST* request to *https://api.twitch.tv/helix/eventsub/subscriptions* with the following HTTP Headers:

```HTTP
Client-ID: CLIENT_ID
Authorization: Bearer ACCESS_TOKEN
Content-Type: application/json
```

Within the body of the request, a JSON payload with four keys, `version`, `type`, `condition`, and `transport` is expected. Each should have a value as follows:

* **version** : Currently, this is always `1`.
* **type** : This is the [name of the event](https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types) to subscribe to.
* **condition** : This is an object which itself will have a single key, `broadcaster_user_id`, which contains the numeric user id of the account that is requesting the subscription.
* **transport** : This is an object with three keys, `method`, `callback`, and `secret`, which are used to specify the transport mechanism for Twitch to send event information. Currently only `webhook` is supported for the `method` key, though the documentation alludes to plans to support others in the future. The `callback` will be the URL that Twitch will contact when the subscribed-to-event occurs, and the `secret` should be a 10 to 100 character secret value unique to this subscription.

The secret will be used to validate the subsequent subscription request verification, so your code must remember what it sent as the secret for this subscription.

The whole package to issue a request for a *channel.follow* subscription will look something like this:

```json
{
  version: "1",
  type: "channel.follow",
  "condition": {
    "broadcaster_user_id": "12826"
  },
  "transport": {
    "method": "webhook",
    "callback": "https://example.com/webhooks/callback",
    "secret": "abcdefghij0123456789"
  }  
}
```

#### Step 2 -- Receive a response indicating request status

In response to the subscription request, Twitch will send a JSON payload as a response. If the request was successfully received, the response from Twitch will be similar to that described above when listing subscriptions, except that the subscription contained in the *data* array will have a status of *webhook_callback_verification_pending*:

```json
"status": "webhook_callback_verification_pending"
```

This indicates that Twitch has received and will be verifying the subscription request.

#### Step 3 -- Receive verification

Twitch must verify that the callback provided in the subscription request belongs to the caller. To that end, it will contact the callback URL in order to initiate a verification exchange.

It will make a *POST* request to the callback URL. The handler for the callback URL must be able to recognize a Twitch verification request and respond appropriately. Twitch sets a number of custom HTTP headers on the request, several of which are particularly important:

* **Twitch-Eventsub-Message-Id** : This is a UUID representing the unique ID of this specific message. This will be used in *step 4*.
* **Twitch-Eventsub-Message-Timestamp** : The timestamp is also used in *step 4*
* **Twitch-Eventsub-Message-Type** : This is the message type. For a verification attempt, this will be set to `webhook_callback_verification`.
* **Twitch-Eventsub-Message-Signature** : This is a *HMAC-SHA256* message signature in the format of `sha256=4471d611ed1f44cf2fe1d7a462fc62`. This is used in *step 4*
* **Twitch-Eventsub-Subscription-Type** : This is the subscription type that is being verified. From the example above, this would be `channel.follow`.

In the body of the request will be a JSON payload that contains another copy of the subscription, in the same format that has already been discussed, along with a *challenge* key, and a value in the form of a random string of letters and digits separated into clusters by dashes. The *challenge* will be used after the request is validated, in *step 5*.

#### Step 4 -- Validate the request's *Message-Signature*

The *Twitch-Eventsub-Message-Signature* is calculated with *HMAC-SHA256* using the secret that was provided to Twitch in the original subscription request. It is a concantenation of the value of the *Twitch-Eventsub-Message-Id* and the *Twitch-Eventsub-Message-Timestamp* headers with the message body, signed using *HMAC-SHA256* with the aforementioned secret.

If the calculated signature does not match the signature that was provided in the *Twitch-Eventsub-Message-Signature* header, return a 403 status. If it does match, continue to *step 5*.

Your code will probably look something like this:

```crystal
calculated_signature = OpenSSL::HMAC.hexdigest(
  OpenSSL::Algorithm::SHA256,
  secret,
  request.headers["Twitch-Eventsub-Message-Id"] +
    request.headers["Twitch-Eventsub-Message-Timestamp"] +
    request.body.gets_to_end
)

signature = request.headers["Twitch-Eventsub-Message-Signature"]

if signature != calculated_signature
  response.respond_with_status(403)
else
  # Yay! The Signature was verified. Continue with processing.
end
```

#### Step 5 -- Respond to the verification request with the *challenge*

At this point, your code will have validated Twitch's request for verification. The only thing that is left to do is to respond to the validation request.

As mentioned previously, the JSON payload in the body of the request will have contained a key *challenge*. The value for this key must be returned in a status code 200 response to the Twitch request, with nothing added or changed.

A sample of how this might look is as follows:

```crystal
body = request_body.gets_to_end
params = JSON.parse(body)

challenge = params["challenge"]?
if challenge
  response.status_code = 200
  response.write challenge.as_s.to_slice
  response.close
end
```

#### Step 6 -- There is no step 6

At this point the subscription is active.

## Deleting a Subscription

At some point, you will want to delete a subscription, either because you no longer need the information, or because a subscription request failed, and you need to clear it out.

Deleting subscriptions is a straight forward process, fortunately.

Every subscription has a UUID that identifies it. This ID can be retrieved from the JSON response that is returned when subscriptions are listed. To delete a subscription, a *DELETE* request is sent to the *https://api.twitch.tv/helix/eventsub/subscriptions* URL, with *Client-ID* and *Authorization* headers, just as for listing subscriptions. 

## Handling Notifications

When an event occurs for one of the active subscriptions, Twitch will send a *POST* request to the callback URL with the details. The headers will be the same as was discussed above for subscription verification, except that the *Twitch-Eventsub-Message-Type* header will have a value of `notification.

The JSON payload for the request will contain an object with two top-level keys, `subscription`, and `event`.

The value for the `subscription` key will contain a copy of the subscription that generated the event.

The value for the `event` key will contain an object that describes the event details. The precise values that will be available in this object depend on the event type. Please [refer to the Twitch documentation](https://dev.twitch.tv/docs/eventsub/eventsub-reference) in order to figure out what to expect.

It is expected that the notification will be verified in exactly the same manner that the subscription request was verified, by checking the *HMAC2SHA256* signature of the request before trusting it. If it is validated, it is expected that a 200 response will be sent back to Twitch to confirm that the event was received. If validation fails, it is expected that a 403 response (or other appropriate 4xx response) is sent to Twitch to indicate the validation failure.

If Twitch isn't sure that the event was received (such as a case where neither a 2xx nor a 4xx response are received in response to an event), Twitch may resend the event, so one's event handler must be able to cope if an event that was already received is received a second time.

## All Of The Details Are In The API Docs

The Twitch API documentation contains all of the above details, and while they are not presented in a linear fashion that is easy to implement-your-own-code from, they are all present in full details if one hunts enough.

Please use this as a guide to get yourself going, and then refer back to the Twitch documentation for full details, as in many places some of the details have been elided in order to keep this guide as direct and simple as possible.

---

I stream on Twitch for The Relicans. [Stop by and follow me at https://www.twitch.tv/wyhaines](https://www.twitch.tv/wyhaines), and feel free to drop in any time. In addition to whatever I happen to be working on that day, I'm always happy to field questions or to talk about anything that I may have written.