How to get the spent Channelpoints of a User?

I am currently working on a website similar to r/place. However to place a pixel on the website, the user first has to spend 100 channelpoints on my Twitch Stream.

To implement this, I am considering adding a “login with Twitch” authentication feature on my website. Once the user is logged in, the server will verify if they have spent 100 channelpoints for a pixel. If they have, they will be granted the ability to place the pixel.

I wonder if that is technically possible and maybe someone can give me a approach path for it.

You want either

To get/receive the channel points usage event(s).

Then you can either recieve all redeems that exist on your channel or the pixel placing specfic redeem.

I would use channel.channel_points_custom_reward_redemption.add to collect a “someone has a pixel to use” then mark the redemption as “user” with Update Redemption Status

1 Like

I also read about pubsub. would that be more useful in that case? I also wanted to try the custom reward redemption on my own channel but it sadly doesnt work since im not a affiliate or twitch partner.

PubSub is deprecated and will be removed in about a year.

EventSub (which replaces PubSub) provides events over various transports, including a socket, like pubsub which is only a socket.

However for this project I would likely be using Webhooks, or the API, as you want to check for “has a pixel to use” either aginst your database or the API.

Which means you have a WebServer, so webhooks seem the logical solution

Then you need to be testing this aginst the channel your product is for.
Since your clientID also needs to “own” the redwards/redeems in question in order to manage/control/update the redeems.
Or find a friendly affiliate/partner to borrow keys for but then you are stuck generated reddems to consume… So the CLI is your best bet here

Alternatively the TwitchCLI provides some Mock functions in order to test your EventSub handler. (Either Sockets or Webhooks)

1 Like

Thanks a lot I will try that out. I am working with a bigger Twitch Streamer together who is twitch partner. In that case he will have to create a Twitch Application, create the custom channelpoint event with it in order to use the Get Custom Reward Redemption for that custom reward?

No the owner of the application is irrelevant, you can own the application or I can own the application.

Rewards and redeems can only be managed by the ClientID that created them.
So if the dashboard is used to create the rewards, the Client ID can read the redeems but it cannot update the redeems to used/cancelled.

So if you or I own the ClientID, we just get the broadcaster to authenticate on our clientID

Then the ClientID can manage the rewards on the channel but only the rewards the ClientID has created.

See only_manageable_rewards on Reference | Twitch Developers

A Boolean value that determines whether the response contains only the custom rewards that the app may manage (the app is identified by the ID in the Client-Id header). Set to true to get only the custom rewards that the app may manage. The default is false.

And this GitHub Example Tool

This is my GitHub ClientID used for my public github testings

Reward 1 and 4 were created by that clientID so can be updated/managed from my GitHub ClientID, but it got all the rewards on my channel, and marked those the ClientID can manage

Sooooooooooo:

  • Anyone Creates a ClientID, logically this would be you
  • The channel(s) then “login with twitch” using your ClientID likely via the website backend to your game/tool
  • Now you can use that token and your clientID to ignore rewards created by others/the dashboard, create your own rewards.

After that reward is created it can be updated/changed as much as needed in the dashboard, it just has to be born via the controlling Client ID.

So in summary:

  • The owner of the ClientID is irrelevant
  • A clientID can recieve all redeems/rewards on a channel
  • A clientID can only manage/update redeems and rewards for rewards that the clientID has created itself via the API

Practical Exmpla

Cult of the Lamb, (a Twitch Extension) I have no idea who owns the ClientID.

But when you open the game all your have to do is login with Twitch.

Then it creates the Channel Point Reward it uses and away it goes.

1 Like

Damn appreciate the detailed response that made it a lot clearer. So just to see if I understood it correctly. I create my own Application (which I already have). But with my own Client ID I wouldn’t be able to update the redeems to used/cancelled on the Streamers Channel since the Client ID does not match the broadcasters ID. In order to “fix” that the broadcaster needs to authenticate on our Client ID and then the Application would be able to update the redeemds to?

I think you meant:

But your pixel placing redeem probably doesn’t exist yet. So thats not a problem

Basically yes

  • streamer authenticates to allow your ClientID to read/write to their channel
  • your ClientID then creates your reward(s) that you want on the channel

After that any changes can be done via your tool or via the dashboard.
Since, for example, reward images cannot be updated/uploaded via the API

1 Like

Alright thanks a lot :slight_smile:

So I’ll bump this thread now as I got further but I’m stuck again.

I now have a node express server that listens to port 2083.
With that I can use https://api.twitch.tv/helix/channel_points/custom_rewards/redemptions to get the latest redemptions for a specific reward which works fine.

As I got that working, I am now trying to use channel.channel_points_custom_reward_redemption.add instead.

For that I created an event like so:

app.get('/register-webhook', function(req, res) {
  if(req.session && req.session.passport && req.session.passport.user) {
    var clientId = TWITCH_CLIENT_ID;
    var clientSecret = TWITCH_SECRET;
    
    // Request an app access token
    var tokenOptions = {
      url: 'https://id.twitch.tv/oauth2/token',
      method: 'POST',
      form: {
        client_id: clientId,
        client_secret: clientSecret,
        grant_type: 'client_credentials'
      }
    };

    request(tokenOptions, function (error, response, body) {
      if (response && response.statusCode == 200) {
        var accessToken = JSON.parse(body).access_token;
        
        // Register the webhook endpoint with Twitch
        var webhookOptions = {
          url: 'https://api.twitch.tv/helix/eventsub/subscriptions',
          method: 'POST',
          headers: {
            'Client-ID': clientId,
            'Authorization': 'Bearer ' + accessToken,
            'Content-Type': 'application/json'
          },
          json: {
            type: 'channel.channel_points_custom_reward_redemption.add',
            version: '1',
            condition: {
              broadcaster_user_id: 'myid',
              reward_id: 'rewardid'
            },
            transport: {
              method: 'webhook',
              callback: 'https://www.censored.fun/webhooks/custom_reward_redemption/add',
              secret: 'mysecret'
            }
          }
        };

        request(webhookOptions, function (error, response, body) {
          if (response && response.statusCode == 200) {
            res.send('Webhook registered successfully');
          } else {
            res.status(response.statusCode).send(body);
          }
        });
      } else {
        res.status(response.statusCode).send(body);
      }
    });
  } else {
    res.status(401).send('Unauthorized');
  }
});

Which then returns this (i censored my stuff):

{
  "data": [
    {
      "id": "censored",
      "status": "webhook_callback_verification_pending",
      "type": "channel.channel_points_custom_reward_redemption.add",
      "version": "1",
      "condition": {
        "broadcaster_user_id": "censored",
        "reward_id": "censored"
      },
      "created_at": "2024-04-28T09:47:03.444336379Z",
      "transport": {
        "method": "webhook",
        "callback": "https://www.censored.fun/webhooks/custom_reward_redemption/add"
      },
      "cost": 0
    }
  ],
  "total": 9,
  "max_total_cost": 10000,
  "total_cost": 0
}

As I can see it tells me “webhook_callback_verification_pending”. Now I am wondering, is this a process that needs to be manually reviewed and if everything is fine I get unlocked? How long does that take and how do I see if it has been verified? I couldn’t find anything specific online about it so I asked ChatGPT and it responded with:

  1. Handle Verification Challenge: When you register the webhook, Twitch sends a verification challenge to your endpoint to confirm that it can receive and handle events. You’ve already implemented this part in your /webhooks/custom_reward_redemption/add endpoint. It verifies the challenge and responds accordingly.

This is my endpoint, do I need to do anything else?:

// Endpoint to handle initial redemption events
app.post('/webhooks/custom_reward_redemption/add', function(req, res) {
  // Verify the request is coming from Twitch by checking the 'Twitch-Eventsub-Message-Type' header
  if (req.headers['twitch-eventsub-message-type'] === 'webhook_callback_verification') {
    // Respond to the verification challenge
    console.log("Verification Challenge:", req.body.challenge);
    res.status(200).send(req.body.challenge);
  } else {
    // Handle the initial redemption event here
    // Extract necessary data from the request body
    var eventData = req.body.event;
    var userId = eventData.user_id;
    var userName = eventData.user_name;
    
    // Log the redemption event
    console.log("Redemption Event ID:", redemptionId);
    console.log("User ID:", userId);
    console.log("User Name:", userName);

    // Perform actions based on the redemption event
    // For example, you can update your database, trigger notifications, etc.

    // Respond with a 200 OK status code
    res.sendStatus(200);
  }
});

Also I noticed something different. When I stop my nodejs server and restart it, the webhook also stopps, then I need to do /auth/twitch again and can register a new webhook with /register-webhook.
However when I not restart it and try /register-webhook again it tells me “subscription already exists”.
Does that mean, that everytime I restart my server, I need to reregister the webhook which then requires me to wait everytime until the webhook is verified?

And my last question is, currently the express node server is running on port 2083 (cloudflare). However the callback requires https / 443:

(callback: ‘https://www.censored.fun/webhooks/custom_reward_redemption/add’,)

Would my webhook even work and receive those events as it runs on 2083 currently?
Not sure if I understood everything correct, just trying my best to see how it works.

ChatGPT doesn’t know the answer, and this forum can tell you quicker that it can

Cloudflare likely blocked the request thinking Twitch is a malicous bot.

Or the SSL configuration is invalid

You should also check if your server access logs actually logged a incoming request to confirm your server recieved it
Then check your nodeJS log and/or add logging to reprot if nodeJS got it

And you can use the twitchCLI to run some basic tests (skips SSL steps/checks) Twitch CLI | Twitch Developers

So after Twitch gives up trying to verify, (it’ll try a few times) it should go to callback verification failed (which I forget the specific label for) which then will allow you to attempt to (re)create the subscription.

1 Like

Thanks for your response. I’m not sure about cloudflare blocking it as with using this I receive it (Powershell):

Invoke-RestMethod -Uri “https://www.censored.fun:2083/webhooks/custom_reward_redemption/add” -Method Post -Body $body -ContentType “application/json”

It returns this:

test
TypeError: Cannot read property ‘user_id’ of undefined

which is exactly what I should get.

As you can see I used the 2083 port inside that request.
However doing it with https://www.censored.fun/webhooks/custom_reward_redemption/add instead (no port), I get 404 so it doesn’t reach it. The website itself with https://www.censored.fun is reachable tho as my apache server is running with a default index.html site on https 443 port.

So both the SSL and Cloudflare configurations are working fine. Same with the access logs which are logging incoming requests. However I am not sure if I need to run the express websocket on 443 instead of 2083? But that would interfere with my apache server so that would be unfortunate.

Is the websocket also receiving incoming request on other ports, when it is only running on 2083? Do I need to configure the socket to also listen to incoming 443 requests somehow?

Becuase cloudflare isn’t (currently) blocking you, but may be blocking traffic that originates from Twitch’s services

Twitch requries HTTPS of 443 not for the destination to be on non standard ports.

This indicates your Apache configuration is wrong

What websocket?

This is a webhooks configuration/setup.

If using anything in front of node, such as Apache, that anything should offer something called proxy_pass to redirect a route to a differnet internal port.

https://httpd.apache.org/docs/2.4/mod/mod_proxy.html

So this is the issue the route on the required port of 443 cannot be reached.

However, cloudflare may also be blocking Twitch.

1 Like

Becuase cloudflare isn’t (currently) blocking you, but may be blocking traffic that originates from Twitch’s services

Hmm I see, I checked my cloudflare page but got 0 blocked requests as it looks like in my control panel.
I also used the twitch cli and got the following results:

This is with port 2083:

twitch-cli_1.1.22_Windows_x86_64> twitch event verify-subscription cheer -s “mysecret” -F “https://censored.fun:2083/webhooks/custom_reward_redemption/add
:heavy_check_mark: Valid response. Received challenge 8366cc[…]5822 in body
✗ Invalid content-type header. Received type text/html with charset utf-8. Expecting text/plain.
:heavy_check_mark: Valid status code. Received status 200

And this is without the specified port:

twitch-cli_1.1.22_Windows_x86_64> twitch event verify-subscription cheer -s “mysecret” -F “https://placede.fun/webhooks/custom_reward_redemption/add
✗ Invalid response. Received error code: 521 as body, expected c12f4233-dff[…]a6d1412
:heavy_check_mark: Valid content-type header. Received type text/plain with charset UTF-8
✗ Invalid status code. Received 521, expected a 2XX status

So it seems like twitch is able to reach me when I specify the port, but without it it fails.

This indicates your Apache configuration is wrong
If using anything in front of node, such as Apache, that anything should offer something called proxy_pass to redirect a route to a differnet internal port.

mod_proxy - Apache HTTP Server Version 2.4

I will look into that then, thank you I’ll get back to you if I get any further. Thanks for the help.

The TwitchCLI tests from your computer not from Twitch servers

It may not be Cloudflare blocking I only cite it as it could be

Your apache access logs or node console logs would confirm if there is traffic from Twitch is hitting your service.

Which is should be and surfacing a 404 as you didn’t proxy pass and this route is 404’ing.

1 Like

Okay so good news, for anyone with the same issue in the future, here is how I fixed it:

Added the following to my website.conf:

    SSLProxyEngine on
    ProxyPreserveHost On
    ProxyPass /webhooks/custom_reward_redemption/add https://localhost:2083/webhooks/custom_reward_redemption/add
    ProxyPassReverse /webhooks/custom_reward_redemption/add https://localhost:2083/webhooks/custom_reward_redemption/add

In Ubuntu:

sudo a2enmod proxy
sudo a2enmod proxy_http

Everything works fine now, thanks for the help :slight_smile: