Connecting custom chat UI to CXone WebSocket Guest API returns 403 on handshake

Problem
Trying to spin up a bare-bones chat interface without the default widget. We’ve got a custom frontend that needs full DOM control. The plan is to handle the WebSocket handshake directly from a Python backend proxy, then relay messages over a local socket to the browser.

Code

import websockets
import json

token = get_oauth_token()
ws_url = 'wss://api.nicecxone.com/api/v2/webchat/external/conversations'
headers = {'Authorization': f'Bearer {token}', 'Content-Type': 'application/json'}

async with websockets.connect(ws_url, extra_headers=headers) as ws:
 payload = {'type': 'CONVERSATION_CREATE', 'routingData': {'attributes': {'queueId': 'abc-123'}}}
 await ws.send(json.dumps(payload))
 response = await ws.recv()
 print(response)

Error
Server closes the connection immediately with a 1008 policy violation. The initial HTTP upgrade request gets a 403 Forbidden. Docs mention the X-Genesys-Auth header but nothing about WebSocket subprotocols or the exact handshake payload. Tried swapping the auth header. Doesn’t help. The socket drops right after the upgrade request. Also noticed the response headers missing the Sec-WebSocket-Accept field.

Question
What’s the correct way to format the initial CONVERSATION_CREATE frame? The default widget handles the session token exchange automatically, but building this from scratch leaves out the guest token step. Should I be calling /api/v2/webchat/external/sessions first to grab a guestToken before opening the socket? The Python SDK doesn’t expose a WebSocket client, so I’m wiring this up manually. Still stuck on the handshake payload