How to Configure Web Messaging Deployment to Customize Colors and Launcher Position

How to Configure Web Messaging Deployment to Customize Colors and Launcher Position

What You Will Build

You will build a script that retrieves an existing Genesys Cloud Web Messaging deployment, modifies the UI configuration to change the launcher button color and position, and updates the deployment in the cloud.
This tutorial uses the Genesys Cloud CX API v2 endpoint for Web Messaging deployments.
The implementation is provided in Python using the official genesyscloud SDK and requests library for direct API comparison.

Prerequisites

OAuth Client and Scopes

You must create an OAuth Client in the Genesys Cloud Admin Portal. The client requires the following scopes to manage Web Messaging deployments:

  • webmessaging:deployment:read
  • webmessaging:deployment:write

Ensure the client is authorized for the specific environment (sandbox or production) where the deployment resides.

SDK and Dependencies

This tutorial uses Python 3.9+. You need to install the following packages:

  • genesyscloud: The official Genesys Cloud Python SDK.
  • requests: For making HTTP requests (used in the direct API section).

Install them using pip:

pip install genesyscloud requests

Environment Variables

Store your credentials securely. This tutorial assumes you have set the following environment variables:

  • GENESYS_CLOUD_REGION: e.g., us-east-1 or mypurecloud.com
  • GENESYS_CLOUD_CLIENT_ID: Your OAuth Client ID
  • GENESYS_CLOUD_CLIENT_SECRET: Your OAuth Client Secret

Authentication Setup

Before interacting with the Web Messaging API, you must obtain an access token. The Genesys Cloud API uses OAuth 2.0 Client Credentials flow.

The following function handles token acquisition. In production, you should implement caching to avoid requesting a new token every time, as tokens are valid for one hour.

import os
import requests
from typing import Optional

def get_access_token(region: str, client_id: str, client_secret: str) -> str:
    """
    Retrieves an OAuth 2.0 access token from Genesys Cloud.
    
    Args:
        region: The Genesys Cloud region (e.g., 'mypurecloud.com').
        client_id: The OAuth client ID.
        client_secret: The OAuth client secret.
        
    Returns:
        The access token string.
        
    Raises:
        requests.exceptions.HTTPError: If authentication fails.
    """
    # Construct the auth URL based on region
    if region == "mypurecloud.com":
        auth_url = "https://api.mypurecloud.com"
    else:
        auth_url = f"https://{region}.mypurecloud.com"
        
    token_url = f"{auth_url}/oauth/token"
    
    payload = {
        "grant_type": "client_credentials",
        "client_id": client_id,
        "client_secret": client_secret
    }
    
    headers = {
        "Content-Type": "application/x-www-form-urlencoded"
    }
    
    response = requests.post(token_url, data=payload, headers=headers)
    response.raise_for_status()
    
    data = response.json()
    access_token = data.get("access_token")
    
    if not access_token:
        raise ValueError("Access token not found in response")
        
    return access_token

# Example usage
if __name__ == "__main__":
    region = os.getenv("GENESYS_CLOUD_REGION", "mypurecloud.com")
    client_id = os.getenv("GENESYS_CLOUD_CLIENT_ID")
    client_secret = os.getenv("GENESYS_CLOUD_CLIENT_SECRET")
    
    if not all([client_id, client_secret]):
        raise EnvironmentError("Missing environment variables for credentials.")
        
    token = get_access_token(region, client_id, client_secret)
    print(f"Token acquired successfully.")

Implementation

Step 1: Retrieve the Existing Deployment

To modify a deployment, you must first fetch its current configuration. You cannot patch a deployment; you must retrieve the full object, modify the specific fields, and update the entire object. This is a standard RESTful pattern in Genesys Cloud.

The endpoint is GET /api/v2/webmessaging/deployments/{deploymentId}.

You need the deploymentId. You can find this in the Admin Portal under Engagement > Web Messaging > Deployments, or by listing all deployments via GET /api/v2/webmessaging/deployments.

Here is how to fetch the deployment using the requests library to show the raw JSON structure:

def fetch_deployment(access_token: str, region: str, deployment_id: str) -> dict:
    """
    Fetches the full configuration of a Web Messaging deployment.
    
    Args:
        access_token: The OAuth access token.
        region: The Genesys Cloud region.
        deployment_id: The ID of the deployment to fetch.
        
    Returns:
        A dictionary containing the deployment configuration.
    """
    if region == "mypurecloud.com":
        base_url = "https://api.mypurecloud.com"
    else:
        base_url = f"https://{region}.mypurecloud.com"
        
    url = f"{base_url}/api/v2/webmessaging/deployments/{deployment_id}"
    
    headers = {
        "Authorization": f"Bearer {access_token}",
        "Content-Type": "application/json"
    }
    
    response = requests.get(url, headers=headers)
    
    # Handle common errors
    if response.status_code == 401:
        raise Exception("Authentication failed. Check your token.")
    elif response.status_code == 403:
        raise Exception("Permission denied. Ensure the client has 'webmessaging:deployment:read' scope.")
    elif response.status_code == 404:
        raise Exception(f"Deployment {deployment_id} not found.")
        
    response.raise_for_status()
    return response.json()

# Usage
# deployment_config = fetch_deployment(token, region, "your-deployment-id")
# print(deployment_config)

The response body contains a nested object called uiConfig. This is where all visual customizations live. The structure looks like this:

{
  "id": "your-deployment-id",
  "name": "Support Chat",
  "enabled": true,
  "uiConfig": {
    "launcher": {
      "position": "bottom-right",
      "color": "#0066FF",
      "hoverColor": "#0052CC",
      "iconType": "message"
    },
    "chatWindow": {
      "title": "Support",
      "headerColor": "#0066FF",
      "backgroundColor": "#FFFFFF"
    }
    // ... other config
  }
}

Step 2: Modify UI Configuration

The uiConfig object contains the launcher and chatWindow objects. To customize the launcher position and color, you modify the launcher object.

Key fields for customization:

  • launcher.position: Accepts bottom-left, bottom-right, top-left, or top-right. Default is bottom-right.
  • launcher.color: Hex code for the launcher button background.
  • launcher.hoverColor: Hex code for the button when hovered.
  • launcher.iconType: Accepts message, headset, or custom.

Here is the logic to apply changes:

def update_deployment_ui(config: dict, new_color: str, new_position: str) -> dict:
    """
    Modifies the deployment configuration dictionary to update UI settings.
    
    Args:
        config: The current deployment configuration dictionary.
        new_color: The new hex color for the launcher (e.g., '#FF5733').
        new_position: The new position for the launcher (e.g., 'bottom-left').
        
    Returns:
        The modified configuration dictionary.
    """
    # Validate position
    valid_positions = ["bottom-left", "bottom-right", "top-left", "top-right"]
    if new_position not in valid_positions:
        raise ValueError(f"Invalid position: {new_position}. Must be one of {valid_positions}")
        
    # Ensure uiConfig exists
    if "uiConfig" not in config:
        config["uiConfig"] = {}
        
    if "launcher" not in config["uiConfig"]:
        config["uiConfig"]["launcher"] = {}
        
    # Apply changes
    config["uiConfig"]["launcher"]["color"] = new_color
    config["uiConfig"]["launcher"]["position"] = new_position
    
    # Optional: Set hover color to a darker shade of the new color
    # This is a simple heuristic for demonstration
    config["uiConfig"]["launcher"]["hoverColor"] = new_color
    
    return config

Step 3: Update the Deployment

To persist the changes, send a PUT request to the same endpoint used for fetching. The body must include the entire deployment configuration, not just the changed fields.

OAuth Scope Required: webmessaging:deployment:write

def save_deployment(access_token: str, region: str, deployment_id: str, config: dict) -> dict:
    """
    Updates an existing Web Messaging deployment with new configuration.
    
    Args:
        access_token: The OAuth access token.
        region: The Genesys Cloud region.
        deployment_id: The ID of the deployment to update.
        config: The full deployment configuration dictionary.
        
    Returns:
        The updated deployment configuration from the API response.
    """
    if region == "mypurecloud.com":
        base_url = "https://api.mypurecloud.com"
    else:
        base_url = f"https://{region}.mypurecloud.com"
        
    url = f"{base_url}/api/v2/webmessaging/deployments/{deployment_id}"
    
    headers = {
        "Authorization": f"Bearer {access_token}",
        "Content-Type": "application/json"
    }
    
    # Send the entire config object
    response = requests.put(url, json=config, headers=headers)
    
    # Handle errors
    if response.status_code == 401:
        raise Exception("Authentication failed.")
    elif response.status_code == 403:
        raise Exception("Permission denied. Ensure the client has 'webmessaging:deployment:write' scope.")
    elif response.status_code == 400:
        error_body = response.json()
        raise Exception(f"Bad Request: {error_body.get('message', 'Unknown error')}")
    elif response.status_code == 429:
        raise Exception("Rate limit exceeded. Wait before retrying.")
        
    response.raise_for_status()
    return response.json()

Complete Working Example

The following script combines authentication, fetching, modification, and updating. It includes basic error handling and logging.

import os
import sys
import requests
import logging
from typing import Dict, Any

# Configure logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

class WebMessagingConfigurer:
    def __init__(self, region: str, client_id: str, client_secret: str):
        self.region = region
        self.client_id = client_id
        self.client_secret = client_secret
        self.access_token = None
        
        # Determine base URL
        if region == "mypurecloud.com":
            self.base_url = "https://api.mypurecloud.com"
        else:
            self.base_url = f"https://{region}.mypurecloud.com"

    def authenticate(self) -> None:
        """Acquires OAuth access token."""
        logger.info("Authenticating with Genesys Cloud...")
        token_url = f"{self.base_url}/oauth/token"
        payload = {
            "grant_type": "client_credentials",
            "client_id": self.client_id,
            "client_secret": self.client_secret
        }
        headers = {"Content-Type": "application/x-www-form-urlencoded"}
        
        try:
            response = requests.post(token_url, data=payload, headers=headers, timeout=10)
            response.raise_for_status()
            self.access_token = response.json().get("access_token")
            if not self.access_token:
                raise ValueError("No access token in response")
            logger.info("Authentication successful.")
        except requests.exceptions.RequestException as e:
            logger.error(f"Authentication failed: {e}")
            raise

    def get_deployment(self, deployment_id: str) -> Dict[str, Any]:
        """Fetches deployment configuration."""
        url = f"{self.base_url}/api/v2/webmessaging/deployments/{deployment_id}"
        headers = {
            "Authorization": f"Bearer {self.access_token}",
            "Content-Type": "application/json"
        }
        
        logger.info(f"Fetching deployment: {deployment_id}")
        try:
            response = requests.get(url, headers=headers, timeout=10)
            response.raise_for_status()
            return response.json()
        except requests.exceptions.HTTPError as e:
            if response.status_code == 404:
                logger.error(f"Deployment {deployment_id} not found.")
            elif response.status_code == 403:
                logger.error("Forbidden. Check OAuth scopes.")
            raise

    def update_deployment(self, deployment_id: str, config: Dict[str, Any]) -> Dict[str, Any]:
        """Updates deployment configuration."""
        url = f"{self.base_url}/api/v2/webmessaging/deployments/{deployment_id}"
        headers = {
            "Authorization": f"Bearer {self.access_token}",
            "Content-Type": "application/json"
        }
        
        logger.info(f"Updating deployment: {deployment_id}")
        try:
            response = requests.put(url, json=config, headers=headers, timeout=10)
            response.raise_for_status()
            logger.info("Deployment updated successfully.")
            return response.json()
        except requests.exceptions.HTTPError as e:
            logger.error(f"Update failed: {response.text}")
            raise

    def configure_launcher(self, deployment_id: str, color: str, position: str) -> None:
        """
        High-level method to configure launcher color and position.
        """
        # 1. Fetch
        config = self.get_deployment(deployment_id)
        
        # 2. Modify
        if "uiConfig" not in config:
            config["uiConfig"] = {}
        if "launcher" not in config["uiConfig"]:
            config["uiConfig"]["launcher"] = {}
            
        logger.info(f"Setting launcher color to {color} and position to {position}")
        config["uiConfig"]["launcher"]["color"] = color
        config["uiConfig"]["launcher"]["position"] = position
        
        # 3. Update
        self.update_deployment(deployment_id, config)

def main():
    # Configuration
    REGION = os.getenv("GENESYS_CLOUD_REGION", "mypurecloud.com")
    CLIENT_ID = os.getenv("GENESYS_CLOUD_CLIENT_ID")
    CLIENT_SECRET = os.getenv("GENESYS_CLOUD_CLIENT_SECRET")
    DEPLOYMENT_ID = os.getenv("GENESYS_CLOUD_DEPLOYMENT_ID")
    
    # Customization parameters
    NEW_COLOR = "#00D4AA"  # Teal
    NEW_POSITION = "bottom-left"
    
    if not all([CLIENT_ID, CLIENT_SECRET, DEPLOYMENT_ID]):
        logger.error("Missing environment variables. Set GENESYS_CLOUD_CLIENT_ID, GENESYS_CLOUD_CLIENT_SECRET, and GENESYS_CLOUD_DEPLOYMENT_ID.")
        sys.exit(1)
        
    try:
        # Initialize
        configurer = WebMessagingConfigurer(REGION, CLIENT_ID, CLIENT_SECRET)
        
        # Authenticate
        configurer.authenticate()
        
        # Configure
        configurer.configure_launcher(DEPLOYMENT_ID, NEW_COLOR, NEW_POSITION)
        
        logger.info("Process completed successfully.")
        
    except Exception as e:
        logger.error(f"An error occurred: {e}")
        sys.exit(1)

if __name__ == "__main__":
    main()

Common Errors & Debugging

Error: 403 Forbidden

Cause: The OAuth client does not have the required scopes.
Fix: Ensure the client has webmessaging:deployment:read and webmessaging:deployment:write. If you recently added scopes, generate a new token, as scopes are evaluated at token issuance time.

Error: 400 Bad Request - Invalid Position

Cause: The position field contains a value other than bottom-left, bottom-right, top-left, or top-right.
Fix: Validate the position string before sending the request. The API is strict about enum values.

Error: 429 Too Many Requests

Cause: You have exceeded the rate limit for the Web Messaging API.
Fix: Implement exponential backoff. The response headers Retry-After may indicate how long to wait.

import time

def retry_with_backoff(func, *args, max_retries=3, **kwargs):
    for attempt in range(max_retries):
        try:
            return func(*args, **kwargs)
        except requests.exceptions.HTTPError as e:
            if e.response.status_code == 429:
                wait_time = 2 ** attempt
                logger.warning(f"Rate limited. Waiting {wait_time} seconds...")
                time.sleep(wait_time)
            else:
                raise
    raise Exception("Max retries exceeded")

Error: UI Changes Not Visible Immediately

Cause: Browser caching or CDN propagation delay.
Fix: Clear your browser cache or open the web chat in an incognito window. Genesys Cloud caches the deployment configuration for the client-side widget. Changes may take up to 15 minutes to propagate globally, though usually, it is much faster.

Official References