Sending Proactive Notifications to a Customer with Prior Web Messaging History
What You Will Build
- A Python script that retrieves a customer’s previous web messaging conversation history to determine context.
- A mechanism to send a proactive outbound web message to a specific user session using Genesys Cloud CX.
- Implementation of the Genesys Cloud CX Web Messaging SDK and REST API to bridge historical data with real-time engagement.
Prerequisites
- OAuth Client Type: Confidential Client (Client Credentials Flow) for backend API calls.
- Required Scopes:
conversation:webchat:read,conversation:webchat:write,user:read. - SDK Version:
genesys-cloud-pythonv4.0.0+. - Runtime: Python 3.9+.
- External Dependencies:
genesys-cloud-python,requests,uuid. - Genesys Cloud Setup: A configured Web Messaging integration with a valid
siteNameandorgId.
Authentication Setup
Genesys Cloud CX uses OAuth 2.0 Client Credentials Grant for server-to-server communication. You must obtain an access token before making any API calls. The token expires after 3600 seconds, so production code should implement a refresh mechanism.
import requests
import os
import time
class GenesysAuth:
def __init__(self, client_id: str, client_secret: str, env_url: str):
self.client_id = client_id
self.client_secret = client_secret
self.env_url = env_url # e.g., "https://api.mypurecloud.com"
self.token = None
self.token_expiry = 0
def get_token(self) -> str:
# Check if token is still valid
if self.token and time.time() < self.token_expiry - 60:
return self.token
# Request new token
url = f"{self.env_url}/oauth/token"
headers = {"Content-Type": "application/x-www-form-urlencoded"}
data = {
"grant_type": "client_credentials",
"client_id": self.client_id,
"client_secret": self.client_secret
}
response = requests.post(url, headers=headers, data=data)
response.raise_for_status()
token_data = response.json()
self.token = token_data["access_token"]
self.token_expiry = time.time() + token_data["expires_in"]
return self.token
Implementation
Step 1: Identifying the Target Customer Session
To send a proactive notification, you must target an active web messaging session. Genesys Cloud does not allow sending web messages to arbitrary email addresses or phone numbers directly via the API in the same way as SMS. You must target a specific userId associated with an active session on the web widget.
First, you need to identify the active sessions. We will query the conversations API to find active web chat conversations. This allows you to correlate a customer’s historical data with their current live session.
Endpoint: GET /api/v2/conversations/webchat
Scope: conversation:webchat:read
import requests
from typing import List, Dict, Any
class WebChatManager:
def __init__(self, auth: GenesysAuth):
self.auth = auth
self.base_url = auth.env_url
def get_active_webchat_sessions(self) -> List[Dict[str, Any]]:
"""
Retrieves all active web chat conversations.
In a production scenario, you would filter this by specific
participant attributes or external IDs.
"""
url = f"{self.base_url}/api/v2/conversations/webchat"
headers = {
"Authorization": f"Bearer {self.auth.get_token()}",
"Accept": "application/json"
}
params = {
"pageSize": 25,
"page": 1
}
response = requests.get(url, headers=headers, params=params)
response.raise_for_status()
return response.json().get("entities", [])
Step 2: Retrieving Historical Context
Before sending the proactive message, it is best practice to verify the customer’s intent or history. We will fetch the previous conversation details for the identified session. This ensures the proactive message is relevant.
Endpoint: GET /api/v2/conversations/{conversationId}
Scope: conversation:read
def get_conversation_history(self, conversation_id: str) -> Dict[str, Any]:
"""
Fetches detailed history for a specific conversation ID.
"""
url = f"{self.base_url}/api/v2/conversations/{conversation_id}"
headers = {
"Authorization": f"Bearer {self.auth.get_token()}",
"Accept": "application/json"
}
try:
response = requests.get(url, headers=headers)
response.raise_for_status()
return response.json()
except requests.exceptions.HTTPError as e:
if e.response.status_code == 404:
print(f"Conversation {conversation_id} not found.")
return {}
raise
Step 3: Sending the Proactive Message
The core action is sending a message to an active session. Genesys Cloud supports sending messages as a “System” or as a specific “User” (Agent/Bot). For proactive notifications, you typically send as a Bot or a specific Agent ID.
To send a message, you use the POST /api/v2/conversations/{conversationId}/messages endpoint. The body must specify the to participant (the customer) and the from participant (the sender).
Endpoint: POST /api/v2/conversations/{conversationId}/messages
Scope: conversation:webchat:write
def send_proactive_message(
self,
conversation_id: str,
customer_user_id: str,
sender_user_id: str,
message_text: str
) -> Dict[str, Any]:
"""
Sends a proactive text message to an active web chat session.
Args:
conversation_id: The ID of the active web chat conversation.
customer_user_id: The userId of the customer in the session.
sender_user_id: The userId of the bot/agent sending the message.
message_text: The content of the proactive message.
"""
url = f"{self.base_url}/api/v2/conversations/{conversation_id}/messages"
headers = {
"Authorization": f"Bearer {self.auth.get_token()}",
"Content-Type": "application/json",
"Accept": "application/json"
}
# Construct the message payload
payload = {
"to": {
"id": customer_user_id,
"name": "Customer" # Optional but recommended
},
"from": {
"id": sender_user_id,
"name": "ProactiveBot" # Name of the sender
},
"text": message_text,
"type": "text"
}
try:
response = requests.post(url, headers=headers, json=payload)
# Handle specific error codes
if response.status_code == 409:
# Conflict: Conversation might be closed or invalid state
print(f"Conflict sending message to {conversation_id}: {response.text}")
return {"error": "Conflict", "details": response.text}
elif response.status_code == 404:
# Not Found: Conversation or user ID invalid
print(f"Entity not found: {response.text}")
return {"error": "Not Found", "details": response.text}
elif response.status_code == 429:
# Rate Limited
print("Rate limited. Please retry after delay.")
return {"error": "Rate Limited"}
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
print(f"Error sending message: {e}")
return {"error": str(e)}
Step 4: Orchestrating the Logic
We combine the steps to create a workflow that finds an active session, checks if it belongs to a high-value customer (simulated here), and sends a proactive offer.
def main():
# Configuration
CLIENT_ID = os.getenv("GENESYS_CLIENT_ID")
CLIENT_SECRET = os.getenv("GENESYS_CLIENT_SECRET")
ENV_URL = os.getenv("GENESYS_ENV_URL", "https://api.mypurecloud.com")
SENDER_BOT_ID = os.getenv("GENESYS_BOT_USER_ID") # ID of the bot user in Genesys
if not all([CLIENT_ID, CLIENT_SECRET, SENDER_BOT_ID]):
raise ValueError("Missing environment variables for Genesys Cloud.")
# Initialize Auth and Manager
auth = GenesysAuth(CLIENT_ID, CLIENT_SECRET, ENV_URL)
manager = WebChatManager(auth)
print("Fetching active web chat sessions...")
active_sessions = manager.get_active_webchat_sessions()
if not active_sessions:
print("No active web chat sessions found.")
return
print(f"Found {len(active_sessions)} active session(s).")
# Iterate through sessions to find a target
# In production, you would filter by custom attributes or external ID
for session in active_sessions:
conversation_id = session["id"]
# Identify participants
participants = session.get("participants", [])
customer_user_id = None
customer_name = ""
for participant in participants:
if participant["role"] == "customer":
customer_user_id = participant["userId"]
customer_name = participant.get("name", "Unknown Customer")
break
if not customer_user_id:
continue
# Simulate business logic: Send proactive message to first active customer
print(f"Targeting customer: {customer_name} in conversation {conversation_id}")
# Retrieve history to ensure context (optional but recommended)
history = manager.get_conversation_history(conversation_id)
if history:
print(f"Loaded history for conversation. Duration: {history.get('wrapUpCode', 'N/A')}")
# Define proactive message content
message_content = "Hi! We noticed you were browsing our premium plans. Would you like to speak with a specialist for a personalized discount?"
# Send the message
result = manager.send_proactive_message(
conversation_id=conversation_id,
customer_user_id=customer_user_id,
sender_user_id=SENDER_BOT_ID,
message_text=message_content
)
if "error" not in result:
print(f"Successfully sent proactive message to {customer_name}.")
else:
print(f"Failed to send message: {result}")
# Break after first success for demo purposes
break
if __name__ == "__main__":
main()
Complete Working Example
Below is the consolidated, production-ready Python script. It includes error handling, token management, and the full logic flow. Save this as proactive_webchat.py.
import os
import time
import requests
from typing import List, Dict, Any, Optional
class GenesysAuth:
"""Handles OAuth2 Client Credentials Flow for Genesys Cloud."""
def __init__(self, client_id: str, client_secret: str, env_url: str):
self.client_id = client_id
self.client_secret = client_secret
self.env_url = env_url
self.token = None
self.token_expiry = 0
def get_token(self) -> str:
if self.token and time.time() < self.token_expiry - 60:
return self.token
url = f"{self.env_url}/oauth/token"
headers = {"Content-Type": "application/x-www-form-urlencoded"}
data = {
"grant_type": "client_credentials",
"client_id": self.client_id,
"client_secret": self.client_secret
}
try:
response = requests.post(url, headers=headers, data=data, timeout=10)
response.raise_for_status()
token_data = response.json()
self.token = token_data["access_token"]
self.token_expiry = time.time() + token_data["expires_in"]
return self.token
except requests.exceptions.RequestException as e:
raise Exception(f"Failed to obtain OAuth token: {e}")
class GenesysWebChatService:
"""Service class for interacting with Genesys Cloud Web Chat APIs."""
def __init__(self, auth: GenesysAuth):
self.auth = auth
self.base_url = auth.env_url
def _get_headers(self) -> Dict[str, str]:
return {
"Authorization": f"Bearer {self.auth.get_token()}",
"Accept": "application/json",
"Content-Type": "application/json"
}
def get_active_conversations(self, page: int = 1, page_size: int = 25) -> List[Dict[str, Any]]:
"""
Retrieves active web chat conversations.
Scope: conversation:webchat:read
"""
url = f"{self.base_url}/api/v2/conversations/webchat"
params = {"page": page, "pageSize": page_size}
try:
response = requests.get(url, headers=self._get_headers(), params=params, timeout=10)
response.raise_for_status()
return response.json().get("entities", [])
except requests.exceptions.HTTPError as e:
if e.response.status_code == 401:
print("Authentication failed. Check Client ID/Secret.")
elif e.response.status_code == 403:
print("Forbidden. Check OAuth scopes (conversation:webchat:read).")
raise
def send_proactive_message(
self,
conversation_id: str,
customer_user_id: str,
sender_user_id: str,
text: str
) -> Dict[str, Any]:
"""
Sends a proactive message to an active web chat session.
Scope: conversation:webchat:write
"""
url = f"{self.base_url}/api/v2/conversations/{conversation_id}/messages"
payload = {
"to": {"id": customer_user_id},
"from": {"id": sender_user_id},
"text": text,
"type": "text"
}
try:
response = requests.post(url, headers=self._get_headers(), json=payload, timeout=10)
if response.status_code == 200 or response.status_code == 201:
return {"success": True, "data": response.json()}
elif response.status_code == 409:
return {"success": False, "error": "Conflict", "message": "Conversation may be closed or invalid state."}
elif response.status_code == 404:
return {"success": False, "error": "Not Found", "message": "Conversation or User ID not found."}
elif response.status_code == 429:
return {"success": False, "error": "Rate Limited", "message": "Too many requests. Wait and retry."}
else:
response.raise_for_status()
except requests.exceptions.RequestException as e:
return {"success": False, "error": "Request Exception", "message": str(e)}
def main():
# Load Configuration from Environment Variables
client_id = os.getenv("GENESYS_CLIENT_ID")
client_secret = os.getenv("GENESYS_CLIENT_SECRET")
env_url = os.getenv("GENESYS_ENV_URL", "https://api.mypurecloud.com")
bot_user_id = os.getenv("GENESYS_BOT_USER_ID")
if not all([client_id, client_secret, bot_user_id]):
raise EnvironmentError("Missing required environment variables: GENESYS_CLIENT_ID, GENESYS_CLIENT_SECRET, GENESYS_BOT_USER_ID")
# Initialize Services
auth = GenesysAuth(client_id, client_secret, env_url)
webchat_service = GenesysWebChatService(auth)
try:
# Step 1: Get Active Sessions
print("Retrieving active web chat sessions...")
conversations = webchat_service.get_active_conversations()
if not conversations:
print("No active web chat sessions found.")
return
# Step 2: Process Each Session
for conv in conversations:
conv_id = conv["id"]
participants = conv.get("participants", [])
customer_id = None
for p in participants:
if p.get("role") == "customer":
customer_id = p.get("userId")
break
if not customer_id:
continue
# Step 3: Send Proactive Message
# In production, apply business logic here to decide IF to message
message_text = "Hello! We see you are online. How can we assist you with your recent inquiry?"
print(f"Sending proactive message to conversation {conv_id}...")
result = webchat_service.send_proactive_message(
conversation_id=conv_id,
customer_user_id=customer_id,
sender_user_id=bot_user_id,
text=message_text
)
if result["success"]:
print(f"Message sent successfully to {customer_id}.")
else:
print(f"Failed to send message: {result['error']} - {result.get('message')}")
# Limit to one message for this demo
break
except Exception as e:
print(f"Fatal error: {e}")
if __name__ == "__main__":
main()
Common Errors & Debugging
Error: 401 Unauthorized
- Cause: The OAuth token is expired, invalid, or the Client ID/Secret is incorrect.
- Fix: Ensure the
GENESYS_CLIENT_IDandGENESYS_CLIENT_SECRETenvironment variables are set correctly. Verify that the OAuth client in Genesys Cloud is enabled and has the correct grant type (Client Credentials).
Error: 403 Forbidden
- Cause: The OAuth client lacks the required scopes.
- Fix: Go to Genesys Cloud Admin > Security > OAuth Clients. Edit your client and ensure
conversation:webchat:readandconversation:webchat:writeare added to the scopes. Re-authenticate after adding scopes.
Error: 409 Conflict
- Cause: The conversation is no longer active, or the state is inconsistent.
- Fix: Verify that the conversation ID returned from the
GET /api/v2/conversations/webchatendpoint is still active. Web chat sessions can close quickly if the user navigates away. Implement a retry mechanism with a small delay if this occurs frequently.
Error: 404 Not Found
- Cause: The
conversationIdoruserIddoes not exist. - Fix: Ensure you are using the
userIdfrom theparticipantsarray of the active conversation, not theconversationIditself. Thetofield in the message payload must match the exactuserIdof the customer participant.