Sending Proactive Notifications to a Customer with a History of Web Messaging
What You Will Build
- You will build a Python script that identifies a specific customer by their external ID or phone number and sends them a proactive web message notification.
- This solution uses the Genesys Cloud CX API v2 endpoints for User Search, Interaction Management, and Webchat Proactive Messaging.
- The tutorial covers Python using the
requestslibrary and the officialgenesyscloudSDK.
Prerequisites
- OAuth Client: A Genesys Cloud OAuth client with the
webchat:proactive:sendandinteraction:readscopes. - SDK Version:
genesys-cloud-purecloud-platform-clientv130.0.0 or later. - Runtime: Python 3.8+.
- Dependencies:
pip install requests genesys-cloud-purecloud-platform-client. - Environment Variables: You must have
GENESYS_CLOUD_CLIENT_ID,GENESYS_CLOUD_CLIENT_SECRET, andGENESYS_CLOUD_REGIONset in your environment.
Authentication Setup
Genesys Cloud uses OAuth 2.0 Client Credentials grant for server-to-server integrations. You must obtain a valid access token before making any API calls. The token expires after one hour, so production code should implement a refresh mechanism. For this tutorial, we will use a helper function to fetch a fresh token.
import os
import requests
import time
from typing import Optional
def get_access_token() -> str:
"""
Fetches a new OAuth2 access token using Client Credentials.
In production, implement caching to avoid fetching a new token on every request.
"""
client_id = os.getenv("GENESYS_CLOUD_CLIENT_ID")
client_secret = os.getenv("GENESYS_CLOUD_CLIENT_SECRET")
region = os.getenv("GENESYS_CLOUD_REGION", "us-east-1")
if not client_id or not client_secret:
raise ValueError("GENESYS_CLOUD_CLIENT_ID and GENESYS_CLOUD_CLIENT_SECRET must be set.")
# Construct the OAuth endpoint based on the region
if region == "us-east-1":
auth_host = "https://api.mypurecloud.com"
elif region == "eu-west-1":
auth_host = "https://api.eu.pure.cloud"
elif region == "ap-southeast-1":
auth_host = "https://api.ap.pure.cloud"
else:
raise ValueError(f"Unsupported region: {region}")
url = f"{auth_host}/oauth/token"
headers = {
"Content-Type": "application/x-www-form-urlencoded"
}
data = {
"grant_type": "client_credentials",
"client_id": client_id,
"client_secret": client_secret
}
response = requests.post(url, headers=headers, data=data)
if response.status_code != 200:
raise Exception(f"Failed to get access token: {response.status_code} - {response.text}")
token_data = response.json()
return token_data["access_token"]
# Example usage
token = get_access_token()
print(f"Access Token obtained: {token[:20]}...")
Implementation
Step 1: Locate the Customer Interaction Context
To send a proactive message, you need to target a specific device or user. Genesys Cloud Webchat proactive messages are typically sent to a device identified by a device_id or by matching a user’s external_id (often an email or phone number) within a specific webchat widget.
First, we must find the recent interactions associated with the customer. We will search for interactions by the customer’s phone number or email. This step ensures we are targeting a known entity and allows us to retrieve the device_id if it was stored in previous interaction metadata.
Required Scope: interaction:read
import json
from datetime import datetime, timedelta
def find_recent_interaction(token: str, customer_phone: str, region: str) -> Optional[dict]:
"""
Searches for recent webchat interactions for a given phone number.
Returns the most recent interaction object or None.
"""
auth_host = get_auth_host(region)
url = f"{auth_host}/api/v2/interactions/details/query"
# Define the time window for the search (last 30 days)
end_time = datetime.utcnow().isoformat() + "Z"
start_time = (datetime.utcnow() - timedelta(days=30)).isoformat() + "Z"
# Query body for interaction search
query_body = {
"groupBy": "interaction",
"filter": {
"type": "and",
"clauses": [
{
"type": "field",
"field": "routing.phoneNumber",
"op": "eq",
"values": [customer_phone]
},
{
"type": "field",
"field": "routing.mediaType",
"op": "eq",
"values": ["webchat"]
},
{
"type": "field",
"field": "routing.startTime",
"op": "gte",
"values": [start_time]
}
]
},
"sort": [
{
"field": "routing.startTime",
"order": "desc"
}
],
"size": 1
}
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
response = requests.post(url, headers=headers, json=query_body)
if response.status_code == 200:
data = response.json()
if data.get("total") > 0:
return data["results"][0]
else:
return None
else:
raise Exception(f"Failed to query interactions: {response.status_code} - {response.text}")
def get_auth_host(region: str) -> str:
if region == "us-east-1":
return "https://api.mypurecloud.com"
elif region == "eu-west-1":
return "https://api.eu.pure.cloud"
elif region == "ap-southeast-1":
return "https://api.ap.pure.cloud"
else:
raise ValueError(f"Unsupported region: {region}")
Step 2: Identify the Target Webchat Widget and Device
Proactive messages are sent to a specific Webchat Widget. You must know the widgetId of the webchat interface where you want the message to appear. Additionally, if you want to ensure the message goes to a specific browser instance, you need the device_id.
If you do not have the device_id from the previous interaction, you can send the proactive message to all devices associated with a user’s external_id (if configured in your Genesys Cloud instance to track users by external ID). However, the most robust method for “previous session” targeting is using the device_id stored in the interaction metadata.
For this example, we assume you have the widgetId and you are targeting by external_id (e.g., email) or device_id. If you do not have the device_id, you must rely on the Genesys Cloud User Management or Interaction metadata to map the customer to a device.
Note: The proactive message API does not “search” for devices. It pushes to a known device_id or a user identified by external_id within a widget.
Let us assume we are targeting a user by their external_id (email) in a specific widget.
Required Scope: webchat:proactive:send
Step 3: Construct and Send the Proactive Message
The core API call is POST /api/v2/webchat/proactive/messages. This endpoint accepts a payload containing the widget ID, the target device/user, and the message content.
def send_proactive_message(token: str, widget_id: str, target_external_id: str, message_body: str, region: str) -> dict:
"""
Sends a proactive webchat message to a user identified by external_id.
"""
auth_host = get_auth_host(region)
url = f"{auth_host}/api/v2/webchat/proactive/messages"
# The payload structure for proactive messaging
payload = {
"widgetId": widget_id,
"target": {
"type": "externalId", # Can also be "deviceId"
"value": target_external_id
},
"message": {
"type": "text",
"text": message_body
},
"metadata": {
"source": "proactive_campaign",
"campaign_id": "summer_sale_2024"
}
}
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
response = requests.post(url, headers=headers, json=payload)
if response.status_code == 202:
# 202 Accepted means the message is queued for delivery
return {"status": "queued", "response": response.json()}
elif response.status_code == 404:
# Target user/device not found or widget does not exist
return {"status": "not_found", "error": response.json()}
elif response.status_code == 429:
# Rate limited
retry_after = response.headers.get("Retry-After", 1)
return {"status": "rate_limited", "retry_after": int(retry_after)}
else:
raise Exception(f"Failed to send proactive message: {response.status_code} - {response.text}")
Complete Working Example
This script combines the authentication, interaction lookup (to verify the user exists), and the proactive message sending. It demonstrates a safe workflow: verify the user had a recent interaction, then send the message.
import os
import sys
import time
import requests
from datetime import datetime, timedelta
from typing import Optional
# --- Configuration ---
REGION = os.getenv("GENESYS_CLOUD_REGION", "us-east-1")
CLIENT_ID = os.getenv("GENESYS_CLOUD_CLIENT_ID")
CLIENT_SECRET = os.getenv("GENESYS_CLOUD_CLIENT_SECRET")
WIDGET_ID = os.getenv("GENESYS_CLOUD_WIDGET_ID") # Required: Your Webchat Widget ID
# --- Helper Functions ---
def get_auth_host(region: str) -> str:
if region == "us-east-1":
return "https://api.mypurecloud.com"
elif region == "eu-west-1":
return "https://api.eu.pure.cloud"
elif region == "ap-southeast-1":
return "https://api.ap.pure.cloud"
else:
raise ValueError(f"Unsupported region: {region}")
def get_access_token() -> str:
client_id = CLIENT_ID
client_secret = CLIENT_SECRET
region = REGION
if not client_id or not client_secret:
raise ValueError("Environment variables GENESYS_CLOUD_CLIENT_ID and GENESYS_CLOUD_CLIENT_SECRET must be set.")
auth_host = get_auth_host(region)
url = f"{auth_host}/oauth/token"
headers = {"Content-Type": "application/x-www-form-urlencoded"}
data = {
"grant_type": "client_credentials",
"client_id": client_id,
"client_secret": client_secret
}
response = requests.post(url, headers=headers, data=data)
if response.status_code != 200:
raise Exception(f"Failed to get access token: {response.status_code} - {response.text}")
return response.json()["access_token"]
def verify_recent_interaction(token: str, customer_phone: str) -> bool:
"""
Checks if the customer had a webchat interaction in the last 30 days.
"""
auth_host = get_auth_host(REGION)
url = f"{auth_host}/api/v2/interactions/details/query"
end_time = datetime.utcnow().isoformat() + "Z"
start_time = (datetime.utcnow() - timedelta(days=30)).isoformat() + "Z"
query_body = {
"groupBy": "interaction",
"filter": {
"type": "and",
"clauses": [
{"type": "field", "field": "routing.phoneNumber", "op": "eq", "values": [customer_phone]},
{"type": "field", "field": "routing.mediaType", "op": "eq", "values": ["webchat"]},
{"type": "field", "field": "routing.startTime", "op": "gte", "values": [start_time]}
]
},
"size": 1
}
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
response = requests.post(url, headers=headers, json=query_body)
if response.status_code == 200:
data = response.json()
return data.get("total", 0) > 0
else:
raise Exception(f"Interaction check failed: {response.status_code}")
def send_proactive_notification(token: str, widget_id: str, target_external_id: str, message_text: str) -> dict:
"""
Sends the proactive message.
"""
auth_host = get_auth_host(REGION)
url = f"{auth_host}/api/v2/webchat/proactive/messages"
payload = {
"widgetId": widget_id,
"target": {
"type": "externalId",
"value": target_external_id
},
"message": {
"type": "text",
"text": message_text
}
}
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
# Implement basic retry logic for 429s
for attempt in range(3):
response = requests.post(url, headers=headers, json=payload)
if response.status_code == 202:
return {"success": True, "message": "Message queued", "data": response.json()}
elif response.status_code == 429:
retry_after = int(response.headers.get("Retry-After", 2 ** attempt))
print(f"Rate limited. Retrying in {retry_after} seconds...")
time.sleep(retry_after)
else:
raise Exception(f"Failed to send message: {response.status_code} - {response.text}")
raise Exception("Max retries exceeded for proactive message.")
# --- Main Execution ---
def main():
# 1. Setup
if not WIDGET_ID:
print("Error: GENESYS_CLOUD_WIDGET_ID environment variable not set.")
sys.exit(1)
customer_phone = "+15550199822" # Example customer phone
customer_external_id = "customer@example.com" # Example external ID (email)
message_content = "Hello! We noticed you had a recent session with us. Is there anything else we can help you with today?"
try:
# 2. Authenticate
print("Authenticating...")
token = get_access_token()
# 3. Verify Context (Optional but recommended)
print(f"Checking recent interactions for {customer_phone}...")
has_history = verify_recent_interaction(token, customer_phone)
if not has_history:
print("No recent webchat interactions found. Skipping proactive message to avoid spam.")
return
# 4. Send Proactive Message
print(f"Sending proactive message to {customer_external_id}...")
result = send_proactive_notification(token, WIDGET_ID, customer_external_id, message_content)
print("Success:", result)
except Exception as e:
print(f"Error: {e}")
sys.exit(1)
if __name__ == "__main__":
main()
Common Errors & Debugging
Error: 403 Forbidden
Cause: The OAuth token does not have the webchat:proactive:send scope. This is the most common error when starting with proactive messaging.
Fix:
- Go to the Genesys Cloud Admin console.
- Navigate to Setup > Integrations > OAuth 2.0 Clients.
- Select your client.
- Edit the Scopes.
- Ensure
webchat:proactive:sendis checked. - Save and regenerate the token.
Code Check:
Verify your token request includes the correct scopes if you are using a custom token generation flow. In the Client Credentials flow, the scopes are defined in the client configuration in the admin console, not in the POST body.
Error: 404 Not Found
Cause:
- The
widgetIdprovided does not exist or is not active. - The
target(external ID or device ID) does not exist in the Genesys Cloud system for that widget.
Fix:
- Verify the
widgetIdby listing webchat widgets:GET /api/v2/webchat/widgets. - Ensure the
target_external_idmatches exactly how it was stored in the user’s profile during their previous interaction. Genesys Cloud is case-sensitive for external IDs.
Error: 429 Too Many Requests
Cause: You are exceeding the rate limit for the proactive messaging endpoint. The default rate limit is typically 100 requests per minute per client, but this can vary by license tier.
Fix:
Implement exponential backoff. The code example above includes a basic retry loop with Retry-After header parsing.
# Improved retry logic snippet
import time
def send_with_retry(url, headers, payload, max_retries=3):
for attempt in range(max_retries):
response = requests.post(url, headers=headers, json=payload)
if response.status_code == 429:
wait_time = int(response.headers.get("Retry-After", 2 ** attempt))
print(f"Rate limited. Waiting {wait_time}s...")
time.sleep(wait_time)
else:
return response
raise Exception("Max retries exceeded")
Error: Message Not Receiving
Cause: The user’s browser does not have the webchat widget open, or the proactive messaging feature is not enabled in the Webchat Widget configuration.
Fix:
- In the Genesys Cloud Admin console, go to Channels > Webchat > Widgets.
- Select your widget.
- Go to the Proactive tab.
- Ensure “Enable Proactive Messaging” is checked.
- Ensure the user has the widget open in their browser. Proactive messages are only delivered to active sessions or registered devices if push notifications are configured. Note: Webchat proactive messages typically require the user to be on the page with the widget loaded. If you need to reach users offline, consider using SMS or Email channels instead, or configure Webchat to integrate with a mobile push notification service.