WebSocket guest api handshake failing with 401 when bypassing default widget

Could someone explain why my custom vue 3 chat implementation keeps getting dropped during the initial handshake? i’m trying to ditch the heavy default widget and build a lightweight ui using the raw web socket guest api endpoints.

i’ve got the oauth token flow working fine for the admin side but the guest authentication is throwing a fit. here is my setup:

  1. i call /api/v2/conversations/messaging/guests to create a guest profile.
  2. i grab the guestId and messagingChannelId from the response.
  3. i construct the web socket url using the pattern wss://{orgId}.mypurecloud.com/api/v2/conversations/messaging/websockets/guests/{guestId}.

the connection opens briefly then immediately closes with a 401 unauthorized. i’ve verified the token is valid by calling /api/v2/oauth/token right before and it returns a 200 with a fresh access token.

here is the relevant code snippet:

const connectSocket = async () => {
 const token = getAccessToken(); // returns valid bearer token
 const url = `${WS_URL}/guests/${guestId}`;
 
 const ws = new WebSocket(url);
 ws.onopen = () => {
 // send auth message
 ws.send(JSON.stringify({
 type: 'auth',
 token: token
 }));
 };
 
 ws.onclose = (event) => {
 console.log('closed', event.code, event.reason);
 // logs: closed 1008 policy violation
 };
};

i’m sending the auth payload as json immediately after the socket opens. the docs say the token should be passed in the query string or the auth message. i’ve tried both. query string gives me a 400 bad request saying invalid params. auth message gives me the 1008 close code which maps to a 401 on the server side if i check the error logs in the admin portal.

is there a specific header i need to set on the web socket connection? or am i missing a step in the guest creation flow? i’m running this from a vue 3 composition api component and the cors preflight passes fine for the http calls but the web socket handshake is failing.

any ideas? i’ve been stuck on this for two days. the default widget works fine but it’s too bloated for our current ui requirements.

Take a look at at the token exchange sequence. The guest API doesn’t use your admin OAuth token for the WebSocket handshake. It requires a specific guestToken returned from the POST /api/v2/conversations/messaging/guests call.

i call /api/v2/conversations/messaging/guests to create a guest profile. i grab the guest…

The 401 usually happens because you are passing the accessToken from your platform client (admin) into the WebSocket URL or the subsequent API calls. The guest endpoint returns a guestToken in the response body. This is the only token valid for guest-initiated actions.

Here is the correct flow in Node.js:

// 1. Create Guest
const guestResp = await fetch('https://api.mypurecloud.com/api/v2/conversations/messaging/guests', {
 method: 'POST',
 headers: {
 'Authorization': `Bearer ${adminAccessToken}`,
 'Content-Type': 'application/json'
 },
 body: JSON.stringify({
 displayName: 'Guest User',
 email: '[email protected]'
 })
});

const guestData = await guestResp.json();
const guestToken = guestData.guestToken; // <--- USE THIS ONE

// 2. WebSocket Handshake
const wsUrl = `wss://api.mypurecloud.com/api/v2/conversations/messaging/sessions?guestToken=${guestToken}`;
const ws = new WebSocket(wsUrl);

ws.on('open', () => {
 // Send initial message
 ws.send(JSON.stringify({
 type: 'message',
 to: 'some-queue-id',
 text: 'Hello'
 }));
});

Also, ensure your Content-Type is application/json for the POST request. If you are using the JS SDK, platformClient.Conversations.createConversationsMessagingGuests handles the token extraction internally. If you are using raw fetch, you must extract guestToken explicitly. Do not mix admin scopes with guest endpoints.

The suggestion above is spot on. You need to swap the admin token for the guestToken from the guest creation response before initiating the WebSocket connection.

const guestToken = guestResponse.body.guestToken;
ws = new WebSocket(`wss://${host}/ws/v1`, [guestToken]);