Building a Custom Chat Client with Genesys Cloud Guest APIs
What You Will Build
- A headless chat application that establishes a conversation with Genesys Cloud without using the embedded Messenger widget.
- Integration with the Genesys Cloud Web Messaging (Guest) API and WebSocket gateway for real-time message exchange.
- Implementation in TypeScript/JavaScript using
axiosfor HTTP requests and native WebSocket for streaming.
Prerequisites
- OAuth Client Type: Client Credentials or Authorization Code flow (depending on whether you represent a logged-in user or a pure guest). For this tutorial, we assume a Client Credentials flow for a backend service acting on behalf of a guest, or a Guest Token flow if handling purely anonymous interactions.
- Required Scopes:
webchat:guest:write(to create webchat sessions)webchat:guest:read(to read session details)conversation:webchat:read(to read conversation history)conversation:webchat:write(to send messages)
- SDK/API Version: Genesys Cloud API v2.
- Language/Runtime: Node.js (v16+).
- Dependencies:
axios: For REST API calls.ws: For WebSocket connections (optional, nativeWebSocketworks in browsers).
Authentication Setup
The Guest API requires a valid JWT token. Unlike internal APIs that use platformClient.auth, Guest APIs often use a specific endpoint to obtain a guest token if you are building a frontend-facing solution, or you use your standard OAuth token if the backend proxies the request.
For this tutorial, we will assume a backend service that holds a valid OAuth token with the webchat:guest:* scopes. If you are building a pure frontend solution, you must first exchange a guest credential for a token via /api/v2/oauth/token.
Step 1: Obtain OAuth Token (Client Credentials)
import axios from 'axios';
const GENESYS_DOMAIN = 'mycompany.mypurecloud.com';
const CLIENT_ID = 'your_client_id';
const CLIENT_SECRET = 'your_client_secret';
interface TokenResponse {
access_token: string;
token_type: string;
expires_in: number;
scope: string;
}
async function getOAuthToken(): Promise<string> {
const url = `https://${GENESYS_DOMAIN}/oauth/token`;
const data = new URLSearchParams();
data.append('grant_type', 'client_credentials');
try {
const response = await axios.post<TokenResponse>(url, data, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: `Basic ${Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`).toString('base64')}`,
},
});
if (!response.data.access_token) {
throw new Error('Failed to retrieve access token');
}
return response.data.access_token;
} catch (error) {
if (axios.isAxiosError(error)) {
console.error('OAuth Error:', error.response?.data || error.message);
} else {
console.error('Unknown Error:', error);
}
throw error;
}
}
Implementation
Step 1: Create a Webchat Session
Before sending messages, you must create a Webchat Session. This session represents the guest’s connection to the Genesys Cloud routing system. It returns a webchatSessionId which is critical for all subsequent API calls.
Endpoint: POST /api/v2/webchat/guests/sessions
Required Headers:
Authorization: Bearer <token>Content-Type: application/json
interface WebchatSessionPayload {
language: string;
displayName: string;
email?: string;
tags?: string[];
metadata?: Record<string, string>;
}
interface WebchatSessionResponse {
id: string;
webchatSessionId: string;
language: string;
displayName: string;
email?: string;
createdTime: string;
updatedTime: string;
}
async function createWebchatSession(
token: string,
payload: WebchatSessionPayload
): Promise<WebchatSessionResponse> {
const url = `https://${GENESYS_DOMAIN}/api/v2/webchat/guests/sessions`;
try {
const response = await axios.post<WebchatSessionResponse>(url, payload, {
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
});
return response.data;
} catch (error) {
if (axios.isAxiosError(error)) {
// Handle 400 Bad Request (invalid payload) or 401 Unauthorized
console.error('Session Creation Error:', error.response?.data);
}
throw error;
}
}
Expected Response:
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"webchatSessionId": "ws-12345678-1234-1234-1234-123456789012",
"language": "en-US",
"displayName": "Guest User",
"email": "guest@example.com",
"createdTime": "2023-10-27T10:00:00.000Z",
"updatedTime": "2023-10-27T10:00:00.000Z"
}
Step 2: Establish WebSocket Connection for Real-Time Messaging
While you can poll for messages, the standard pattern for Webchat is to use the WebSocket gateway. This allows you to send and receive messages in real-time. The WebSocket URL is derived from your domain.
WebSocket URL: wss://<domain>/webchat/v1/guests/sessions/<webchatSessionId>
Note: The WebSocket handshake must include the Authorization header with the Bearer token.
import WebSocket from 'ws';
interface WebchatMessage {
type: 'text' | 'typing' | 'ack';
text?: string;
conversationId?: string;
messageDirection?: 'inbound' | 'outbound';
}
class WebchatClient {
private ws: WebSocket | null = null;
private token: string;
private webchatSessionId: string;
private domain: string;
constructor(token: string, webchatSessionId: string, domain: string) {
this.token = token;
this.webchatSessionId = webchatSessionId;
this.domain = domain;
}
connect(): Promise<void> {
return new Promise((resolve, reject) => {
const wsUrl = `wss://${this.domain}/webchat/v1/guests/sessions/${this.webchatSessionId}`;
this.ws = new WebSocket(wsUrl, {
headers: {
Authorization: `Bearer ${this.token}`,
},
});
this.ws.on('open', () => {
console.log('WebSocket connected');
resolve();
});
this.ws.on('error', (err) => {
console.error('WebSocket Error:', err);
reject(err);
});
this.ws.on('close', () => {
console.log('WebSocket closed');
});
this.ws.on('message', (data) => {
try {
const message: WebchatMessage = JSON.parse(data.toString());
this.handleIncomingMessage(message);
} catch (e) {
console.error('Failed to parse WebSocket message:', e);
}
});
});
}
private handleIncomingMessage(message: WebchatMessage) {
if (message.type === 'text') {
console.log(`Received Message [${message.messageDirection}]: ${message.text}`);
} else if (message.type === 'typing') {
console.log('Agent is typing...');
}
}
sendMessage(text: string, conversationId: string): void {
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
throw new Error('WebSocket is not connected');
}
const payload: WebchatMessage = {
type: 'text',
text: text,
conversationId: conversationId,
messageDirection: 'inbound',
};
this.ws.send(JSON.stringify(payload));
}
close(): void {
if (this.ws) {
this.ws.close();
}
}
}
Step 3: Retrieve Conversation History
When a guest reconnects or when you need to display previous messages, you must query the Conversation API. The Webchat Session ID is linked to a specific Conversation ID. You can retrieve the Conversation ID from the WebSocket messages or by querying the session details.
Endpoint: GET /api/v2/conversations/webchat/{conversationId}/messages
Required Scopes: conversation:webchat:read
interface ConversationMessage {
id: string;
conversationId: string;
from: {
name: string;
externalId: string;
};
to: {
name: string;
externalId: string;
};
text?: string;
date: string;
direction: 'inbound' | 'outbound';
}
async function getConversationMessages(
token: string,
conversationId: string,
pageSize: number = 50,
pageNumber: number = 1
): Promise<ConversationMessage[]> {
const url = `https://${GENESYS_DOMAIN}/api/v2/conversations/webchat/${conversationId}/messages`;
try {
const response = await axios.get(url, {
headers: {
Authorization: `Bearer ${token}`,
},
params: {
pageSize,
pageNumber,
},
});
return response.data.entities || [];
} catch (error) {
if (axios.isAxiosError(error)) {
// Handle 404 if conversation ID is invalid
console.error('Fetch Messages Error:', error.response?.data);
}
throw error;
}
}
Pagination Note: The response includes pageSize, pageNumber, and nextPage. You should loop through pages until nextPage is null to retrieve full history.
Step 4: Integrating Session to Conversation
The Webchat Session does not immediately have a Conversation ID. The Conversation ID is assigned once the guest is routed to a queue or an agent. You can detect this by listening for a message with a conversationId field in the WebSocket stream, or by polling the session status.
A robust approach is to listen for the conversationId in the WebSocket messages. Often, the first message from the system (or the initial handshake response) may not contain it, but subsequent routing events will.
Alternatively, you can query the Webchat Session details to see if a conversationId has been assigned.
Endpoint: GET /api/v2/webchat/guests/sessions/{webchatSessionId}
interface SessionDetailsResponse {
id: string;
webchatSessionId: string;
conversationId?: string; // Present if routed
status: string;
}
async function getSessionDetails(
token: string,
webchatSessionId: string
): Promise<SessionDetailsResponse> {
const url = `https://${GENESYS_DOMAIN}/api/v2/webchat/guests/sessions/${webchatSessionId}`;
try {
const response = await axios.get<SessionDetailsResponse>(url, {
headers: {
Authorization: `Bearer ${token}`,
},
});
return response.data;
} catch (error) {
if (axios.isAxiosError(error)) {
console.error('Get Session Details Error:', error.response?.data);
}
throw error;
}
}
Complete Working Example
This script creates a session, connects via WebSocket, waits for a conversation ID to be assigned (simulated by waiting for a message with an ID or polling), and then sends a message.
import axios from 'axios';
import WebSocket from 'ws';
// Configuration
const GENESYS_DOMAIN = 'mycompany.mypurecloud.com';
const CLIENT_ID = 'your_client_id';
const CLIENT_SECRET = 'your_client_secret';
// Types
interface TokenResponse { access_token: string; }
interface WebchatSessionResponse { id: string; webchatSessionId: string; conversationId?: string; }
interface WebchatMessage { type: string; text?: string; conversationId?: string; }
// 1. Authentication
async function getOAuthToken(): Promise<string> {
const url = `https://${GENESYS_DOMAIN}/oauth/token`;
const data = new URLSearchParams();
data.append('grant_type', 'client_credentials');
const response = await axios.post<TokenResponse>(url, data, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: `Basic ${Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`).toString('base64')}`,
},
});
return response.data.access_token;
}
// 2. Create Session
async function createSession(token: string): Promise<WebchatSessionResponse> {
const url = `https://${GENESYS_DOMAIN}/api/v2/webchat/guests/sessions`;
const payload = {
language: 'en-US',
displayName: 'API Guest',
email: 'guest@apitest.com',
};
const response = await axios.post<WebchatSessionResponse>(url, payload, {
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
});
return response.data;
}
// 3. WebSocket Client
async function startChat(token: string, webchatSessionId: string) {
const wsUrl = `wss://${GENESYS_DOMAIN}/webchat/v1/guests/sessions/${webchatSessionId}`;
const ws = new WebSocket(wsUrl, {
headers: { Authorization: `Bearer ${token}` },
});
let conversationId: string | null = null;
ws.on('open', () => {
console.log('WebSocket Connected');
// Send initial message to trigger routing if needed, or wait for agent
// Note: In a real scenario, you might wait for a specific system message
});
ws.on('message', (data) => {
const message: WebchatMessage = JSON.parse(data.toString());
console.log('Incoming:', JSON.stringify(message));
// Capture Conversation ID if present
if (message.conversationId && !conversationId) {
conversationId = message.conversationId;
console.log(`Conversation ID Assigned: ${conversationId}`);
// Now we can send a message to the conversation
if (conversationId) {
const userMessage: WebchatMessage = {
type: 'text',
text: 'Hello, this is a test message from the Guest API.',
conversationId: conversationId,
messageDirection: 'inbound',
};
ws.send(JSON.stringify(userMessage));
}
}
});
ws.on('error', (err) => {
console.error('WebSocket Error:', err);
});
// Keep alive for demo purposes
setTimeout(() => {
ws.close();
console.log('Connection Closed');
}, 30000);
}
// Main Execution
async function main() {
try {
console.log('1. Obtaining OAuth Token...');
const token = await getOAuthToken();
console.log('2. Creating Webchat Session...');
const session = await createSession(token);
console.log(`Session Created: ${session.webchatSessionId}`);
console.log('3. Starting WebSocket Chat...');
await startChat(token, session.webchatSessionId);
} catch (error) {
console.error('Fatal Error:', error);
process.exit(1);
}
}
main();
Common Errors & Debugging
Error: 401 Unauthorized
- Cause: The OAuth token is expired, invalid, or lacks the required scopes (
webchat:guest:write). - Fix: Verify your client credentials. Ensure the token was generated recently. Check the
scopefield in the token response to confirmwebchat:guest:*is present.
Error: 403 Forbidden
- Cause: The OAuth client does not have permission to access the Webchat Guest API.
- Fix: In the Genesys Cloud Admin UI, navigate to Security > OAuth 2.0 Clients. Edit your client and ensure the “Webchat Guest” scopes are checked. Also, verify that the user associated with the client (if using Authorization Code) has the necessary role permissions.
Error: WebSocket Connection Refused
- Cause: The
webchatSessionIdis invalid or the session has expired. - Fix: Ensure you are using the
webchatSessionIdreturned immediately after thePOST /api/v2/webchat/guests/sessionscall. Webchat sessions have a TTL (Time To Live). If too much time passes between creation and connection, the session may expire.
Error: 404 Not Found on Conversation Messages
- Cause: You are trying to fetch messages before a conversation ID is assigned.
- Fix: The Webchat Session ID and Conversation ID are different. You cannot query messages until the guest is routed and a
conversationIdis generated. Listen for theconversationIdin the WebSocket stream or poll the session details until it appears.