How to configure the Web Messaging deployment to customize colors and launcher position
What You Will Build
- You will build a script that retrieves an existing Web Messaging deployment, modifies its visual theme (launcher button color, background color) and layout position, and saves the changes back to Genesys Cloud.
- This tutorial uses the Genesys Cloud Pure Cloud Platform API v2 (
/api/v2/webchat/deployments) and the Python SDK. - The implementation is written in Python 3.9+ using
httpxfor HTTP requests and the officialgenesyscloudSDK for object mapping.
Prerequisites
- OAuth Client Type: A Genesys Cloud OAuth client with
privateorpublictype. - Required Scopes:
webchat:deployment:read(to retrieve the deployment)webchat:deployment:write(to update the deployment)webchat:deployment:theme:readandwebchat:deployment:theme:write(optional, depending on theme complexity, but usually covered by deployment write)
- SDK Version:
genesyscloudPython SDK v2.20.0 or later. - Runtime: Python 3.9+
- Dependencies:
pip install genesyscloud httpx
Authentication Setup
Genesys Cloud uses OAuth 2.0 for authentication. For server-to-server integrations (like this configuration script), the Client Credentials Grant flow is the standard approach. This flow requires your client ID, client secret, and the environment URL.
The following function handles the token acquisition. In a production environment, you should cache this token and implement refresh logic, as tokens expire after 3600 seconds (1 hour). For this tutorial, we request a new token each run to ensure simplicity and reliability.
import httpx
import json
from typing import Optional
class GenesysAuth:
def __init__(self, environment: str, client_id: str, client_secret: str):
self.environment = environment
self.client_id = client_id
self.client_secret = client_secret
self.token_url = f"https://{environment}.mypurecloud.com/oauth/token"
self.access_token: Optional[str] = None
self.expires_at: Optional[float] = None
def get_access_token(self) -> str:
"""
Retrieves an OAuth2 access token using Client Credentials Grant.
"""
# In production, check if self.access_token is valid and not expired
# before making a new request.
headers = {
"Content-Type": "application/x-www-form-urlencoded"
}
data = {
"grant_type": "client_credentials",
"client_id": self.client_id,
"client_secret": self.client_secret,
"scope": "webchat:deployment:read webchat:deployment:write"
}
try:
with httpx.Client() as client:
response = client.post(
self.token_url,
headers=headers,
data=data,
timeout=10.0
)
response.raise_for_status()
token_data = response.json()
self.access_token = token_data["access_token"]
return self.access_token
except httpx.HTTPStatusError as e:
raise Exception(f"Authentication failed: {e.response.status_code} - {e.response.text}")
except Exception as e:
raise Exception(f"Unexpected error during authentication: {str(e)}")
Implementation
Step 1: Retrieve the Existing Deployment Configuration
Before modifying colors or positions, you must retrieve the current state of the Web Messaging deployment. The deployment ID is required for all subsequent operations. You can find this ID in the Admin UI under Channels > Web Messaging, or by listing all deployments via the API.
The endpoint GET /api/v2/webchat/deployments/{deploymentId} returns the full deployment object, including the theme and launcher configurations.
import httpx
import os
class WebMessagingConfigurator:
def __init__(self, auth: GenesysAuth, environment: str):
self.auth = auth
self.base_url = f"https://{environment}.mypurecloud.com/api/v2"
self.headers = {
"Authorization": f"Bearer {auth.get_access_token()}",
"Content-Type": "application/json",
"Accept": "application/json"
}
def get_deployment(self, deployment_id: str) -> dict:
"""
Retrieves the full configuration of a specific Web Messaging deployment.
"""
endpoint = f"{self.base_url}/webchat/deployments/{deployment_id}"
try:
with httpx.Client() as client:
response = client.get(endpoint, headers=self.headers, timeout=10.0)
if response.status_code == 401:
# Token may have expired; refresh and retry once
self.headers["Authorization"] = f"Bearer {self.auth.get_access_token()}"
response = client.get(endpoint, headers=self.headers, timeout=10.0)
response.raise_for_status()
return response.json()
except httpx.HTTPStatusError as e:
if e.response.status_code == 404:
raise Exception(f"Deployment with ID {deployment_id} not found.")
elif e.response.status_code == 429:
raise Exception("Rate limit exceeded. Please wait and retry.")
else:
raise Exception(f"HTTP Error: {e.response.status_code} - {e.response.text}")
except Exception as e:
raise Exception(f"Failed to fetch deployment: {str(e)}")
Step 2: Modify Theme Colors and Launcher Position
The Web Messaging deployment object contains two critical sections for visual customization:
theme: Controls colors, fonts, and bubble styling.launcher: Controls the position, icon, and behavior of the chat entry point.
Understanding the Theme Structure
The theme object is nested. Key properties for color customization include:
theme.bubble.primaryColor: The main background color of the chat window header and buttons.theme.bubble.secondaryColor: Accent colors for hover states or secondary actions.theme.text.primaryColor: Text color inside the chat window.theme.icon.primaryColor: Color of the launcher icon.
Understanding the Launcher Position
The launcher object contains:
launcher.position: A string value, typically"bottom-right"or"bottom-left".launcher.type: Usually"button"or"icon".launcher.offsetXandlauncher.offsetY: Pixel offsets from the edge of the screen.
In this step, we define a function that takes the retrieved deployment JSON and mutates it with new values.
def customize_deployment_theme(
deployment: dict,
primary_color: str,
launcher_position: str = "bottom-right",
offset_x: int = 20,
offset_y: int = 20
) -> dict:
"""
Mutates the deployment dictionary to apply custom colors and position.
Args:
deployment: The JSON object retrieved from the API.
primary_color: Hex code for the primary brand color (e.g., "#007BFF").
launcher_position: "bottom-right" or "bottom-left".
offset_x: Horizontal distance from the edge in pixels.
offset_y: Vertical distance from the edge in pixels.
Returns:
The modified deployment dictionary.
"""
# Ensure theme structure exists
if "theme" not in deployment:
deployment["theme"] = {}
# Ensure bubble theme exists
if "bubble" not in deployment["theme"]:
deployment["theme"]["bubble"] = {}
# Apply Colors
# Note: Genesys Cloud expects hex strings with '#' prefix
deployment["theme"]["bubble"]["primaryColor"] = primary_color
# Optional: Set a contrasting secondary color if not already defined
if "secondaryColor" not in deployment["theme"]["bubble"]:
# Simple logic to invert brightness slightly for contrast if needed
# In production, you might pass this as a parameter
deployment["theme"]["bubble"]["secondaryColor"] = "#FFFFFF"
# Apply Launcher Position
if "launcher" not in deployment:
deployment["launcher"] = {}
deployment["launcher"]["position"] = launcher_position
deployment["launcher"]["offsetX"] = offset_x
deployment["launcher"]["offsetY"] = offset_y
# Optional: Ensure the launcher icon color contrasts with the background
# If the primary color is dark, set icon to white, otherwise black
# This is a simple heuristic; robust apps should calculate luminance
if primary_color.startswith("#"):
hex_color = primary_color.lstrip("#")
r, g, b = int(hex_color[0:2], 16), int(hex_color[2:4], 16), int(hex_color[4:6], 16)
luminance = 0.299 * r + 0.587 * g + 0.114 * b
icon_color = "#FFFFFF" if luminance < 128 else "#000000"
if "icon" not in deployment["theme"]:
deployment["theme"]["icon"] = {}
deployment["theme"]["icon"]["primaryColor"] = icon_color
return deployment
Step 3: Save the Updated Deployment
Once the JSON object is modified, you must send it back to Genesys Cloud using a PUT request. The PUT method replaces the entire deployment configuration. This means you must send the full object returned from Step 1, not just the changed fields.
Critical Note: The id field must be preserved. The etag field is optional for PUT but recommended for concurrency control. If you omit etag, Genesys Cloud will overwrite any concurrent changes made by an admin in the UI.
def update_deployment(self, deployment_id: str, deployment_config: dict) -> dict:
"""
Updates the Web Messaging deployment with the new configuration.
"""
endpoint = f"{self.base_url}/webchat/deployments/{deployment_id}"
# Remove etag if present to force overwrite, or keep it for optimistic locking
# For this tutorial, we force overwrite to ensure the script works
if "etag" in deployment_config:
del deployment_config["etag"]
try:
with httpx.Client() as client:
response = client.put(
endpoint,
headers=self.headers,
json=deployment_config,
timeout=10.0
)
if response.status_code == 401:
self.headers["Authorization"] = f"Bearer {self.auth.get_access_token()}"
response = client.put(
endpoint,
headers=self.headers,
json=deployment_config,
timeout=10.0
)
response.raise_for_status()
return response.json()
except httpx.HTTPStatusError as e:
if e.response.status_code == 400:
raise Exception(f"Bad Request: Validation failed. Check the JSON payload structure. {e.response.text}")
elif e.response.status_code == 403:
raise Exception("Forbidden: Your OAuth client lacks webchat:deployment:write scope.")
elif e.response.status_code == 429:
raise Exception("Rate limit exceeded. Please implement exponential backoff.")
else:
raise Exception(f"HTTP Error: {e.response.status_code} - {e.response.text}")
except Exception as e:
raise Exception(f"Failed to update deployment: {str(e)}")
Complete Working Example
The following script combines all components. It authenticates, retrieves a deployment by ID, customizes the primary color to a specific brand blue (#1E90FF) and moves the launcher to the bottom-left corner, then saves the changes.
import httpx
import json
import sys
from typing import Optional
# --- Authentication Class ---
class GenesysAuth:
def __init__(self, environment: str, client_id: str, client_secret: str):
self.environment = environment
self.client_id = client_id
self.client_secret = client_secret
self.token_url = f"https://{environment}.mypurecloud.com/oauth/token"
self.access_token: Optional[str] = None
def get_access_token(self) -> str:
headers = {"Content-Type": "application/x-www-form-urlencoded"}
data = {
"grant_type": "client_credentials",
"client_id": self.client_id,
"client_secret": self.client_secret,
"scope": "webchat:deployment:read webchat:deployment:write"
}
try:
with httpx.Client() as client:
response = client.post(self.token_url, headers=headers, data=data, timeout=10.0)
response.raise_for_status()
self.access_token = response.json()["access_token"]
return self.access_token
except httpx.HTTPStatusError as e:
raise Exception(f"Authentication failed: {e.response.status_code} - {e.response.text}")
# --- Configuration Class ---
class WebMessagingConfigurator:
def __init__(self, auth: GenesysAuth, environment: str):
self.auth = auth
self.base_url = f"https://{environment}.mypurecloud.com/api/v2"
self.headers = {
"Authorization": f"Bearer {auth.get_access_token()}",
"Content-Type": "application/json",
"Accept": "application/json"
}
def get_deployment(self, deployment_id: str) -> dict:
endpoint = f"{self.base_url}/webchat/deployments/{deployment_id}"
try:
with httpx.Client() as client:
response = client.get(endpoint, headers=self.headers, timeout=10.0)
if response.status_code == 401:
self.headers["Authorization"] = f"Bearer {self.auth.get_access_token()}"
response = client.get(endpoint, headers=self.headers, timeout=10.0)
response.raise_for_status()
return response.json()
except httpx.HTTPStatusError as e:
raise Exception(f"Fetch failed: {e.response.status_code} - {e.response.text}")
def update_deployment(self, deployment_id: str, deployment_config: dict) -> dict:
endpoint = f"{self.base_url}/webchat/deployments/{deployment_id}"
if "etag" in deployment_config:
del deployment_config["etag"]
try:
with httpx.Client() as client:
response = client.put(endpoint, headers=self.headers, json=deployment_config, timeout=10.0)
if response.status_code == 401:
self.headers["Authorization"] = f"Bearer {self.auth.get_access_token()}"
response = client.put(endpoint, headers=self.headers, json=deployment_config, timeout=10.0)
response.raise_for_status()
return response.json()
except httpx.HTTPStatusError as e:
raise Exception(f"Update failed: {e.response.status_code} - {e.response.text}")
def customize_deployment_theme(deployment: dict, primary_color: str, launcher_position: str) -> dict:
if "theme" not in deployment:
deployment["theme"] = {}
if "bubble" not in deployment["theme"]:
deployment["theme"]["bubble"] = {}
deployment["theme"]["bubble"]["primaryColor"] = primary_color
if "launcher" not in deployment:
deployment["launcher"] = {}
deployment["launcher"]["position"] = launcher_position
# Set icon color based on primary color luminance
if primary_color.startswith("#"):
hex_color = primary_color.lstrip("#")
r, g, b = int(hex_color[0:2], 16), int(hex_color[2:4], 16), int(hex_color[4:6], 16)
luminance = 0.299 * r + 0.587 * g + 0.114 * b
icon_color = "#FFFFFF" if luminance < 128 else "#000000"
if "icon" not in deployment["theme"]:
deployment["theme"]["icon"] = {}
deployment["theme"]["icon"]["primaryColor"] = icon_color
return deployment
def main():
# Configuration
ENVIRONMENT = "us-east-1" # Change to your environment (e.g., eu-west-1, au)
CLIENT_ID = os.getenv("GENESYS_CLIENT_ID")
CLIENT_SECRET = os.getenv("GENESYS_CLIENT_SECRET")
DEPLOYMENT_ID = os.getenv("GENESYS_DEPLOYMENT_ID")
if not all([CLIENT_ID, CLIENT_SECRET, DEPLOYMENT_ID]):
print("Error: Missing environment variables. Set GENESYS_CLIENT_ID, GENESYS_CLIENT_SECRET, and GENESYS_DEPLOYMENT_ID.")
sys.exit(1)
try:
# 1. Authenticate
print("Authenticating...")
auth = GenesysAuth(ENVIRONMENT, CLIENT_ID, CLIENT_SECRET)
token = auth.get_access_token()
print("Authentication successful.")
# 2. Initialize Configurator
configurator = WebMessagingConfigurator(auth, ENVIRONMENT)
# 3. Retrieve Deployment
print(f"Retrieving deployment {DEPLOYMENT_ID}...")
deployment = configurator.get_deployment(DEPLOYMENT_ID)
print("Deployment retrieved.")
# 4. Customize
# Set primary color to Dodger Blue and position to bottom-left
NEW_PRIMARY_COLOR = "#1E90FF"
NEW_POSITION = "bottom-left"
print(f"Applying customizations: Color={NEW_PRIMARY_COLOR}, Position={NEW_POSITION}")
modified_deployment = customize_deployment_theme(deployment, NEW_PRIMARY_COLOR, NEW_POSITION)
# 5. Update Deployment
print("Saving changes to Genesys Cloud...")
updated_deployment = configurator.update_deployment(DEPLOYMENT_ID, modified_deployment)
print("Success! Deployment updated.")
print(f"New Primary Color: {updated_deployment['theme']['bubble']['primaryColor']}")
print(f"New Launcher Position: {updated_deployment['launcher']['position']}")
except Exception as e:
print(f"Error: {str(e)}")
sys.exit(1)
if __name__ == "__main__":
import os
main()
Common Errors & Debugging
Error: 400 Bad Request - Validation Failed
Cause: The JSON payload sent to the PUT endpoint contains invalid structure or missing required fields. Web Messaging deployments have a complex nested schema. If you manually constructed the JSON instead of retrieving and modifying it, you likely missed a required field like deploymentId or routingType.
Fix: Always retrieve the existing deployment using GET, modify the dictionary in memory, and send the full object back. Do not strip out fields you do not intend to change.
Error: 403 Forbidden - Scope Denied
Cause: The OAuth client used for authentication does not have the webchat:deployment:write scope.
Fix: Go to Admin > Security > OAuth Clients, select your client, and ensure webchat:deployment:write is checked. Save the client and generate a new token.
Error: 409 Conflict - ETag Mismatch
Cause: You included the etag from the GET response in your PUT request, but another admin modified the deployment in the UI between your GET and PUT calls. Genesys Cloud uses ETags for optimistic locking.
Fix: For automated configuration scripts, it is often safer to delete the etag field from the payload before sending the PUT request, as shown in the update_deployment method above. This forces the server to accept your version as the latest. If you require strict concurrency control, catch the 409 error, re-fetch the deployment, re-apply your changes, and retry.
Error: 429 Too Many Requests
Cause: You exceeded the Genesys Cloud API rate limits. This is common if you are looping through many deployments.
Fix: Implement exponential backoff. Wait 1 second, then 2, then 4, etc., before retrying the request. The Retry-After header in the response may indicate how long to wait.