Google Apps Script + EventSub

This is my first time trying Twitch api, especially in conjunction with Google Apps Script. I managed to send a subscription request, but I don’t understand what to do next to create a subscription and start working with EventSub. Please help!

EventSub is a transport agnostic notification platform for various Twitch related topics.

EventSub right now only offers a “webhook” transport.

A webhook is where the service (Twitch) sends you data via a HTTPS POST submission, when data occurs.
This also forms part of the verification step when you are creating a subscription. As Twitch will ping the callback URL with a message you echo back.

So first you need to create a handler/script/webpage capable of recieving a HTTP POST request, which essentially is the same as when people fill in a login form on a website to login, the from will HTTP POST. The difference is instead of form data, you will get JSON data in the body.

Once you have that up and running then you should be good to go.

I wrote a NodeJS example of a handler and how to verify payloads on which you can refer to which may help

Could you look through my code please?

function auth(){
  var url = ''
  var data = {
    "client_secret": client_secret,
    "client_id": client_id,
    "grant_type": "client_credentials"
  var options = {
  'method' : 'post',
  'payload' : data
  var response = UrlFetchApp.fetch(url,options);
  var accesstoken = JSON.parse(response.getContentText()).access_token

It gives me:



function createSubscription(accesstoken) {
  var callback = url+"/webhooks/callback"
  var data = {
    "type": "channel.follow",
    "version": "1",
    "condition": {
        "broadcaster_user_id": String(givinglifetopID)
    "transport": {
        "method": "webhook",
        "callback": callback,
        "secret": "some secret code"
  var options = {
    'method' : 'post',
    'headers' : {
      'Client-ID' : client_id,
      'Authorization' : "Bearer "+accesstoken,
      'Content-Type' : "application/json"},
    'payload' : JSON.stringify(data)
  var response = UrlFetchApp.fetch('', options);

It gives:



function sig(data) {
  var blob = Utilities.newBlob(data)
  var encoded = Utilities.base64Encode(blob.getBytes())
  var message = Utilities.base64Decode(encoded);
  var key = Utilities.base64Decode("a2V5a2V5a2V5")
  var signature = Utilities.computeHmacSha256Signature(message, key);
  var sig = signature.reduce(function(str,chr){
    chr = (chr < 0 ? chr + 256 : chr).toString(16);
    return str + (chr.length==1?'0':'') + chr;
  return sig;

Which gives me a signature code.

At least, I’m trying to deal with all of it by function responseToSubReq:

And here I get:
“Exception: Request failed for returned code 401. Truncated server response: {“error”:“Unauthorized”,“status”:401,“message”:“OAuth token is missing”}”

Fixed your post formatting for you

Your reesponseToSubReq is wrong.

All you need to do is grab req.body.challenge and echo it to the server.

None of what you have in that function, which seems to be constructing a request and sending it on to the endpoint.

You need to REPLY to the incoming post request with the challenge in the payload. Not construct another payload and curl it to the subscriptions endpoint.


You are basically responding to a login form post submission, to refer to the analogy I used earlier.

Thanks a lot! I will try to figure out how to manage this in Google Apps Script.

I made doGet script, but I don’t get any request from Twitch after sending my POST. What could be wrong?

Check any logs for the server to see if HTTP POST requests are being recieved and/or anything that logs this information.

If you doGet/doPost are for receiving messages/requests.

You need a doPost not a doGet as EventSub only uses POST requests to communicate to you.

But to make subscription with challenge I have to get request from Twitch with challenge parameter and then send it back, isn’t it?

Yes and Twitch sends that challenge to you via a POST request

To get this POST I have to receive this by doGet, right? Or doPost?

A doPost appears to be for handling inbound POST requests.

So you need a doPost like I already said

Ok I will try, thanks!

I hope I’m writting for the last time :smiley:

I managed to get challenge. Trying to respond by:

if (update.hasOwnProperty('challenge')){
    var chall = update.challenge;
    var url = '';
    var options = {
      'method' : 'post',
      'header': {
        'Client-ID': client_id,
        'Authorization': "Bearer "+acces_token
      'payload' : String(chall)
    var resp = UrlFetchApp.fetch(url,options) 

But get no answer to this. Checked for available subscriptions and get:


What could be wrong?

You don’t match a fetch request.

Just echo out the challenge.

I believe that’s

return HtmlService.createHtmlOutput(update.challenge);

I logs every challenges i got from function createSubscription into my google sheets, everything is correct.

Meanwhile, I just noticed that verifaction failed as soon as subscription was requested (at function createSubscription).

then it’s failing to call your callback URL.
The URL you specified was invalid.
Or the scipt is not returning the callback correctly.

could it be wrong URL in Twitch App Console? (OAuth Redirect URLs)

For example, my script URL is

And Redirect URL in Twitch Console is the same.

But there is “” in my code to POST.

Should I edit my Redirect URL in Twitch Console with “webhooks/callback”?


The callback is specified in the create subscription call

Example from the curl example

-d '{"type":"users.update","version":"1","condition":{"user_id":"1234"},"transport":{"method":"webhook","callback":"","secret":"s3cre7"}}'

No. the oAuth redirect has nothing to do with EventSub subscription creation.

You need to check any logs or anything you have to see if Twitch is calling the URL and/or the script is working as expected.

You can even test it’s working as expected by calling your callback manually, by creating a test call/script and running it to see if your endpoint/callback is working as expected.

I did it manually. POST subscription gives me:

{"data":[{"id":"XXXX", "status":"webhook_callback_verification_pending", "type":"channel.follow", "version":"1", "condition":{"broadcaster_user_id":"116528496"}, "created_at":"2021-03-25T13:35:03.265284455Z", "transport":{"method":"webhook", "callback":""}, "cost":1}], "limit":10000, "total":4, "max_total_cost":10000, "total_cost":1}

And got POST from Twitch:

    ""subscription"": {
        ""id"": ""87b328e7-bbaf-4168-92af-ab9663f3928c"",
        ""status"": ""webhook_callback_verification_pending"",
        ""type"": ""channel.follow"",
        ""version"": ""1"",
        ""condition"": {
            ""broadcaster_user_id"": ""116528496""
        ""transport"": {
            ""method"": ""webhook"",
            ""callback"": """"
        ""created_at"": ""2021-03-25T13:35:03.265284455Z"",
        ""cost"": 1
    ""challenge"": ""XXXXXXX""

Then I POST to get all subscriptions and get:

{"id":"87b328e7-bbaf-4168-92af-ab9663f3928c", "status":"webhook_callback_verification_failed", "type":"channel.follow", "version":"1", "condition":{"broadcaster_user_id":"116528496"}, "created_at":"2021-03-25T13:35:03.265284455Z", "transport":{"method":"webhook", "callback":""}, "cost":0}], "limit":10000, "max_total_cost":10000, "total_cost":0, "pagination":{}}

So there is already webhook_callback_verification_failed.

You didn’t echo out the challenge so.

Twitch either got a non 200 code when it POST-ed to you
Or when it POST-ed to you you didn’t echo back the challenge.

You can see in my NodeJS example here

I fetch the challenge from the incoming POST request and just echo it out.

You are getting webhook_callback_verification_failed as your callback is not echoing the vierification challenge correctly.

You can even use the twitch-cli to test your endpoint to see if it replies correctly to verification requests