Receiving data from channel point redeems via EventSub?

Salutations! Thanks for looking at this! I am working on trying to use the provided example for how to receive data from channel point redeems via EventSub (to eventually show in a simple browser source). I think i have everything connecting properly with oAuth and the log is showing that i have indeed subscribed to the desired events and im getting keepalive messages from the connected websocket. However, when a channel point reward is redeemed on the connected channel, the log shows nothing and there does not appear to be any response. Is there a way to show the channel point reward redeem’s info in the example’s log upon redeem? (for instance the reward name and user who redeemed and the rewards’ image url?) Thanks a million!!

EventSub WebSockets with Implicit Auth Example
<style>
    #log {
        box-sizing: border-box;
        padding: 5px;
        width: 100%;
        height: 300px;
        overflow: auto;
        border: 1px solid #FFFFFF;
        border-radius: 20px;

        margin-bottom: 20px;
    }

    #log span {
        margin-right: 5px;
    }

    #subscriptions_refresh {
        cursor: pointer;
    }

    table {
        width: 100%;
    }

    .delete_button {
        border: 1px solid red;
        cursor: pointer;
    }
</style>

This example demonstrates how to connect to, create subscriptions and recieve data from EventSub WebSockets

It will just "dumb log" an EventSub notification.

It'll use Implicit auth to obtain a token to use

<ul>
    <li>
    <a id="authorize" target=""></a>
      
    </li>
</ul>

<div id="log">
    <div>LOG</div>
</div>

<div>
    <p>From the RFC - <a href="https://discuss.dev.twitch.tv/t/rfc-0016-eventsub-websockets/32652" target="_blank">https://discuss.dev.twitch.tv/t/rfc-0016-eventsub-websockets/32652</a></p>
    <ul>
        <li>When using a user access token, the developer may create up to 3 WebSocket connections per ClientID/user tuple.</li>
        <li>Additionally, a WebSocket connection is limited to 300 subscriptions.</li>
        <li>However when using a user access token, the default value of max_total_cost will be 10 rather than the
            default of 10,000 for an application access token.</li>
        <li>A given websocket session ID can only use one users access token. You can't connect `fred` and `bob` subscriptions authenticated using each users access token to the same WebSocket SessionID</li>
    </ul>
    <p>The above may be out of date check the <a href="https://dev.twitch.tv/docs/eventsub/handling-websocket-events/#subscription-limits">Subscription limits</a> for changes</p>
    <p>A Websocket can subscribe to 10 cost 1 subscriptions <span id="subscriptions_cost"></span></p>
</div>
<div>
    <p>Add another user to your socket to listen to</p>
    <form action="https://servername.com/strim/GYPoints4/" method="post" id="form">
        <fieldset>
            <label for="add_user">Username</label>
            <input type="text" name="add_user" id="add_user">
            <input type="submit" value="Add User" id="add_user_go">
        </fieldset>
    </form>
</div>

<div>Last KeepAlive: <span id="keepalive"></span></div>
<div id="subscriptions_refresh">Click to Refresh Subscriptions</div>
<div>
    <table>
        <thead>
            <tr>
                <th>Subscription ID</th>
                <th>Topic</th>
                <th>Condition User ID</th>
                <th>Cost</th>
                <th>Status</th>
                <th></th>
            </tr>
        </thead>
        <tbody id="subscriptions"></tbody>
    </table>
</div>

<div id="drawing"></div>

<script type="text/javascript" src="eventsub.js"></script>
<script type="text/javascript">
    // These are set for the GitHub Pages Example
    // Substitute as needed
    var client_id = 'x0mvw507bahn3c61z73x5psrvntcno';
    var redirect = 'https://servername.com/strim/GYPoints4/';
    var access_token = '';
    var socket_space = '';
    var session_id = '';
    var my_user_id = '';

    document.getElementById('authorize').setAttribute('href', 'https://id.twitch.tv/oauth2/authorize?client_id=' + client_id + '&redirect_uri=' + encodeURIComponent(redirect) + '&response_type=token&scope=channel:read:redemptions');

    if (document.location.hash && document.location.hash != '') {
        log('Checking for token');
        var parsedHash = new URLSearchParams(window.location.hash.slice(1));
        if (parsedHash.get('access_token')) {
            log('Got a token');
            processToken(parsedHash.get('access_token'));
        }

        // snip
        //history.replaceState(null, '', window.location.origin + window.location.pathname);
    }

    function log(message) {
        let p = document.createElement('div');
        document.getElementById('log').prepend(p);

        let tim = document.createElement('span');
        let t = [
            new Date().getHours(),
            new Date().getMinutes(),
            new Date().getSeconds()
        ]
        t.forEach((v, i) => {
            t[i] = v < 10 ? '0' + v : v;
        });
        tim.textContent = t.join(':');
        p.append(tim);

        let l = document.createElement('span');
        p.append(l);
        l.textContent = message;
    }

    function processToken(token) {
        access_token = token;

        fetch(
            'https://api.twitch.tv/helix/users',
            {
                "headers": {
                    "Client-ID": client_id,
                    "Authorization": "Bearer " + access_token
                }
            }
        )
            .then(resp => resp.json())
            .then(resp => {
                socket_space = new initSocket(true);
                // and build schnanaigans
                socket_space.on('connected', (id) => {
                    log(`Connected to WebSocket with ${id}`);
                    session_id = id;
                    my_user_id = resp.data[0].id;

                    requestHooks(resp.data[0].id, my_user_id);
                });

                socket_space.on('session_keepalive', () => {
                    document.getElementById('keepalive').textContent = new Date();
                });
            })
            .catch(err => {
                console.log(err);
                log('Error with Users Call');
            });
    }
    function addUser(username) {
        let url = new URL('https://api.twitch.tv/helix/users');
        url.search = new URLSearchParams([['login', username]]).toString();

        fetch(
            url,
            {
                "headers": {
                    "Client-ID": client_id,
                    "Authorization": "Bearer " + access_token
                }
            }
        )
            .then(resp => resp.json())
            .then(resp => {
                log(`Got ${resp.data[0].id} for ${username}`);
                requestHooks(resp.data[0].id, my_user_id);
            })
            .catch(err => {
                console.log(err);
                log('Error with Users Call');
            });
    }

    function requestHooks(user_id, my_id) {
        let eventSubTypes = {
          //  'channel.update': { version: "1", condition: { broadcaster_user_id: user_id } },
          //  'channel.follow': { version: "2", condition: { broadcaster_user_id: user_id, moderator_user_id: my_id } },
            //inbound raid
          //  'channel.raid': { version: "1", condition: { to_broadcaster_user_id: user_id } },

          //  'stream.online': { version: "1", condition: { broadcaster_user_id: user_id } },
          //  'stream.offline': { version: "1", condition: { broadcaster_user_id: user_id } },

          //  'user.update': { version: "1", condition: { user_id: user_id } },
			  'channel.channel_points_custom_reward.add': { version: "1", condition: { broadcaster_user_id: user_id } },
			  'channel.channel_points_custom_reward.update': { version: "1", condition: { broadcaster_user_id: user_id } },
			  'channel.channel_points_custom_reward_redemption.add': { version: "1", condition: { broadcaster_user_id: user_id } },
			  'channel.channel_points_custom_reward_redemption.update': { version: "1", condition: { broadcaster_user_id: user_id } }
        }
        
        log(`Spawn Topics for ${user_id}`);

        for (let type in eventSubTypes) {
            log(`Attempt create ${type} - ${user_id}`);
            let { version, condition } = eventSubTypes[type];

            spawnHook(type, version, condition);
        }
    }
    function spawnHook(type, version, condition) {
        fetch(
            'https://api.twitch.tv/helix/eventsub/subscriptions',
            {
                "method": "POST",
                "headers": {
                    "Client-ID": client_id,
                    "Authorization": "Bearer " + access_token,
                    'Content-Type': 'application/json'
                },
                "body": JSON.stringify({
                    type,
                    version,
                    condition,
                    transport: {
                        method: "websocket",
                        session_id
                    }
                })
            }
        )
            .then(resp => resp.json())
            .then(resp => {
                if (resp.error) {
                    log(`Error with eventsub Call ${type} Call: ${resp.message ? resp.message : ''}`);
                } else {
                    log(`Created ${type}`);
                }
            })
            .catch(err => {
                console.log(err);
                log(`Error with eventsub Call ${type} Call: ${err.message ? err.message : ''}`);
            });
    }

    document.getElementById('subscriptions_refresh').addEventListener('click', (e) => {
        fetchSubs();
    });

    let subscriptions = document.getElementById('subscriptions');
    function fetchSubs(after) {
        let url = new URL('https://api.twitch.tv/helix/eventsub/subscriptions');
        let params = {
            first: 100,
            status: 'enabled'
        };
        if (after) {
            params.after = after;
        }

        url.search = new URLSearchParams(params).toString();

        fetch(
            url,
            {
                "method": "GET",
                "headers": {
                    "Client-ID": client_id,
                    "Authorization": "Bearer " + access_token,
                    'Content-Type': 'application/json'
                }
            }
        )
            .then(resp => resp.json())
            .then(resp => {

                subscriptions.textContent = '';

                resp.data.forEach(sub => {
                    let tr = document.createElement('tr');
                    subscriptions.append(tr);

                    add(tr, sub.id);
                    add(tr, sub.type);

                    let keys = Object.keys(sub.condition);
                    if (sub.condition[keys[0]]) {
                        add(tr, sub.condition[keys[0]]);
                    } else {
                        add(tr, sub.condition[keys[1]]);
                    }
                    add(tr, sub.cost);

                    add(tr, sub.status);

                    let td = document.createElement('td');
                    tr.append(td);
                    td.textContent = 'Delete';
                    td.classList.add('delete_button');
                    td.addEventListener('click', (e) => {
                        deleteSub(sub.id)
                            .then(resp => {
                                console.log('Delete', resp.status);

                                if (resp.status) {
                                    td.textContent = 'Deleted';
                                } else {
                                    td.textContent = `Err ${resp.status}`;
                                }
                            })
                            .catch(err => {
                                console.log(err);
                                log(`Error with eventsub delete`);
                            });
                    });
                });

                document.getElementById('subscriptions_cost').textContent = `${resp.total_cost}/${resp.max_total_cost}`;

                if (resp.pagination) {
                    if (resp.pagination.cursor) {
                        fetchSubs(resp.pagination.cursor);
                    }
                }
            })
            .catch(err => {
                console.log(err);
                log(`Error with eventsub Fetch`);
            });
    }

    function add(tr, text) {
        let td = document.createElement('td');
        td.textContent = text;
        tr.append(td);
    }

    function deleteSub(id) {
        let url = new URL('https://api.twitch.tv/helix/eventsub/subscriptions');
        url.search = new URLSearchParams([['id', id]]).toString();

        return fetch(
            url,
            {
                "method": "DELETE",
                "headers": {
                    "Client-ID": client_id,
                    "Authorization": "Bearer " + access_token,
                    'Content-Type': 'application/json'
                }
            }
        )
    }

    document.getElementById('form').addEventListener('submit', (e) => {
        e.preventDefault();
        let username = document.getElementById('add_user').value;
        log(`Lets lookup and add ${username}`);
        addUser(username);
    });
</script>
--

Yes, parse the data and extract what you are after instead of “just log the payload” which my example does.

The example is just an example on how to get connection logic going as apposed to processing data.

So for example:

socket_space.on('channel.channel_points_custom_reward_redemption.add', ({ metadata, payload }) => {
    let { event } = payload;
    let { user_login , reward } = event;
    console.log(`${user_login} has redeemed ${reward.title}`);
});

Did you auth the right user and create subscriptions to the right channel?

My goodness you are fast! This makes sense, thank you so much! Your examples are the best working teaching tools available, especially in moving from the pubsub system to the eventsub system. many thank you’s!

Hi there! Thanks for taking a look at this again! Just a follow up in properly parsing the correct channel point redemption data from events - is there a way to get the rewards’ custom image urls and custom background_color from the channel.channel_points_custom_reward_redemption.add event?

I have found a reference to this data here: Reference | Twitch Developers but ‘image’ or ‘reward.image’ or ‘reward.background_color’ come back as undefined. reward.cost does work so I thought it looked like you would use the .update instead of the .add event, but that appears to only be for making changes to channel point rewards programmatically, and not for retrieving the data for a redeem.

even so, in trying:

socket_space.on('channel.channel_points_custom_reward_redemption.update', ({ metadata, payload }) => {
    					let { event } = payload;
    					let { id , user_login , image , reward } = event;
    					console.log(`${user_login} has updateredeemed ${reward.title}`);
						log(`${user_login} has redeemed ${reward.title} ${id} ${image} ${reward.cost} ${reward.background_color}`);
						
						
						document.getElementById("box").style.background_color = `${reward.background_color}`

						
					});

it does not seem to fire upon channel point redeem like the .add does. I can’t find a reference to the background_color in the .add documentation but felt like it might be in there somewhere?
You rock, this forum is so great (; thank you for your insight!

channel.channel_points_custom_reward_redemption.update only triggers when a redeem changes state.

Say from pending to accepted or pending to completed.

Neither .add/.update of a redeem include “rich” information about the reward it was for such as color/images, for that pre cache with a call to Get Custom Reward

Thank you for this response! always truly stellar input! This makes sense. Is there an example of what a pre-cache call or set of calls would look like for getting all of the custom rewards for a channel? Which may be a lot of data for some channels.

channels can have up to 50 custom rewards.

Just call the API I already linked to as needed/how best works for your application

Understood, thank you! You rule, Barry!

Is there any downside to calling the api when you receive a channel point redeem to get just that one specific reward’s rich data (color & image) by specifying it’s unique reward.id in the call?

nope

1 Like

Thanks a million dude!

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.