DEV Community

Kirk Haines
Kirk Haines

Posted on • Updated on

Twitch EventSub - The Direct Approach to Getting Started With It

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

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

"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. On the right of the page, there should be a section labeled Applications. Click on the Register Your Application button.

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

image

Give your app a name.

image

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

image

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

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

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 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 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

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:

curl -d client_id=$TWITCH_CLIENT_ID \
     -d client_secret=$TWITCH_CLIENT_SECRET \
     -d grant_type=client_credentials \ 
     https://id.twitch.tv/oauth2/token
Enter fullscreen mode Exit fullscreen mode

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:

{"status":400,"message":"invalid client"}
Enter fullscreen mode Exit fullscreen mode

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

{"status":403,"message":"invalid client secret"}
Enter fullscreen mode Exit fullscreen mode

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

{"access_token":"q3b5n90ua7du0mgpwl149ge2yf90r0","expires_in":4776914,"token_type":"bearer"}
Enter fullscreen mode Exit fullscreen mode

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

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"]
Enter fullscreen mode Exit fullscreen mode

Javascript

The Javascript example assumes the use of Fetch.

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"]}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Bash

The Bash example assumes that the jq utility is installed.

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.

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:

Client-ID: CLIENT_ID
Authorization: Bearer ACCESS_TOKEN
Content-Type: application/json
Enter fullscreen mode Exit fullscreen mode

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 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:

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

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:

"status": "webhook_callback_verification_pending"
Enter fullscreen mode Exit fullscreen mode

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:

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
Enter fullscreen mode Exit fullscreen mode

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:

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
Enter fullscreen mode Exit fullscreen mode

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 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, 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.

Top comments (0)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.