Sending Proactive Notifications to a Customer Who Previously Had a Web Messaging Session
What You Will Build
- This tutorial demonstrates how to retrieve a customer’s identity from a previous web chat session and send them a proactive outbound message via the Genesys Cloud Messaging API.
- The solution uses the Genesys Cloud PureCloud Platform Client SDK (Python) and the REST API for
analytics/conversations/details/queryandapi/v2/messaging/conversations. - The programming language covered is Python 3.9+.
Prerequisites
- OAuth Client Type: Service Account (Client Credentials Grant) or Resource Owner Password Credentials (for user context). This example uses Client Credentials for server-to-server automation.
- Required OAuth Scopes:
analytics:conversation:view(to query previous chat history)messaging:conversation:viewandmessaging:conversation:create(to send the new message)identity:profile:view(optional, if resolving user details)
- SDK Version:
genesys-cloud-purecloud-platform-client>= 162.0.0 - Runtime Requirements: Python 3.9+
- External Dependencies:
pip install genesys-cloud-purecloud-platform-clientpip install python-dotenv(for secure credential management)
Authentication Setup
Genesys Cloud APIs require a valid OAuth 2.0 access token. For automated scripts that run without a logged-in user context (such as a background job triggering proactive messages), use the Client Credentials Grant flow.
Create a .env file in your project root:
GENESYS_REGION="mypurecloud.com"
GENESYS_CLIENT_ID="your_client_id"
GENESYS_CLIENT_SECRET="your_client_secret"
The following Python code initializes the PureCloud client with automatic token refresh handling. The SDK manages the expires_in window, so you do not need to manually refresh tokens unless the script runs for hours.
import os
from dotenv import load_dotenv
from purecloud_platform_client import Configuration, ApiClient, PureCloudAuthMethod
from purecloud_platform_client.rest import ApiException
# Load environment variables
load_dotenv()
def get_purecloud_api_client() -> ApiClient:
"""
Initializes and returns an authenticated PureCloud API Client.
"""
config = Configuration(
host=f"https://api.{os.getenv('GENESYS_REGION')}",
access_token_url=f"https://login.{os.getenv('GENESYS_REGION')}/oauth/token"
)
# Configure authentication using Client Credentials
config.auth_settings = {
'OAuth2': {
'client_id': os.getenv('GENESYS_CLIENT_ID'),
'client_secret': os.getenv('GENESYS_CLIENT_SECRET'),
'grant_type': 'client_credentials',
'scope': 'analytics:conversation:view messaging:conversation:create messaging:conversation:view'
}
}
# Create the API client
api_client = ApiClient(configuration=config, auth_method=PureCloudAuthMethod.OAUTH2)
# Verify connection by fetching a simple endpoint (optional but recommended for debugging)
try:
# This triggers the initial token fetch
api_client.call_api('/api/v2/health', 'GET', _return_http_data_only=True)
except ApiException as e:
print(f"Authentication failed: {e.body}")
raise
return api_client
Implementation
Step 1: Identify the Customer from Previous Chat History
To send a proactive message, you must identify the customer. In Genesys Cloud, a “customer” in web messaging is identified by their externalId (a unique string you assign, often an email hash or CRM ID) or their address (e.g., email:user@example.com).
Assume you have a CRM ID or Email address. You will query the analytics/conversations/details/query endpoint to find the most recent web chat session associated with that identity. This ensures you are targeting the correct conversation thread or user profile.
Endpoint: POST /api/v2/analytics/conversations/details/query
from purecloud_platform_client import AnalyticsApi, MessagingApi
from purecloud_platform_client.models import ConversationDetailsQuery, ConversationDetailsQueryFilter
def find_recent_chat_session(api_client: ApiClient, customer_email: str) -> str | None:
"""
Queries analytics to find the most recent web chat for a customer email.
Returns the externalId if found, otherwise None.
"""
analytics_api = AnalyticsApi(api_client)
# Define the query filter
# We look for conversations of type 'webchat'
# We filter by the participant identity if possible, though analytics queries
# often require aggregating by address.
query = ConversationDetailsQuery(
interval="2023-01-01/2025-12-31", # Wide range to ensure history is captured
metrics=[
{
"name": "conversationId",
"filter": {
"name": "conversationType",
"op": "eq",
"value": "webchat"
}
}
],
# Note: Direct filtering by customer address in analytics requires
# specific segment configurations. For this tutorial, we assume
# we are searching by a known ExternalId stored in your CRM,
# OR we are using the address directly in the next step.
#
# A more robust pattern for proactive messaging is:
# 1. You already know the customer's 'externalId' from your CRM.
# 2. If you only have their email, construct the address.
# For this example, let's assume we are looking up by a known ExternalId
# If you do not have an ExternalId, you must use the 'address' field
# in the messaging create call, as shown in Step 2.
# Here is how you would query if you had a segment or specific filter
# configured for "Recent Web Chats".
pass
# SIMPLIFICATION: In production proactive messaging, you usually already
# possess the customer's identifier (ExternalId or Email) from your CRM.
# The analytics query is used if you need to verify they *had* a chat.
# For this tutorial, we will skip the complex analytics aggregation
# and assume we have the customer's email, which we will convert to a Genesys Address.
return customer_email
Note: The Analytics API is powerful but complex for simple lookups. If you maintain a CRM, you likely already have the externalId or email of the user who chatted previously. The critical step is constructing the correct address for the outbound message.
Step 2: Construct the Outbound Messaging Request
Genesys Cloud Web Messaging uses an address format to identify participants. For a customer, the address is typically email:user@example.com. For a proactive outbound message, you must create a new conversation or add a message to an existing one.
Scenario: You want to send a proactive message to customer@example.com who chatted with you last week.
Required OAuth Scope: messaging:conversation:create
from purecloud_platform_client.models import (
MessagingConversationPostRequest,
MessagingConversationMessage,
MessagingConversationMessageContent,
MessagingConversationParticipant
)
def build_proactive_message_request(customer_email: str, message_body: str) -> MessagingConversationPostRequest:
"""
Constructs the payload for creating a new outbound messaging conversation.
"""
# The address format for a customer is 'email:address'
customer_address = f"email:{customer_email}"
# Define the message content
content = MessagingConversationMessageContent(
text=message_body
)
# Define the message itself
message = MessagingConversationMessage(
content=content
)
# Define the customer participant
customer_participant = MessagingConversationParticipant(
address=customer_address,
# If you have a specific externalId from your CRM, include it here
# to link this new conversation to the previous one in analytics.
# external_id="crm_user_id_123"
)
# The request object
request = MessagingConversationPostRequest(
messages=[message],
participants=[customer_participant]
# Optionally specify a channel if you have multiple webchat channels configured
# channel_id="your-webchat-channel-id"
)
return request
Step 3: Send the Message and Handle Errors
This step executes the API call. It includes robust error handling for common scenarios like rate limiting (429) and invalid addresses (400/404).
def send_proactive_message(api_client: ApiClient, customer_email: str, message_body: str) -> dict:
"""
Sends the proactive message to the customer.
Returns the API response body or raises an exception.
"""
messaging_api = MessagingApi(api_client)
try:
request_body = build_proactive_message_request(customer_email, message_body)
# Execute the API call
response = messaging_api.post_messaging_conversations(body=request_body)
print(f"Message sent successfully. Conversation ID: {response.conversation_id}")
return response.to_dict()
except ApiException as e:
# Handle specific HTTP status codes
if e.status == 401:
print("Error: Unauthorized. Check OAuth token validity.")
elif e.status == 403:
print("Error: Forbidden. Check OAuth scopes (messaging:conversation:create).")
elif e.status == 429:
print("Error: Rate Limited. Implement exponential backoff.")
# In a production loop, you would sleep and retry here
elif e.status == 400:
print(f"Error: Bad Request. Invalid payload or address format. Body: {e.body}")
elif e.status == 404:
print(f"Error: Not Found. The customer address may be invalid or not associated with a known identity.")
else:
print(f"Unexpected Error: {e.status} - {e.body}")
raise e
Complete Working Example
This script combines authentication, request construction, and execution. It assumes you have a list of customers to message.
import os
import time
from dotenv import load_dotenv
from purecloud_platform_client import Configuration, ApiClient, PureCloudAuthMethod, MessagingApi
from purecloud_platform_client.rest import ApiException
from purecloud_platform_client.models import (
MessagingConversationPostRequest,
MessagingConversationMessage,
MessagingConversationMessageContent,
MessagingConversationParticipant
)
# Load environment variables
load_dotenv()
def get_api_client() -> ApiClient:
config = Configuration(
host=f"https://api.{os.getenv('GENESYS_REGION')}",
access_token_url=f"https://login.{os.getenv('GENESYS_REGION')}/oauth/token"
)
config.auth_settings = {
'OAuth2': {
'client_id': os.getenv('GENESYS_CLIENT_ID'),
'client_secret': os.getenv('GENESYS_CLIENT_SECRET'),
'grant_type': 'client_credentials',
'scope': 'messaging:conversation:create messaging:conversation:view'
}
}
return ApiClient(configuration=config, auth_method=PureCloudAuthMethod.OAUTH2)
def send_proactive_outbound(api_client: ApiClient, email: str, message: str):
"""
Sends a proactive web message to a customer.
"""
messaging_api = MessagingApi(api_client)
# Construct Address
address = f"email:{email}"
# Construct Content
content = MessagingConversationMessageContent(text=message)
msg = MessagingConversationMessage(content=content)
# Construct Participant
# Note: If you want to link this to a previous session, ensure the 'externalId'
# matches the one used in the previous chat session if you tracked it.
participant = MessagingConversationParticipant(address=address)
# Construct Request
request = MessagingConversationPostRequest(
messages=[msg],
participants=[participant]
)
try:
# Send the message
response = messaging_api.post_messaging_conversations(body=request)
print(f"Success: Sent to {email}. Conversation ID: {response.conversation_id}")
return response.conversation_id
except ApiException as e:
print(f"Failed to send to {email}: Status {e.status}, Reason: {e.reason}")
if e.status == 429:
print("Retrying after 5 seconds due to rate limit...")
time.sleep(5)
# Retry logic would go here in a production loop
return None
def main():
# 1. Initialize Client
api_client = get_api_client()
# 2. Define Customers and Messages
# In a real scenario, fetch these from your CRM/Database
customers = [
{"email": "john.doe@example.com", "message": "Hi John, thanks for chatting with us earlier. Here is the link you asked for: https://example.com/help"},
{"email": "jane.smith@example.com", "message": "Hi Jane, we noticed your session timed out. How can we assist you further?"}
]
# 3. Iterate and Send
for customer in customers:
send_proactive_outbound(
api_client,
customer["email"],
customer["message"]
)
# Respect rate limits: Genesys recommends spacing out requests
time.sleep(0.5)
if __name__ == "__main__":
main()
Common Errors & Debugging
Error: 400 Bad Request - “Invalid Address”
- Cause: The
addressfield in the participant object is malformed. - Fix: Ensure the format is exactly
email:user@example.com. Do not include spaces. If using a different protocol (e.g., SMS), the format changes (tel:+15555555555). - Code Fix:
# Incorrect address = "user@example.com" # Correct address = "email:user@example.com"
Error: 403 Forbidden - “Insufficient Permissions”
- Cause: The OAuth token used does not have the
messaging:conversation:createscope. - Fix: Update your OAuth client configuration in the Genesys Cloud Admin portal or your code’s
auth_settingsto includemessaging:conversation:create. - Debugging: Print the token scopes (if accessible) or verify the
scopestring in theConfigurationobject.
Error: 429 Too Many Requests
- Cause: You are sending messages faster than Genesys Cloud allows. The limit varies by contract and endpoint but is typically around 10-20 requests per second for messaging creation.
- Fix: Implement exponential backoff.
- Code Fix:
import time def safe_send(api_client, email, message, max_retries=3): for attempt in range(max_retries): try: return send_proactive_outbound(api_client, email, message) except ApiException as e: if e.status == 429: wait_time = 2 ** attempt # 1s, 2s, 4s print(f"Rate limited. Waiting {wait_time}s...") time.sleep(wait_time) else: raise return None
Error: Message Not Delivered to Customer’s Web Browser
- Cause: Proactive web messages require the customer to have the Genesys Cloud Web Chat widget open and active, OR the message is sent as a “push” notification if configured. If the customer is not currently on the page with the widget loaded, the message is stored in their conversation history but may not trigger a browser notification unless they have the “Push Notification” capability enabled and the widget supports it.
- Fix: Ensure your Web Chat widget configuration in Genesys Cloud has “Proactive Messaging” enabled. Also, verify that the customer’s browser has granted notification permissions if you expect a pop-up.