How to programmatically close a Web Messaging session from the backend
What You Will Build
- You will build a backend service that programmatically terminates an active Genesys Cloud Web Messaging conversation.
- This tutorial uses the Genesys Cloud Platform API v2 (
/api/v2/conversations/messaging/) and the PythongenesyscloudSDK. - The implementation is written in Python 3.9+ using the
asyncioframework for high-concurrency handling.
Prerequisites
- OAuth Client Type: Machine-to-Machine (M2M) or Public Client with appropriate scopes.
- Required Scopes:
conversation:message:writeis mandatory to update conversation status.conversation:readis required if you need to fetch conversation details before closing. - SDK Version:
genesyscloudPython SDK version 140.0.0 or higher. - Runtime: Python 3.9 or higher.
- Dependencies:
genesyscloudpydantic(for data validation, optional but recommended)
Install the SDK via pip:
pip install genesyscloud
Authentication Setup
Genesys Cloud uses OAuth 2.0 for authentication. For backend services, the Machine-to-Machine (M2M) flow is the standard. This flow exchanges a client ID and client secret for an access token.
The genesyscloud SDK handles token caching and automatic refresh if you configure the PlatformClient correctly. You must provide your client_id, client_secret, and environment (e.g., mypurecloud.com or usw2.pure.cloud).
import os
from purecloudplatformclientv2 import PlatformClient
def get_platform_client() -> PlatformClient:
"""
Initialize and return the Genesys Cloud Platform Client.
"""
client_id = os.getenv("GENESYS_CLIENT_ID")
client_secret = os.getenv("GENESYS_CLIENT_SECRET")
environment = os.getenv("GENESYS_ENVIRONMENT", "mypurecloud.com")
if not client_id or not client_secret:
raise ValueError("GENESYS_CLIENT_ID and GENESYS_CLIENT_SECRET must be set.")
pc = PlatformClient()
pc.set_environment(environment)
pc.set_client_credentials(client_id, client_secret)
# Enable automatic token refresh
pc.enable_auto_refresh()
return pc
Implementation
Step 1: Retrieve the Conversation ID
To close a session, you need the unique conversationId. In a typical Web Messaging integration, the frontend generates a temporary session ID, but the backend must map this to the Genesys Cloud conversationId if it is not passed directly.
If your frontend sends the conversationId directly (recommended for backend-initiated actions), you can skip retrieval. If you only have the sessionKey (the temporary ID used by the widget), you must query the conversation API.
However, the most robust backend pattern is to store the mapping between your internal user/session ID and the Genesys conversationId when the conversation starts. This tutorial assumes you have the conversationId.
If you do not have it, you can list recent conversations for a specific user or queue. This is expensive and should be avoided in production. Instead, ensure your frontend passes the conversationId to your backend via a secure HTTP header or body payload upon session start.
Step 2: Construct the Close Payload
To close a Web Messaging conversation, you must perform a PATCH request to the conversation endpoint. The genesyscloud SDK provides the ConversationMessagingApi class.
The key parameter is status. For Web Messaging, the valid statuses are active, closed, and abandoned. To close the session cleanly, set status to closed.
You must also provide the closeReason if your Genesys Cloud organization requires it. This is configured in the Messaging settings. If not configured, you can omit it, but it is best practice to include a reason for analytics.
The request body requires a ConversationUpdate object.
from purecloudplatformclientv2 import (
ConversationMessagingApi,
ConversationUpdate,
ConversationStatus
)
def prepare_close_payload(conversation_id: str, close_reason: str = "Agent initiated close") -> dict:
"""
Prepare the payload for closing a conversation.
"""
# Create the ConversationUpdate object
update_body = ConversationUpdate(
status="closed", # Set status to closed
close_reason=close_reason # Optional but recommended
)
return {
"conversation_id": conversation_id,
"body": update_body
}
Step 3: Execute the Close Request
Use the patch_conversations_messaging_conversation method. This method sends the PATCH request to /api/v2/conversations/messaging/{conversationId}.
You must handle potential errors:
404 Not Found: The conversation ID is invalid or does not exist.409 Conflict: The conversation is already closed or in a state that prevents closing.403 Forbidden: The OAuth token lacks theconversation:message:writescope.429 Too Many Requests: Rate limiting has been triggered.
import logging
from purecloudplatformclientv2.rest import ApiException
logger = logging.getLogger(__name__)
async def close_web_messaging_session(pc: PlatformClient, conversation_id: str) -> bool:
"""
Programmatically close a Web Messaging conversation.
Args:
pc: The configured PlatformClient instance.
conversation_id: The unique ID of the conversation to close.
Returns:
True if the conversation was successfully closed, False otherwise.
"""
api_instance = ConversationMessagingApi(pc)
try:
# Prepare the update body
update_body = ConversationUpdate(
status="closed",
close_reason="Backend system close"
)
# Execute the patch request
# The SDK method name is patch_conversations_messaging_conversation
api_instance.patch_conversations_messaging_conversation(
conversation_id=conversation_id,
body=update_body
)
logger.info(f"Successfully closed conversation {conversation_id}")
return True
except ApiException as e:
logger.error(f"API Exception when closing conversation {conversation_id}: {e.status} - {e.reason}")
if e.status == 404:
logger.warning(f"Conversation {conversation_id} not found.")
elif e.status == 409:
logger.warning(f"Conversation {conversation_id} is already closed or in an invalid state.")
elif e.status == 403:
logger.error(f"Permission denied. Check OAuth scopes for conversation:message:write.")
elif e.status == 429:
logger.warning("Rate limit exceeded. Implement exponential backoff.")
return False
except Exception as e:
logger.error(f"Unexpected error closing conversation {conversation_id}: {str(e)}")
return False
Step 4: Handling Webhook Triggers (Optional but Recommended)
Often, you want to close a conversation automatically based on an event, such as an agent wrapping up. You can achieve this by listening to the conversation:statusChanged webhook.
When the webhook fires, check if the status is wrapup or closed. If it is wrapup and you want to force-close the messaging session immediately, invoke the close_web_messaging_session function.
This ensures that the frontend widget updates immediately to reflect the closed state, rather than waiting for the agent to manually click “Close”.
Complete Working Example
This is a complete, runnable Python script. It initializes the client, accepts a conversation ID from the command line, and closes the session.
import os
import sys
import asyncio
import logging
from purecloudplatformclientv2 import PlatformClient, ConversationMessagingApi, ConversationUpdate
from purecloudplatformclientv2.rest import ApiException
# Configure logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
def get_platform_client() -> PlatformClient:
"""
Initialize the Genesys Cloud Platform Client with M2M OAuth.
"""
client_id = os.getenv("GENESYS_CLIENT_ID")
client_secret = os.getenv("GENESYS_CLIENT_SECRET")
environment = os.getenv("GENESYS_ENVIRONMENT", "mypurecloud.com")
if not client_id or not client_secret:
raise ValueError("GENESYS_CLIENT_ID and GENESYS_CLIENT_SECRET environment variables must be set.")
pc = PlatformClient()
pc.set_environment(environment)
pc.set_client_credentials(client_id, client_secret)
pc.enable_auto_refresh()
return pc
async def close_conversation(conversation_id: str) -> None:
"""
Main function to close a Web Messaging conversation.
"""
pc = get_platform_client()
api_instance = ConversationMessagingApi(pc)
# Define the update payload
# Status must be 'closed' for Web Messaging
update_body = ConversationUpdate(
status="closed",
close_reason="Programmatic close via backend"
)
try:
logger.info(f"Attempting to close conversation: {conversation_id}")
# Perform the PATCH request
# Note: The SDK method returns the updated conversation object
updated_conversation = api_instance.patch_conversations_messaging_conversation(
conversation_id=conversation_id,
body=update_body
)
logger.info(f"Conversation {conversation_id} closed successfully. New status: {updated_conversation.status}")
except ApiException as e:
logger.error(f"Failed to close conversation {conversation_id}. Status: {e.status}, Reason: {e.reason}")
logger.error(f"Response body: {e.body}")
sys.exit(1)
except Exception as e:
logger.error(f"Unexpected error: {str(e)}")
sys.exit(1)
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python close_session.py <conversation_id>")
sys.exit(1)
conversation_id = sys.argv[1]
asyncio.run(close_conversation(conversation_id))
Common Errors & Debugging
Error: 403 Forbidden - Insufficient Scope
What causes it: The OAuth token used by the PlatformClient does not have the conversation:message:write scope.
How to fix it:
- Go to the Genesys Cloud Admin Portal.
- Navigate to Applications and Integrations > API Applications.
- Select your client ID.
- Edit the Scopes.
- Add
conversation:message:writeto the list. - Save the changes.
- Generate a new token (the SDK will do this automatically on next call if
enable_auto_refresh()is true, but ensure the client credentials are correct).
Code Fix: Verify the scope in your OAuth token payload (decode the JWT) or check the Admin Console.
Error: 409 Conflict - Conversation Already Closed
What causes it: The conversation is already in the closed status, or it is in a state that cannot be transitioned to closed (e.g., already abandoned).
How to fix it:
Check the current status before attempting to close. If the status is already closed, skip the API call.
# Check status before closing
def is_conversation_closed(conversation_id: str, pc: PlatformClient) -> bool:
api_instance = ConversationMessagingApi(pc)
try:
conv = api_instance.get_conversations_messaging_conversation(conversation_id)
return conv.status == "closed"
except ApiException as e:
if e.status == 404:
return False
raise
Error: 429 Too Many Requests
What causes it: You are sending close requests faster than the API rate limit allows. Genesys Cloud enforces rate limits per client ID and per endpoint.
How to fix it:
Implement exponential backoff. The genesyscloud SDK does not automatically retry all requests, so you must handle this in your code.
import time
async def close_with_retry(pc: PlatformClient, conversation_id: str, max_retries: int = 3) -> bool:
for attempt in range(max_retries):
try:
return await close_web_messaging_session(pc, conversation_id)
except ApiException as e:
if e.status == 429:
wait_time = 2 ** attempt # Exponential backoff: 1s, 2s, 4s
logger.warning(f"Rate limited. Retrying in {wait_time} seconds...")
await asyncio.sleep(wait_time)
else:
raise
return False
Error: 400 Bad Request - Invalid Status
What causes it: You passed an invalid status value. For Web Messaging, the only valid statuses are active, closed, and abandoned. You cannot set it to wrapup or queued via this API.
How to fix it:
Ensure the status field in ConversationUpdate is exactly "closed".