Cannot send PubSub from EBS

I am trying to send a PubSub message from a PHP EBS. Using the documentation at Reference | Twitch Developers and a php-jwt library, I came up with the following code to create the payload.

$jwt = new JWT(base64_decode(SOTD_SECRET), 'HS256', 3600, 10);
$token = $jwt->encode([
'exp' => $exp,
'user_id' => $userID,
'channel_id' =>$broadcasterID,

The secret is from Extension Secrets under Extension Client Configuration.
I have used several online jwt “testers” and and I am confident that the token is created and signed correctly.

I’m using the following code to send the post.

$body=json_encode(["broadcaster_id" => $broadcasterID,'message' => $message,'target' => ['broadcast'],])	;
$curl = curl_init($url);
mylog("Body: $body");
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
$headers = array( 'Authorization' => 'Bearer ' . $token, 'Client-ID' => SOTD_CLIENT_ID, 'Content-Type' => 'application/json');
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
curl_setopt($curl, CURLOPT_POSTFIELDS, $body);
$resp = curl_exec($curl);
mylog("Response from Send Message: ".$resp);

The Client ID is the extension client ID
The response I get is {“error”:“Unauthorized”,“status”:401,“message”:“OAuth token is missing”}. This seems strange since the documentation clearly states " Authorization - Signed JWT created by an Extension Backend Service (EBS), following the requirements documented in [Signing the JWT]".

I’ve tried different combinations of client id and secrets. I’ve tried opaque_user_id instead of user_id. I’ve tried different combinations for target.
I’ve read as much documentation as I could find, as well as any forum posts on the topic. I cannot figure out what I’m doing wrong.

The URL is


You don’t need 'is_linked'=>'true',

And make sure both the user_id and channel_id are strings not integers


$userID = '123123';


$userID = 123123;

The trailing backslash was a BIG issue. I usually find details like that, but missed it… over and over. Thank you.

Debugging as I type this, so… rubber ducky

‘is_linked’ is gone.
user_id and channel_id were both strings, but I re-cast them anyway to be certain. (verified at
Now the response is {“error”:“Unauthorized”,“status”:401,“message”:“jwt token is required”}
Checked expiration. “exp” is +1 hour. (verified on
I have tried without base64_decodeing the secret. Same error so I changed it back.
I re-wrote the header and found two errors. No colon and no space after Authorization.
New error is {“error”:“Unauthorized”,“status”:401,“message”:“Client ID is missing”}.
Of course it’s missing. Same reason. No colon and no space: Same with Content-Type. I changed the header creation to

$headers = array( 
			'Authorization: '.'Bearer ' . $token,
			'Client-ID: ' . SOTD_CLIENT_ID,
			'Content-Type: ' .'application/json');

Problem solved and the message is being recieved by the extension.
Thank you again! This has caused me uncounted hours of grief.

I got as far as the URL before debugging your actual PHP to spot the header malconstruct :stuck_out_tongue:

No worries. I just needed to get past the URL error. The backslash was a leftover from trying the endpoint. Feeling doubly ignorant since I recall some time in the past week seeing you mention this exact thing in an older post.
Off Topic: I tried letting my cat be a rubber ducky, but he just walks away.