Sending Proactive Notifications to a Customer Who Previously Had a Web Messaging Session

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/query and api/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:view and messaging: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-client
    • pip 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 address field 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:create scope.
  • Fix: Update your OAuth client configuration in the Genesys Cloud Admin portal or your code’s auth_settings to include messaging:conversation:create.
  • Debugging: Print the token scopes (if accessible) or verify the scope string in the Configuration object.

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.

Official References