Sending Typing Indicators and Read Receipts via the Genesys Cloud Web Messaging API

Sending Typing Indicators and Read Receipts via the Genesys Cloud Web Messaging API

What You Will Build

  • This tutorial builds a client-side JavaScript module that sends typing and read status updates to a Genesys Cloud Web Messaging conversation.
  • It uses the Genesys Cloud Web Messaging SDK (@genesyscloud/web-messaging-sdk) and the underlying WebSocket protocol.
  • The implementation is in JavaScript/TypeScript for browser environments.

Prerequisites

  • Platform: Genesys Cloud CX
  • SDK: @genesyscloud/web-messaging-sdk (v1.0.0+)
  • Runtime: Modern Browser (Chrome, Firefox, Safari, Edge) with WebSocket support.
  • Dependencies: @genesyscloud/web-messaging-sdk installed via npm or loaded via CDN.
  • Conceptual Knowledge: Understanding of the Web Messaging lifecycle (Initiate → Connect → Send).

Authentication Setup

Genesys Cloud Web Messaging does not use standard OAuth 2.0 client credentials for the guest client. Instead, it uses a Server-Side Initiation model. Your backend server generates a one-time use token or initiates the session with Genesys Cloud, and your frontend receives a deploymentId and optionally a configurationId.

The SDK handles the WebSocket handshake and authentication internally once initialized with the correct deployment configuration. You do not manage JWT tokens directly in the browser.

Required Configuration:

  1. Deployment ID: The unique identifier for your Web Messaging deployment.
  2. Configuration ID (Optional): If using dynamic forms or specific branding.

Implementation

Step 1: Initialize the Web Messaging Client

Before sending any messages or status indicators, you must establish a connection to the Genesys Cloud messaging infrastructure. This involves initializing the SDK and connecting to the deployment.

import { WebMessagingSDK } from '@genesyscloud/web-messaging-sdk';

/**
 * Initializes and connects the Web Messaging client.
 * @param {string} deploymentId - The unique ID of the Web Messaging deployment.
 * @param {string} configurationId - Optional configuration ID for dynamic forms.
 * @returns {Promise<WebMessagingSDK>} The initialized SDK instance.
 */
async function initWebMessaging(deploymentId, configurationId = null) {
  try {
    const sdk = new WebMessagingSDK({
      deploymentId: deploymentId,
      configurationId: configurationId,
      // Optional: Configure logging for debugging
      logger: {
        log: (message) => console.log(`[WebMessaging]: ${message}`),
        error: (message) => console.error(`[WebMessaging Error]: ${message}`),
      },
    });

    // Connect to the WebSocket endpoint
    await sdk.connect();

    console.log('Connected to Genesys Cloud Web Messaging.');
    return sdk;
  } catch (error) {
    console.error('Failed to connect to Web Messaging:', error);
    throw error;
  }
}

Expected Response:
The connect() method resolves when the WebSocket connection is established and the initial handshake with the Genesys Cloud gateway is complete. No JSON payload is returned; the success is indicated by the resolved promise and subsequent onConnected event if subscribed.

Error Handling:

  • 401 Unauthorized: Invalid deploymentId.
  • 5xx Server Error: Genesys Cloud gateway is unavailable. Implement exponential backoff if calling this in a retry loop.

Step 2: Send a Typing Indicator

Typing indicators are transient signals sent to the agent or bot to show the guest is composing a message. These are not stored in the conversation history. The API treats these as real-time presence updates.

In the Genesys Cloud Web Messaging protocol, typing indicators are sent via the sendTypingIndicator method on the SDK instance. This method translates to a WebSocket message with the type TYPING.

/**
 * Sends a typing indicator to the current conversation.
 * @param {WebMessagingSDK} sdk - The initialized SDK instance.
 */
async function sendTypingIndicator(sdk) {
  try {
    // Check if the conversation is active
    if (!sdk.isConversationActive()) {
      console.warn('No active conversation to send typing indicator.');
      return;
    }

    // Send the typing indicator
    await sdk.sendTypingIndicator();

    console.log('Typing indicator sent.');
  } catch (error) {
    console.error('Failed to send typing indicator:', error);
    // Handle specific errors, e.g., disconnected state
    if (error.code === 'DISCONNECTED') {
      console.error('Client is disconnected. Reconnect required.');
    }
  }
}

Non-Obvious Parameters:

  • The sendTypingIndicator method typically does not take arguments in the standard SDK implementation because it applies to the current active channel.
  • Rate Limiting: Do not send typing indicators on every keystroke. Genesys Cloud and most client implementations debounce these signals. A common pattern is to send the indicator on the first keystroke and then every 2-3 seconds if typing continues.

Edge Cases:

  • If the user stops typing, you do not need to send a “stop typing” signal. The absence of a TYPING message for a short duration (usually 1-2 seconds) is interpreted by the agent interface as the user stopping. However, some implementations allow sending a NOT_TYPING signal explicitly if supported by the specific SDK version. Check your SDK documentation for sendStopTypingIndicator.

Step 3: Send a Read Receipt

Read receipts confirm that the guest has viewed the messages. This updates the message status in the agent interface and can trigger automation (e.g., closing a bot dialog).

Unlike typing indicators, read receipts are often tied to specific message IDs. The SDK provides a method to mark messages as read.

/**
 * Marks specific messages as read.
 * @param {WebMessagingSDK} sdk - The initialized SDK instance.
 * @param {string[]} messageIds - Array of message IDs to mark as read.
 */
async function markMessagesAsRead(sdk, messageIds) {
  try {
    if (!sdk.isConversationActive()) {
      console.warn('No active conversation to mark messages as read.');
      return;
    }

    // Filter out any undefined or null IDs
    const validIds = messageIds.filter(id => id && id.length > 0);

    if (validIds.length === 0) {
      return;
    }

    // Send the read receipt
    // Note: The exact method name may vary slightly by SDK version.
    // Commonly it is 'sendReadReceipt' or 'markAsRead'.
    // If the SDK does not expose a direct method, you may need to use the raw WebSocket send.
    if (typeof sdk.sendReadReceipt === 'function') {
      await sdk.sendReadReceipt(validIds);
    } else {
      // Fallback: Check if 'markAsRead' exists
      if (typeof sdk.markAsRead === 'function') {
        await sdk.markAsRead(validIds);
      } else {
        console.warn('Read receipt method not found in SDK. Check version.');
        return;
      }
    }

    console.log(`Marked ${validIds.length} messages as read.`);
  } catch (error) {
    console.error('Failed to mark messages as read:', error);
  }
}

Expected Response:
The server acknowledges the receipt. The messages in the local state may update their read property to true.

Error Handling:

  • 404 Not Found: If a messageId is invalid or belongs to a different conversation, the server may ignore it or return an error. Always validate IDs locally before sending.

Step 4: Handling Incoming Messages and Auto-Read

In a production application, you typically want to mark messages as read automatically when they appear on the screen. This requires subscribing to the message stream.

/**
 * Subscribes to incoming messages and marks them as read when displayed.
 * @param {WebMessagingSDK} sdk - The initialized SDK instance.
 */
function setupAutoReadReceipts(sdk) {
  // Subscribe to new messages
  sdk.on('messageReceived', (message) => {
    // Only mark as read if the message is from an agent or bot (not self)
    if (message.senderType !== 'guest') {
      // Debounce the read receipt to avoid sending too many updates
      // In a real app, use a debounce utility (e.g., lodash.debounce)
      markMessagesAsRead(sdk, [message.id]);
    }
  });

  // Subscribe to connection status changes
  sdk.on('disconnected', () => {
    console.warn('Web Messaging disconnected.');
    // Attempt reconnection logic here
  });
}

Complete Working Example

This is a full, copy-pasteable module that initializes the client, sets up listeners, and exposes functions to send typing and read receipts.

// webMessagingClient.js
import { WebMessagingSDK } from '@genesyscloud/web-messaging-sdk';

class WebMessagingClient {
  constructor(deploymentId, configurationId = null) {
    this.deploymentId = deploymentId;
    this.configurationId = configurationId;
    this.sdk = null;
    this.isInitialized = false;
    this.typingTimeout = null;
  }

  async initialize() {
    try {
      this.sdk = new WebMessagingSDK({
        deploymentId: this.deploymentId,
        configurationId: this.configurationId,
        logger: {
          log: (msg) => console.log(`[WM]: ${msg}`),
          error: (msg) => console.error(`[WM Error]: ${msg}`),
        },
      });

      await this.sdk.connect();
      this.isInitialized = true;
      this.setupEventListeners();
      console.log('Web Messaging Client Initialized.');
    } catch (error) {
      console.error('Initialization failed:', error);
      throw error;
    }
  }

  setupEventListeners() {
    if (!this.sdk) return;

    this.sdk.on('messageReceived', (message) => {
      this.handleIncomingMessage(message);
    });

    this.sdk.on('disconnected', () => {
      console.warn('Client disconnected.');
      this.isInitialized = false;
    });

    this.sdk.on('connected', () => {
      console.log('Client connected.');
      this.isInitialized = true;
    });
  }

  handleIncomingMessage(message) {
    // Auto-read non-guest messages
    if (message.senderType !== 'guest') {
      this.markAsRead([message.id]);
    }
  }

  /**
   * Sends a typing indicator with debounce logic.
   */
  sendTyping() {
    if (!this.isInitialized || !this.sdk) {
      console.warn('Client not initialized.');
      return;
    }

    // Clear existing timeout to debounce
    if (this.typingTimeout) {
      clearTimeout(this.typingTimeout);
    }

    // Send immediately
    this.sdk.sendTypingIndicator().catch(err => console.error('Typing error:', err));

    // Schedule a "stop typing" signal if supported, or just let it timeout
    // Here we just clear the timeout to prevent re-sending too frequently
    this.typingTimeout = setTimeout(() => {
      this.typingTimeout = null;
    }, 2000);
  }

  /**
   * Marks an array of message IDs as read.
   * @param {string[]} messageIds
   */
  async markAsRead(messageIds) {
    if (!this.isInitialized || !this.sdk) {
      console.warn('Client not initialized.');
      return;
    }

    try {
      // Check for method existence based on SDK version
      if (this.sdk.sendReadReceipt) {
        await this.sdk.sendReadReceipt(messageIds);
      } else if (this.sdk.markAsRead) {
        await this.sdk.markAsRead(messageIds);
      } else {
        console.warn('No read receipt method available in current SDK.');
      }
    } catch (error) {
      console.error('Failed to mark as read:', error);
    }
  }

  disconnect() {
    if (this.sdk) {
      this.sdk.disconnect();
      this.isInitialized = false;
    }
  }
}

export default WebMessagingClient;

Common Errors & Debugging

Error: WebSocket connection failed

  • What causes it: The deploymentId is invalid, or the browser’s firewall/proxy is blocking WebSocket connections to *.mypurecloud.com.
  • How to fix it: Verify the deploymentId in the Genesys Cloud Admin console under Web Messaging. Check browser console for network tab WebSocket failures. Ensure CORS is not blocking the initial handshake (though Web Messaging SDKs usually bypass standard CORS via WebSocket).

Error: Method sendReadReceipt not found

  • What causes it: You are using an older version of the @genesyscloud/web-messaging-sdk that does not expose the read receipt method directly, or the method name has changed.
  • How to fix it: Check the SDK version. If using v1.x, the method might be markAsRead. If the method is missing, you may need to send a raw WebSocket message. The payload for a read receipt typically looks like:
    {
      "type": "READ",
      "messageIds": ["msg-123", "msg-456"]
    }
    
    You can send this via sdk.sendRawMessage(payload) if exposed.

Error: 429 Too Many Requests

  • What causes it: Sending typing indicators on every keystroke without debouncing.
  • How to fix it: Implement a debounce function. Only send the typing indicator when the user starts typing and then at intervals (e.g., every 3 seconds) if they continue. Do not send more than one typing indicator per second.

Official References