Trying to make a start on using EventSub (PHP)

I’ve spent the past 4 months on and off reading documentation and everything I can find online with regard to the event sub and I am utterly lost. I get the general idea - subscribe to a topic, respond somehow to confirm the subscription (I think?), get the subscription data, do stuff with it. Unfortunately this is something I MUST do and I can’t find anyone to help me, so in the interests of trying to make some progress, however slow, I’d be grateful if someone could point out what is wrong with what I’ve got so far and what I should do next:

   $clientid = 'jbnax47ha22j1ibbskfq5v3b2uwkbq';                    
    $sqlGAT = "SELECT * FROM `TWITCH_Auth` ORDER BY `expires` DESC LIMIT 1"; 
    $resultGAT = $conn->query($sqlGAT);
    if ($resultGAT->num_rows > 0) {
        while($rowGAT = $resultGAT->fetch_assoc()) {
            $authtoken = $rowGAT['token'];
        }
    } 

    $mode = 'subscribe';
    $callbackURL = 'http://www.shamblingincompetence.com/auth/WEBHOOK-receive.php'; // what needs to go in this page?
    $BCID = '501071947';
    $lease = '864000';
    $subtoURL = 'https://api.twitch.tv/helix/webhooks/hub';
    $CSRdata = array(
        'hub.mode' => $mode,
        'hub.topic' => 'https://api.twitch.tv/helix/users/follows?first=1&from_id='.$BCID,
        'hub.callback' => $callbackURL,
        'hub.lease_seconds' => $lease
    );
    $CSdatainput = json_encode($CSRdata);
    $CScurl = curl_init($subtoURL);
    curl_setopt($CScurl, CURLOPT_CUSTOMREQUEST, 'POST');
    curl_setopt($CScurl, CURLOPT_POSTFIELDS, $CSdatainput);
    curl_setopt($CScurl, CURLOPT_RETURNTRANSFER, true);
    
    curl_setopt($CScurl, CURLOPT_HTTPHEADER, array(  
        'Content-type: application/json',
        'Authorization: Bearer '.$authtoken,   
        'Client-ID: '.$clientid, 
    )); 

    $CSoutput = json_decode(curl_exec($CScurl),true);
    curl_close($CScurl);    

The response I get is ‘NULL’ and I’m not sure what to do with that information, is this the correct ‘format’ (it’s hard to know what to do with the generic ‘POST’ instruction in the reference). Obviously I don’t expect it to do anything without the next part (stuff that should go in WEBHOOKS-notification.php) - but I don’t know what that is supposed to be! Any patient assistance is sorely appreciated!

Your topic title says “eventsub” but your code is for “webhooks”

Which are you trying to work with?

Here you assumed you got a HTTP 200 and assumed your got JSON.
You didn’t do any error code or JSON Decode error checking

Event Sub seems more comprehensive, and the process sounded similar, I’m guessing that’s entirely wrong! We did have webhooks set up a while back, but it stopped working for some unknown reason and the person who set it up left no instructions and won’t help any further. Take that as an indication of just how horrifyingly out of my depth I am with any of this and how little I understand of any of it, unfortunately I also have no choice but to try and get it working some how. Also I’m not sure what error checking I should be doing, the var dump of the $CSoutput response just says ‘NULL’ - again, there’s nothing on that in the generic instructions in Twitch’s reference guide which everyone I ask keeps sending me back to - or if there is, I’m not seeing/understanding it.

To be fair the reference documentation keeps referring to webhooks here so I think that’s where my confusion comes from: https://dev.twitch.tv/docs/eventsub

EventSub provides data in a transport agnostic way.

Currently EventSub only provides a Webhook transport


    $CSoutput = json_decode(curl_exec($CScurl),true);
    curl_close($CScurl);    

should be something like


    $CSoutput = curl_exec($CScurl);
    $CSinfo = curl_getinfo($CScurl);
    curl_close($CScurl);

    if ($CSinfo['http_code'] == 200) {
        $CSjson = json_decode($CSoutput);
        if (json_last_error() == JSON_ERROR_NONE) {
            echo 'Got JSON';
            print_r($CSjson);
        } else {
            echo 'Got a JSON decode error with ' . $CSoutput;
        }
    } else {
        echo 'Got a ' . $CSinfo['http_code'] . ' with ' . $CSoutput;
    }

The Twitch documentation describes how to do things in the lowest common denominator of cURL.

Then you can take that and convert it into however your language makes cURL/HTTP requests. In this case with PHP it’s either using the curl functions of a library such as guzzle.

As to code examples

For webhooks - https://github.com/BarryCarlyon/twitch_misc/tree/main/webhooks/handlers/php
For EventSub - https://github.com/BarryCarlyon/twitch_misc/tree/main/eventsub/handlers/php

That covers how to process/secure check incoming messages from Twitch.

And a qucik gloss over your code it looks correct for a creation of a webhook request, without testing it myself. So add some error/debug checking and you can find out the issue/problem

Thank you so much Barry! I very much appreciate you taking the time to write all that out, I’ll have a fiddle with my code to add this and see what progress I can make, I do think the Event Sub is what would be best optimally, but getting the existing webhooks back up and running for gift subs, subs and follows would improve the current situation a lot, the database ‘infrastructure’ is there!

So the response I get is “Got a 202 with” - which is good (I think?) but now I don’t know what to do next! :woman_facepalming:t2:

HTTP 202 is the HTTP code for “accepted”

Webhooks will 202

When you subscribe to a webhook, if the parameters are supplied you receive an immediate 202 Accepted response, with an empty body

EventSub will 200 with a JSON Blob of data about the subscription

Example

{
  "data": [
    {
      "id": "26b1c993-bfcf-44d9-b876-379dacafe75a",
      "status": "webhook_callback_verification_pending",
      "type": "users.update",
      "version": "1",
      "condition": {
        "user_id": "1234"
      },
      "created_at": "2020-11-10T20:29:44Z",
      "transport": {
        "method": "webhook",
        "callback": "https://this-is-a-callback.com"
      },
      "cost": 1
    }
  ],
  "total": 1,
  "total_cost": 1,
  "max_total_cost": 10000
}

If webhooks:

Twitch will call the callback to verify exists
Echo back the challenge
Then you wait for data to be POST’ed to the endpoint.

If eventsub

Twitch will call the callback to verify exists
Echo back the challenge
Then you wait for data to be POST’ed to the endpoint.

So not really making much progress, I’ve made changes according to the EventSub instructions rather than webhooks, but I’m getting a 401 unauthorized error in response. Am I correct that the Client ID in this instance is for the app and not for the user? And if it’s the latter, do they expire? We have one in the database, but that gives me the same “Must provide valid app token” response. I just want to check before I try and get a fresh token and break everything in the process!

ClientID’s represent an application.

Users grant access to that ClientID.

EventSub will use an “An App Access Token” to generate subsciptions
But if you are requesting private data you need to have generate a “User Access Token” with the relevant scopes at least once even if you don’t keep the token on file.

Private would be subscribers for example.

I wrote about it on this blog post

App Access Tokens are documented here

User tokens here

ClientID’s do not expire
All Tokens expire.
Refresh Tokens do not expire

App Access tokens won’t provide a refresh token, so you just make a new token when you need one.

One what? A ClientID or a token?

If a token, then yeah the token probably died

You can use the validate endpoint

To validate a token and determine if it’s a User or App token

Ok… I looked at https://dev.twitch.tv/docs/api/reference#create-eventsub-subscription it doesn’t mention needing a user access token (specifically says app access). Having just checked the script our website uses to generate tokens when people login via Twitch, it is labeled as a user access token, I’ve no idea what the other lone string is for. So the subscription to get data about Twitch subscriptions requires both? Does the user token go in the header or…? I’m also not sure what the secret should be - can it be something I just make up?

Correct, EventSub only uses App Access Tokens.

No.

You have misunderstood what I have written, I think.

EventSub ONLY uses App Access Tokens.

If you want to subscribe to the channel subscriptions topics, for example:

  • EventSub will take the App Access Token,
  • Validate it
  • Get the clientID from that token
  • Look to see if the requested broadcaster has authorised that client ID to read subscribers.

Theres no user token invovled there.

But you need to have gotten a User Token with the relevant scopes from the user, even iif you never store/use that User Token, as otherwise the last step fails of EventSub setup, since you’ve never asked the broadcaster for access to their subscribers.

When creating a subscription to a topic, you only send the App Access Token.

The secret is made up by you and used to sign payloads from Twitch so you can verify that the payloads come from Twitch and not a bad actor. Since only you and Twitch will know this secret

I’m autistic and pretty stupid, so misunderstanding things is my natural habitat. Thank you for your patience in trying to explain it to me!

That said I think I see what you’re getting at now. There is a callback script for the website login where the Oauth scopes are listed including ‘channel:read:subscriptions’ associated with the broadcaster ID in question - whenever I’ve updated it in the past to add new things, he gets a pop-up on logging back in to confirm authorization for the new thing. I’m entirely unsure of most of the terminology which adds to the confusion.

This is what I have so far:

$callbackURL = 'http://www.shamblingincompetence.com/auth/EVENTSUB-subscriptions.php';
$BCID = '501071947';
$secret = 'REMOVED';
$clienttoken = 'REMOVED' // *

$CSRdata = array(
    'type' => 'channel.subscription',
    'version' => '1',
    'condition' => '{"broadcaster_user_id":'.$BCID.'}',
    'transport' => '{"method":"webhook","callback".'.$callbackURL.',"secret":"'.$secret.'"}'
);

$CSdatainput = json_encode($CSRdata);
$CScurl = curl_init('https://api.twitch.tv/helix/eventsub/subscriptions');
curl_setopt($CScurl, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($CScurl, CURLOPT_POSTFIELDS, $CSdatainput);
curl_setopt($CScurl, CURLOPT_RETURNTRANSFER, true);

curl_setopt($CScurl, CURLOPT_HTTPHEADER, array(  
    'Authorization: Bearer '.$clienttoken,   
    'Client-ID: '.$clientid, 
    'Content-Type: application/json',

)); 

echo "<pre><h3>";
var_dump($CSoutput);    
echo "</h3></pre>";  

$CSoutput = curl_exec($CScurl);
$CSinfo = curl_getinfo($CScurl);
curl_close($CScurl);    
if ($CSinfo['http_code'] == 200) {
    $CSjson = json_decode($CSoutput);
    if (json_last_error() == JSON_ERROR_NONE) {
        echo 'Got JSON';
        print_r($CSjson);
    } else {
        echo 'Got a JSON decode error with ' . $CSoutput;
    }
} else {
    echo 'Got a ' . $CSinfo['http_code'] . ' with ' . $CSoutput;
}   
  • assuming I’ve made no other mistakes (highly improbable) I’m assuming the $clienttoken is the point of failure (given the response is Got a 401 with {“error”:“Unauthorized”,“status”:401,“message”:“Must provide valid app token.”}. I’m concerned that if I get a new one it’ll break the entire website because I’m not sure what else uses it and I can’t ask the person who set it up. It sounds like I’d have an easier time doing follows rather than subscriptions, but that does mean I’d just be back with the same problem later. As always, help is so very appreciated :woman_facepalming:t2:

A token doesn’t last forever

So if the website is using a token it must be self regenerating the app access token to use it.

You really need to talk to this guy, or get someone who can go thru your codebase and sort it all out. Since you are fighting to learn the Twitch API, and upgrade from webhooks to eventsub and the “mess” you have inherited from this person.

Only setup the subscriptions to the topics you need.

Your original code is for follows
And I cite information about the subscribers topic to help you understand the duality of access tokens with EventSub.

So if you don’t need subscriber/other private data, don’t request/set up those subscriptions to those topics.