Laravel Guzzle WebSocket auth handshake failing with 403

Could someone explain why my Guzzle client gets a 403 when trying to open the WebSocket connection for conversation events? I am passing the bearer token in the query string like the docs suggest, but the server rejects it immediately.

I am using standard PHP stream sockets with Guzzle for the HTTP upgrade request. The token is valid and works fine for REST calls to /api/v2/notifications, but the WS handshake fails every time.

Here is the cURL equivalent of my request headers. Any idea what I am missing in the Upgrade sequence?

Ah, this is a recognized issue with how Guzzle handles the Upgrade header in PHP streams. You must explicitly set Upgrade: websocket and Connection: Upgrade headers instead of relying on the library’s default HTTP behavior, as the GC WebSocket endpoint strictly validates the handshake protocol.

$client->request('GET', 'wss://api.genesys.cloud/v2/analytics/conversations/events', [
 'query' => ['token' => $bearerToken],
 'headers' => [
 'Upgrade' => 'websocket',
 'Connection' => 'Upgrade',
 'Sec-WebSocket-Key' => base64_encode(random_bytes(16)),
 'Sec-WebSocket-Version' => '13'
 ],
 'stream' => true
]);