Channel Point Redemption EventSub Problem

I’m currently trying to get a program to work which extracts user messages from channel point redemption events in my chat.

The entire setup process works fine, I recieve my app access token, create the subscription and so on and when I test a redemption using the twitch-cli trigger it successfully and correctly identifies and extracts the information.

However when I redeem an actual reward in my chat no sign of any activity is shown across my webhook server. I feel like I’m missing an integral part where I tell the subscription to listen to my chat in particular but I can’t seem to find that spot.

Here’s my eventsub setup function

sync def setup_eventsub():
    app_access_token = await get_app_access_token(twitch_client_id, twitch_client_secret)
    if not app_access_token:
        print("Failed to obtain app access token. Exiting...")
        return

    async with httpx.AsyncClient() as client:
        headers = {
            "Client-ID": twitch_client_id,
            "Authorization": f"Bearer {app_access_token}",
            "Content-Type": "application/json",
        }

        # Delete existing subscriptions (optional, but useful for testing)
        response = await client.get("https://api.twitch.tv/helix/eventsub/subscriptions", headers=headers)
        
        if response.status_code == 200:
            existing_subscriptions = response.json()["data"]
            for sub in existing_subscriptions:
                await client.delete(f"https://api.twitch.tv/helix/eventsub/subscriptions?id={sub['id']}", headers=headers)
        else:
            print(f"Failed to get existing subscriptions: {response.status_code} - {response.text}")
            return  # Exit the function if there was an error

        # Create a new subscription for channel points
        payload = {
            "type": "channel.channel_points_custom_reward_redemption.add",
            "version": "1",
            "condition": {
                "broadcaster_user_id": twitch_channel_id,
            },
            "transport": {
                "method": "webhook",
                "callback": webhook_url,
                "secret": "<My Secret>", 
            },
        }


        response = await client.post("https://api.twitch.tv/helix/eventsub/subscriptions", headers=headers, json=payload)
        if response.status_code == 202:
            print("EventSub subscription created successfully.")
        else:
            print(f"Failed to create EventSub subscription: {response.status_code} - {response.text}")

During setup it always returns 202 “EventSub subscription created successfully.”, so the setup itself seems to run fine.

There’s no other place in my code where I mention my channel ID except for this snippet. Am I missing another spot for that?

I’m also not quite sure if my twitch application that I created for this purpose needs any further investigation. Right now it’s only oauth redirection url is my ngrok webhook server address.

Appreciate any thoughts!

202 means that Twitch accepted the sub request, and will then asynchronously call your callback URL to verify it, and complete setup of the subscription.

You need to check if your callback is being called, and if you are correctnyl sending back the challenge
And then if the subscription is enabled in Get EventSub Subscriptions - Reference | Twitch Developers

This may suggest that your webhook_url has invalid SSL or is not web accessable, hence no data. But check if your subscription is enabled

I suspect, that since you are using ngrok you didn’t challenge response correctly.

My subscription does indeed have the status “webhook_callback_verification_failed” after the initial launch thank you already!

I do respond to the callback verification with the challenge successfully on my side though, but I reckon this is where the SSL problem you mentioned comes into play right?

I’ll leave my eventsub handler here if that helps

@app.post("/webhook")
async def handle_eventsub(request: Request):
    secret = "<~>"
    if not await verify_signature(request, secret):
        return JSONResponse(status_code=401, content={"status": "invalid signature"})
    headers = request.headers
    body = await request.json()

    print("Received request:", headers, body)

    if "Twitch-Eventsub-Message-Type" in headers:
        message_type = headers["Twitch-Eventsub-Message-Type"]
        print("Message type:", message_type)

        
        if message_type == "webhook_callback_verification":
            challenge = body["challenge"]
            print("Received webhook_callback_verification, responding with challenge:", challenge)
            return JSONResponse(content={"challenge": challenge})

        elif message_type == "notification":
            event = body["event"]
            print("Received notification event:", event)

            reward_title = event["reward"]["title"]
            user = event["user_name"]
            custom_input = event["user_input"]

            if reward_title == reward_name:
                print(f"User {user} redeemed the reward '{reward_title}' with input: {custom_input}")
                handle_reward(custom_input)

        print(custom_input)

    return JSONResponse(content={"status": "ok"})

The issue may be you’re returning a JSON response to the verification, when you should be returning just the plain text challenge.

If you use the Twitch CLI https://dev.twitch.tv/docs/cli/event-command/#verifying-your-event-handlers-challenge-response you can use that to test if your response is correct.

This is incorrect.

You need to response with plain text. Not a JSON blob

You cat test this usign the Twitch CLI with the twitch event verify-subscription test - https://github.com/twitchdev/twitch-cli/blob/main/docs/event.md#verify-subscription

Oh that makes so much sense.

Tested it with plain text response, worked out first try. Thank you very much (especially for the VERY quick help)!

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