Sending typing indicators and read receipts via the Web Messaging Guest API
What You Will Build
- You will build a client-side JavaScript module that sends real-time typing indicators and read receipts to a Genesys Cloud Web Messaging session.
- This implementation uses the Genesys Cloud Web Messaging Client SDK (
genesys-cloud-web-messaging-client-sdk) to manage the WebSocket connection and message lifecycle. - The code is written in modern JavaScript (ES6+) and runs in a browser environment or Node.js with a DOM shim.
Prerequisites
- OAuth Client Type: You do not need a standard OAuth client for the Guest API. The Guest API is stateless and relies on a
guestTokengenerated by theinitSessionorcreateSessionflow. - SDK Version:
@genesys/cloud-web-messaging-client-sdkversion 1.0.0 or later. - Language/Runtime: Node.js 18+ or a modern browser environment (Chrome, Firefox, Safari).
- External Dependencies:
@genesys/cloud-web-messaging-client-sdkaxios(for initial session initialization if not handled by a server-side proxy)
Authentication Setup
The Web Messaging Guest API does not use standard OAuth2 Bearer tokens for the WebSocket connection. Instead, it uses a short-lived guestToken obtained during session initialization. This token is scoped to a specific conversation and expires when the session ends.
You must first initialize a session to obtain this token. The following example assumes you have already obtained a guestToken and conversationId from your server-side initialization logic. In production, your backend should call /api/v2/conversations/messaging to start the session and return the guestToken to the frontend.
// mock-init.js
// This represents the response from your backend after calling Genesys Cloud APIs
const sessionData = {
guestToken: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...", // Real JWT token
conversationId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
deploymentId: "deployment-uuid-here"
};
export default sessionData;
Implementation
Step 1: Initialize the Web Messaging Client
The first step is to instantiate the WebMessagingClient from the SDK. This client manages the WebSocket connection to the Genesys Cloud messaging infrastructure. You must provide the guestToken and configuration options.
import { WebMessagingClient } from '@genesys/cloud-web-messaging-client-sdk';
import sessionData from './mock-init.js';
/**
* Initializes the Web Messaging Client.
* @returns {Promise<WebMessagingClient>} The configured client instance.
*/
async function initMessagingClient() {
try {
// Configure the client with the guest token and deployment ID
const client = new WebMessagingClient({
guestToken: sessionData.guestToken,
deploymentId: sessionData.deploymentId,
// Enable debug logging for troubleshooting WebSocket issues
debug: true
});
// Connect to the WebSocket endpoint
await client.connect();
console.log('Successfully connected to Web Messaging service.');
return client;
} catch (error) {
console.error('Failed to initialize Web Messaging Client:', error.message);
throw error;
}
}
Expected Behavior:
- If the
guestTokenis valid, the client establishes a secure WebSocket connection. - If the token is expired or invalid, the SDK throws an
AuthenticationError.
Error Handling:
- 401 Unauthorized: The
guestTokenis invalid or expired. You must re-initialize the session on your backend. - Network Error: The WebSocket connection failed. Implement exponential backoff retry logic.
Step 2: Sending Typing Indicators
Typing indicators are sent as special message types to signal that the user is composing a message. The Genesys Cloud API supports two primary typing events: typing and stopTyping.
The SDK provides a method sendTypingIndicator to handle this. You must send the typing event when the user starts typing and the stopTyping event when they stop or send the message.
/**
* Sends a typing indicator to the agent.
* @param {WebMessagingClient} client - The connected client instance.
* @param {string} type - The type of typing event: 'typing' or 'stopTyping'.
*/
async function sendTypingIndicator(client, type = 'typing') {
try {
// The SDK abstracts the JSON payload structure
await client.sendTypingIndicator({
type: type, // 'typing' or 'stopTyping'
conversationId: sessionData.conversationId
});
console.log(`Sent ${type} indicator.`);
} catch (error) {
// Handle specific SDK errors
if (error.code === 'INVALID_STATE') {
console.error('Client is not connected or session has ended.');
} else {
console.error('Failed to send typing indicator:', error.message);
}
}
}
Non-Obvious Parameters:
type: Must be exactlytypingorstopTyping. Other values are ignored by the server.- Throttling: Do not send
typingevents on every keystroke. Implement a debounce mechanism on the frontend to send the event once after 500ms of inactivity, and sendstopTypingimmediately when the message is sent or the user stops typing for >2 seconds.
Edge Case: Rapid Typing
If you send typing events too frequently, the server may rate-limit your connection. Always debounce.
// Example debounce logic for frontend integration
let typingTimeout = null;
function handleUserInput(client) {
// Clear previous timeout
clearTimeout(typingTimeout);
// Send 'typing' immediately on first input
sendTypingIndicator(client, 'typing');
// Set timeout to send 'stopTyping' after 2 seconds of inactivity
typingTimeout = setTimeout(() => {
sendTypingIndicator(client, 'stopTyping');
}, 2000);
}
Step 3: Sending Read Receipts
Read receipts confirm that the user has viewed a message from the agent. This is critical for agent experience, as it updates the message status in the Agent Desktop.
The SDK provides sendReadReceipt for this purpose. You must provide the messageId of the message that was read.
/**
* Sends a read receipt for a specific message.
* @param {WebMessagingClient} client - The connected client instance.
* @param {string} messageId - The ID of the message that was read.
*/
async function sendReadReceipt(client, messageId) {
try {
await client.sendReadReceipt({
messageId: messageId,
conversationId: sessionData.conversationId
});
console.log(`Sent read receipt for message: ${messageId}`);
} catch (error) {
console.error('Failed to send read receipt:', error.message);
}
}
Integration with UI:
You should call sendReadReceipt when a message enters the visible viewport of the chat window. Use the Intersection Observer API for efficient detection.
/**
* Observes message elements and sends read receipts when they become visible.
* @param {WebMessagingClient} client - The connected client instance.
* @param {HTMLElement} chatContainer - The DOM element containing messages.
*/
function setupReadReceiptObserver(client, chatContainer) {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
// Extract messageId from the DOM element's data attribute
const messageId = entry.target.getAttribute('data-message-id');
if (messageId) {
sendReadReceipt(client, messageId);
// Unobserve after sending to avoid duplicate receipts
observer.unobserve(entry.target);
}
}
});
}, {
root: chatContainer,
threshold: 0.5 // Trigger when 50% of the message is visible
});
// Observe all message elements
const messages = chatContainer.querySelectorAll('.message-agent');
messages.forEach(msg => observer.observe(msg));
}
Step 4: Handling Message Events
To ensure typing indicators and read receipts are sent at the right time, you must listen for incoming messages and local events.
/**
* Sets up event listeners for the Web Messaging Client.
* @param {WebMessagingClient} client - The connected client instance.
*/
function setupEventListeners(client) {
// Listen for incoming messages
client.on('message', (message) => {
console.log('Received message:', message);
// If it is an agent message, ensure it is marked as read if visible
if (message.senderId !== 'guest') {
// In a real app, you would check visibility here
// sendReadReceipt(client, message.id);
}
});
// Listen for typing indicators from the agent
client.on('typing', (data) => {
console.log('Agent is typing:', data);
// Update UI to show "Agent is typing..."
});
// Listen for connection status changes
client.on('connectionStateChange', (state) => {
console.log('Connection state changed:', state);
if (state === 'disconnected') {
// Implement reconnection logic
}
});
}
Complete Working Example
This is a complete, copy-pasteable module that initializes the client, handles typing indicators with debouncing, and sends read receipts.
// web-messaging-client.js
import { WebMessagingClient } from '@genesys/cloud-web-messaging-client-sdk';
import sessionData from './mock-init.js'; // Assume this provides guestToken, conversationId, deploymentId
class WebMessagingSession {
constructor() {
this.client = null;
this.typingTimeout = null;
this.isTyping = false;
}
/**
* Initializes the session and connects to Genesys Cloud.
*/
async initialize() {
try {
this.client = new WebMessagingClient({
guestToken: sessionData.guestToken,
deploymentId: sessionData.deploymentId,
debug: true
});
await this.client.connect();
console.log('Web Messaging Client connected.');
this.setupEventListeners();
} catch (error) {
console.error('Initialization failed:', error);
throw error;
}
}
/**
* Sets up internal event listeners.
*/
setupEventListeners() {
this.client.on('message', (msg) => {
console.log('Message received:', msg);
// Trigger read receipt if message is visible
this.markMessageAsRead(msg.id);
});
this.client.on('typing', (data) => {
console.log('Agent typing event:', data);
});
}
/**
* Handles user input for typing indicators.
* Call this function on every keystroke in your input field.
*/
handleInputChange() {
if (!this.isTyping) {
this.isTyping = true;
this.sendTyping('typing');
}
// Reset debounce timer
clearTimeout(this.typingTimeout);
// Send stopTyping after 2 seconds of inactivity
this.typingTimeout = setTimeout(() => {
this.isTyping = false;
this.sendTyping('stopTyping');
}, 2000);
}
/**
* Sends a typing indicator to the server.
* @param {string} type - 'typing' or 'stopTyping'
*/
async sendTyping(type) {
if (!this.client) return;
try {
await this.client.sendTypingIndicator({
type: type,
conversationId: sessionData.conversationId
});
} catch (error) {
console.error('Failed to send typing indicator:', error);
}
}
/**
* Sends a read receipt for a message.
* @param {string} messageId - The ID of the message.
*/
async markMessageAsRead(messageId) {
if (!this.client || !messageId) return;
try {
await this.client.sendReadReceipt({
messageId: messageId,
conversationId: sessionData.conversationId
});
console.log(`Read receipt sent for ${messageId}`);
} catch (error) {
console.error('Failed to send read receipt:', error);
}
}
/**
* Sends a text message to the agent.
* @param {string} text - The message content.
*/
async sendMessage(text) {
if (!this.client || !text) return;
// Stop typing indicator before sending
if (this.isTyping) {
this.sendTyping('stopTyping');
this.isTyping = false;
}
try {
await this.client.sendMessage({
text: text,
conversationId: sessionData.conversationId
});
} catch (error) {
console.error('Failed to send message:', error);
}
}
/**
* Disconnects the client.
*/
async disconnect() {
if (this.client) {
await this.client.disconnect();
console.log('Disconnected from Web Messaging.');
}
}
}
// Export the class for use in your application
export default WebMessagingSession;
Common Errors & Debugging
Error: 401 Unauthorized
- Cause: The
guestTokenis expired, invalid, or not provided. - Fix: Ensure your backend re-initializes the session if the token is expired. The
guestTokenis short-lived (typically 15-30 minutes). - Code Check: Verify
sessionData.guestTokenis not null or undefined before initializing the client.
Error: WebSocket Connection Failed
- Cause: Network issues, firewall blocking WebSocket ports, or incorrect
deploymentId. - Fix: Check browser console for network errors. Ensure your
deploymentIdmatches the Web Messaging deployment in Genesys Cloud. - Debug: Enable
debug: truein the client configuration to see detailed WebSocket handshake logs.
Error: 429 Too Many Requests
- Cause: Sending typing indicators too frequently (e.g., on every keystroke without debouncing).
- Fix: Implement debouncing on the
typingevent. Only sendtypingonce per typing session andstopTypingwhen idle. - Code Check: Review the
handleInputChangemethod in the complete example. Ensure the timeout is at least 500ms.
Error: Message ID Not Found
- Cause: Attempting to send a read receipt for a message that does not exist or belongs to a different conversation.
- Fix: Ensure
messageIdis taken directly from themessageobject received in theclient.on('message')handler.