Access token always invalid

I’m not well-versed in web-based code, so my apologies in advance.
Use case:
I want to be able to grab a few bits of private info from my own channel (bits:read and channel:read:subscriptions) as well as grab profile pictures and usernames of people in my chat. Previously, I didn’t have subscriptions and bits working, but the picture scraping worked fine.

With the recent change requiring access tokens to access any helix endpoints, I thought I’d go ahead and try to get everything working again.

  1. I created the url to authorize my bot to have the above scopes on my channel
  2. I used said url to get an authorization code
  3. I used the authorization code to get a json object containing an access token and a refresh token
  4. I am able to use the refresh token to get a new json of token data.

However, I am unable to use this token to do anything. When I try to validate it, it returns 401 invalid access token regardless of whether it said "Bearer " or "OAuth
When I try to grab my own user data from the helix endpoint, I get a different response based on what the authorization header says. If it says "Bearer ", I get a 401 invalid access token, and if it says "OAuth ", I get a 401 OAuth token is missing.

I don’t understand why the tokens I’m getting aren’t valid. Can anybody shed some light on this?

Can you show your code?

And you are writing the header has

Authorization: Bearer TOKEN_HERE

As needed for your language of choice

Forgive me if I remove the tokens themselves from the snippets.

Validate token:

curl -H 'Authorization: Bearer ${ACCESS}'\
 -X GET ''

Get test user:

curl -H 'Client-ID: ${CLIENT_ID}'\
 -H 'Authorization: Bearer ${ACCESS}'\
 -X GET ''

Yeah thats expected, never leak those bearers.

But ClientID’s are public.

I don’t see anything wrong with your calls since they are just pure cURL

Whats the URL you construct to validate your bot to have scopes on your channel?${CLIENT_ID}&redirect_uri=${REDIRECT}&response_type=code&scope=bits:read+channel:read:subscriptions

And the json response I get back reflects that.

  "access_token": "...",
  "refresh_token": "...",
  "scope": [
  "token_type": "bearer"

Well thats me out of ideas.

Looks like you are

  • constructing a valid URL.
  • redirecting to twitch
  • as yourself, granting access to your bots application
  • coming back to your code as the redirect_uri
  • exchanging the code from the URL for a valid token

And using that token correctly with your cURL calls.

And refreshing when needed (user tokens are good only for about four hours, and you mentioned you are refreshing and getting a new token)

This reads like you authed your bot to you application, rather than you to your application, but that shouldn’t make a difference to the cURL calls you are doing.

Normally you’d make a oAuth key representing you and your bot would then use that token for the relevant calls to the API where a subscriber/bits scope is needed.

When you refresh you are using the new access token, and not the refresh token as a token right?

I am. Any time a new json is retrieved, the values I care about are extracted and replace the old values. There is only one access/refresh token pair in the system at a time.

I did learn that the old kraken endpoints are still available and seem to be working. If I can’t get this up and running, I can always brush off my old kraken code and try to make do that way. I have absolutely no idea why the helix auth is fighting me so hard.

me neither

They are considered deprecated, so will help you out in the short term but they are set to be removed, at some point

Another reason I’m reluctant to go back.

Just for kicks and giggles, I tried accessing the endpoints another way and wouldn’t you know it, it worked. Therefore, I presume the problem is in the curl commands with the headers. I couldn’t tell you exactly what the problem is, though. At least I now know that the tokens were not invalid.

Seeing as I the curl commands were mostly copy/paste from the documentation page, I figured the verbose log information might be worth a look just in case there’s some update that needs to be made to the docs.

Note: Unnecessary use of -X or --request, GET is already inferred.
* Expire in 0 ms for 6 (transfer 0x1e88880)
(above line more or less repeated about 66 times)
*   Trying
* Expire in 149996 ms for 3 (transfer 0x1e88880)
* Expire in 200 ms for 4 (transfer 0x1e88880)
* Connected to ( port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*   CAfile: none
  CApath: /etc/ssl/certs
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256
* ALPN, server accepted to use h2
* Server certificate:
*  subject:
*  start date: May 24 00:00:00 2019 GMT
*  expire date: Jun 24 12:00:00 2020 GMT
*  subjectAltName: host "" matched cert's ""
*  issuer: C=US; O=Amazon; OU=Server CA 1B; CN=Amazon
*  SSL certificate verify ok.
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x1e88880)
> GET /oauth2/validate HTTP/2
> Host:
> User-Agent: curl/7.64.0
> Accept: */*
> Authorization: Bearer <valid access token>
* Connection state changed (MAX_CONCURRENT_STREAMS == 128)!
< HTTP/2 401
< date: Tue, 19 May 2020 22:23:26 GMT
< content-type: application/json
< content-length: 48
< server: nginx/1.14.1
< access-control-allow-origin: *
< x-ctxlog-logid: 1-5ec45c5e-932e4b78b8cd941cd584a0d8
{"status":401,"message":"invalid access token"}
* Connection #0 to host left intact

Well aside from the validate endpoint requiring “OAuth” not “Bearer” which you already tried theres nothing wrong here, other than your dump being for using “Bearer”

My calls using

curl -H ‘Authorization: OAuth MyToken’
-X GET ‘

Are working here.