How to configure the Web Messaging deployment to customize colors and launcher position

How to configure the Web Messaging deployment to customize colors and launcher position

What You Will Build

  • This tutorial demonstrates how to programmatically update a Genesys Cloud Web Messaging deployment to change the launcher button color, background color, and position on the page.
  • The solution utilizes the Genesys Cloud Platform API v2 endpoint PUT /api/v2/webdeployments/webmessaging/{webDeploymentId}.
  • The implementation is provided in Python using the official genesyscloud SDK and httpx for direct API calls to illustrate the underlying JSON structure.

Prerequisites

  • OAuth Client: A Genesys Cloud OAuth client with the following scopes:
    • webdeployments:webmessaging:write (Required to modify deployment configuration)
    • webdeployments:webmessaging:read (Required to fetch existing configuration)
  • SDK Version: genesyscloud Python SDK version 1.0.0 or higher.
  • Runtime: Python 3.8+.
  • External Dependencies:
    • genesyscloud: pip install genesyscloud
    • httpx: pip install httpx (Used for the raw HTTP example to demonstrate payload structure)
    • pydantic: Usually included with the SDK, used for data validation.

Authentication Setup

Genesys Cloud uses OAuth 2.0 for authentication. For server-side integrations, the Client Credentials flow is the standard approach. The following code initializes the SDK with your client credentials. Ensure you replace the placeholder values with your actual Genesys Cloud credentials.

import os
from genesyscloud.platform.client.configuration import Configuration
from genesyscloud.platform.client.api_client import ApiClient

def get_api_client() -> ApiClient:
    """
    Initializes and returns a configured Genesys Cloud API Client.
    
    Returns:
        ApiClient: The authenticated API client instance.
    """
    # Load credentials from environment variables
    client_id = os.getenv("GENESYS_CLIENT_ID")
    client_secret = os.getenv("GENESYS_CLIENT_SECRET")
    base_url = os.getenv("GENESYS_BASE_URL", "https://api.mypurecloud.com")
    
    if not client_id or not client_secret:
        raise ValueError("GENESYS_CLIENT_ID and GENESYS_CLIENT_SECRET must be set in environment variables.")

    # Configure the platform client
    configuration = Configuration(
        client_id=client_id,
        client_secret=client_secret,
        base_path=base_url
    )
    
    api_client = ApiClient(configuration=configuration)
    return api_client

The SDK handles token acquisition and refresh automatically. If the token expires during a long-running script, the ApiClient will attempt to refresh it using the stored client credentials.

Implementation

Step 1: Fetch the Existing Web Deployment Configuration

Before modifying the deployment, you must retrieve the current configuration. The Web Messaging deployment object contains nested structures for appearance, behavior, and integration settings. Modifying the entire object in one request ensures atomic updates and prevents partial state corruption.

Endpoint: GET /api/v2/webdeployments/webmessaging/{webDeploymentId}
Scope: webdeployments:webmessaging:read

from genesyscloud.platform.client.api.web_deployments_api import WebDeploymentsApi

def get_web_deployment(api_client: ApiClient, deployment_id: str) -> dict:
    """
    Fetches the full configuration of a specific Web Messaging deployment.
    
    Args:
        api_client: The authenticated Genesys Cloud API client.
        deployment_id: The unique identifier of the Web Messaging deployment.
        
    Returns:
        dict: The full deployment configuration object.
        
    Raises:
        Exception: If the deployment is not found or access is denied.
    """
    web_deployments_api = WebDeploymentsApi(api_client)
    
    try:
        # The SDK method returns a WebDeployment object
        response = web_deployments_api.get_web_deployments_web_messaging_web_deployment(
            web_deployment_id=deployment_id
        )
        
        # Convert the SDK model object to a dictionary for easier manipulation
        # The SDK models are Pydantic-based, so .dict() or model_dump() works
        if hasattr(response, 'model_dump'):
            config = response.model_dump(exclude_unset=True, by_alias=True)
        else:
            config = response.dict(exclude_unset=True, by_alias=True)
            
        return config
        
    except Exception as e:
        # Handle 404 Not Found or 401 Unauthorized
        if hasattr(e, 'status') and e.status == 404:
            raise ValueError(f"Web Deployment with ID {deployment_id} not found.")
        elif hasattr(e, 'status') and e.status == 401:
            raise PermissionError("Authentication failed. Check your OAuth scopes.")
        else:
            raise e

Step 2: Customize Appearance and Launcher Position

The WebDeployment object contains an appearance section. Within appearance, you will find launcher and widget sub-sections.

  • Launcher Position: Controlled by launcher.position. Valid values are bottom-right, bottom-left, top-right, top-left.
  • Launcher Colors: Controlled by launcher.backgroundColor and launcher.iconColor.
  • Widget Colors: Controlled by widget.backgroundColor, widget.headerColor, etc.

It is critical to preserve existing fields that you do not intend to change. Sending a partial update with PUT will overwrite the entire object. Therefore, we fetch the object, modify only the specific keys, and send the full object back.

def customize_deployment_appearance(config: dict, settings: dict) -> dict:
    """
    Updates the appearance settings in the deployment configuration dictionary.
    
    Args:
        config: The current deployment configuration dictionary.
        settings: A dictionary containing the new appearance settings.
                  Expected keys: 'launcher_position', 'launcher_bg_color', 
                                 'launcher_icon_color', 'widget_bg_color'.
                                 
    Returns:
        dict: The updated configuration dictionary.
    """
    # Ensure the appearance structure exists
    if 'appearance' not in config:
        config['appearance'] = {}
        
    appearance = config['appearance']
    
    # Update Launcher Settings
    if 'launcher' not in appearance:
        appearance['launcher'] = {}
        
    launcher = appearance['launcher']
    
    if 'launcher_position' in settings:
        valid_positions = ['bottom-right', 'bottom-left', 'top-right', 'top-left']
        if settings['launcher_position'] not in valid_positions:
            raise ValueError(f"Invalid position. Must be one of {valid_positions}")
        launcher['position'] = settings['launcher_position']
        
    if 'launcher_bg_color' in settings:
        launcher['backgroundColor'] = settings['launcher_bg_color']
        
    if 'launcher_icon_color' in settings:
        launcher['iconColor'] = settings['launcher_icon_color']
        
    # Update Widget Settings (Optional, but often desired for consistency)
    if 'widget' not in appearance:
        appearance['widget'] = {}
        
    widget = appearance['widget']
    
    if 'widget_bg_color' in settings:
        widget['backgroundColor'] = settings['widget_bg_color']
        
    if 'widget_header_color' in settings:
        widget['headerColor'] = settings['widget_header_color']
        
    # Assign the modified appearance back to config
    config['appearance'] = appearance
    
    return config

Step 3: Update the Deployment via API

Once the configuration dictionary is modified, you must send it back to Genesys Cloud using the PUT method. The SDK handles the serialization of the dictionary back into the WebDeployment model.

Endpoint: PUT /api/v2/webdeployments/webmessaging/{webDeploymentId}
Scope: webdeployments:webmessaging:write

def update_web_deployment(api_client: ApiClient, deployment_id: str, config: dict) -> dict:
    """
    Updates the Web Messaging deployment with the new configuration.
    
    Args:
        api_client: The authenticated Genesys Cloud API client.
        deployment_id: The unique identifier of the Web Messaging deployment.
        config: The updated deployment configuration dictionary.
        
    Returns:
        dict: The updated deployment configuration returned by the API.
        
    Raises:
        Exception: If the update fails due to validation errors or network issues.
    """
    web_deployments_api = WebDeploymentsApi(api_client)
    
    try:
        # The SDK expects a WebDeployment model object.
        # We can pass the dictionary directly to the constructor if the SDK supports it,
        # or we may need to manually map fields. In newer SDK versions, 
        # passing kwargs to the model constructor works.
        
        # Re-instantiate the model from the dict
        from genesyscloud.platform.client.model.web_deployment import WebDeployment
        
        # Note: Depending on SDK version, you might need to use from_dict()
        # or simply pass the dict if the API accepts a generic object.
        # The safest approach with modern Pydantic-based SDKs:
        new_deployment_model = WebDeployment.from_dict(config)
        
        response = web_deployments_api.put_web_deployments_web_messaging_web_deployment(
            web_deployment_id=deployment_id,
            body=new_deployment_model
        )
        
        # Return the response dict
        if hasattr(response, 'model_dump'):
            return response.model_dump(by_alias=True)
        else:
            return response.dict(by_alias=True)
            
    except Exception as e:
        # Handle 400 Bad Request (Validation Error)
        if hasattr(e, 'status') and e.status == 400:
            print(f"Validation Error: {e.body}")
            raise ValueError("Configuration validation failed. Check color formats and position values.")
        # Handle 429 Too Many Requests
        elif hasattr(e, 'status') and e.status == 429:
            raise Exception("Rate limit exceeded. Please wait and retry.")
        else:
            raise e

Complete Working Example

The following script combines all steps into a single executable module. It fetches the current deployment, applies custom colors and position, and updates the deployment.

import os
import sys
from genesyscloud.platform.client.configuration import Configuration
from genesyscloud.platform.client.api_client import ApiClient
from genesyscloud.platform.client.api.web_deployments_api import WebDeploymentsApi
from genesyscloud.platform.client.model.web_deployment import WebDeployment

# --- Configuration ---
# Replace these with your actual values
DEPLOYMENT_ID = "your-web-deployment-id-here"
GENESYS_CLIENT_ID = os.getenv("GENESYS_CLIENT_ID")
GENESYS_CLIENT_SECRET = os.getenv("GENESYS_CLIENT_SECRET")
BASE_URL = os.getenv("GENESYS_BASE_URL", "https://api.mypurecloud.com")

# Customization Settings
CUSTOM_SETTINGS = {
    "launcher_position": "bottom-left",      # Move launcher to bottom left
    "launcher_bg_color": "#FF5733",          # Reddish-orange background
    "launcher_icon_color": "#FFFFFF",        # White icon
    "widget_bg_color": "#FFFFFF",            # White widget background
    "widget_header_color": "#FF5733"         # Matching header color
}

def main():
    if not GENESYS_CLIENT_ID or not GENESYS_CLIENT_SECRET:
        print("Error: GENESYS_CLIENT_ID and GENESYS_CLIENT_SECRET must be set in environment variables.")
        sys.exit(1)

    try:
        # 1. Initialize API Client
        configuration = Configuration(
            client_id=GENESYS_CLIENT_ID,
            client_secret=GENESYS_CLIENT_SECRET,
            base_path=BASE_URL
        )
        api_client = ApiClient(configuration=configuration)
        
        # 2. Fetch Current Configuration
        print(f"Fetching configuration for deployment: {DEPLOYMENT_ID}...")
        web_deployments_api = WebDeploymentsApi(api_client)
        
        response = web_deployments_api.get_web_deployments_web_messaging_web_deployment(
            web_deployment_id=DEPLOYMENT_ID
        )
        
        # Convert to dict
        if hasattr(response, 'model_dump'):
            config = response.model_dump(exclude_unset=True, by_alias=True)
        else:
            config = response.dict(exclude_unset=True, by_alias=True)
            
        print("Current configuration fetched successfully.")

        # 3. Apply Customizations
        print("Applying custom appearance settings...")
        
        # Ensure nested structures exist
        if 'appearance' not in config:
            config['appearance'] = {}
        if 'launcher' not in config['appearance']:
            config['appearance']['launcher'] = {}
        if 'widget' not in config['appearance']:
            config['appearance']['widget'] = {}
            
        # Update Launcher
        launcher = config['appearance']['launcher']
        launcher['position'] = CUSTOM_SETTINGS['launcher_position']
        launcher['backgroundColor'] = CUSTOM_SETTINGS['launcher_bg_color']
        launcher['iconColor'] = CUSTOM_SETTINGS['launcher_icon_color']
        
        # Update Widget
        widget = config['appearance']['widget']
        widget['backgroundColor'] = CUSTOM_SETTINGS['widget_bg_color']
        widget['headerColor'] = CUSTOM_SETTINGS['widget_header_color']
        
        # 4. Update Deployment
        print("Updating deployment...")
        
        # Re-create model from dict
        new_deployment_model = WebDeployment.from_dict(config)
        
        updated_response = web_deployments_api.put_web_deployments_web_messaging_web_deployment(
            web_deployment_id=DEPLOYMENT_ID,
            body=new_deployment_model
        )
        
        print("Deployment updated successfully!")
        print(f"New Launcher Position: {launcher['position']}")
        print(f"New Launcher Background: {launcher['backgroundColor']}")
        
    except Exception as e:
        print(f"An error occurred: {e}")
        if hasattr(e, 'status'):
            print(f"HTTP Status: {e.status}")
            if hasattr(e, 'body'):
                print(f"Response Body: {e.body}")
        sys.exit(1)

if __name__ == "__main__":
    main()

Common Errors & Debugging

Error: 400 Bad Request - Validation Error

What causes it: The most common cause is an invalid color format or an unsupported position value. Genesys Cloud expects CSS-compatible hex codes (e.g., #FF0000) or named colors. The position must be one of the four predefined corners.

How to fix it:

  1. Verify that all color values start with # and are 6-character hex strings.
  2. Verify that position is exactly bottom-right, bottom-left, top-right, or top-left.
  3. Check the response body for specific field validation errors.

Code Fix:

# Ensure color format is correct
def validate_hex_color(color: str) -> bool:
    import re
    return bool(re.match(r'^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$', color))

if not validate_hex_color(CUSTOM_SETTINGS['launcher_bg_color']):
    raise ValueError("Invalid hex color format for launcher background.")

Error: 403 Forbidden

What causes it: The OAuth client used for authentication lacks the webdeployments:webmessaging:write scope.

How to fix it:

  1. Navigate to the Genesys Cloud Admin portal.
  2. Go to Admin > Security > OAuth Clients.
  3. Select your client.
  4. Add the scope webdeployments:webmessaging:write to the client’s scope list.
  5. Save the changes. Note that existing tokens may need to be refreshed to pick up new scopes.

Error: 429 Too Many Requests

What causes it: Genesys Cloud APIs enforce rate limits. If you are updating multiple deployments in a loop, you may hit the limit.

How to fix it: Implement exponential backoff.

Code Fix:

import time

def update_with_retry(api_client, deployment_id, config, max_retries=3):
    web_deployments_api = WebDeploymentsApi(api_client)
    for attempt in range(max_retries):
        try:
            new_deployment_model = WebDeployment.from_dict(config)
            return web_deployments_api.put_web_deployments_web_messaging_web_deployment(
                web_deployment_id=deployment_id,
                body=new_deployment_model
            )
        except Exception as e:
            if hasattr(e, 'status') and e.status == 429:
                wait_time = 2 ** attempt
                print(f"Rate limit hit. Waiting {wait_time} seconds...")
                time.sleep(wait_time)
            else:
                raise e
    raise Exception("Max retries exceeded.")

Official References