Updating Participant Attributes Mid-Conversation via the Genesys Cloud Conversations API
What You Will Build
- You will build a script that locates an active conversation and updates specific participant attributes (such as custom data fields or interaction metadata) while the conversation is live.
- This tutorial uses the Genesys Cloud PureCloud Platform SDK for Python and the underlying REST API.
- The examples cover Python, with concepts applicable to Java, JavaScript, and C# implementations.
Prerequisites
- OAuth Client: A Genesys Cloud OAuth application with
confidentialclient type. - Required Scopes:
conversation:participant:write(to update participant details)conversation:read(to locate the conversation if not known beforehand)
- SDK Version:
genesyscloudPython SDK v2.0.0 or higher. - Runtime: Python 3.8+.
- Dependencies:
pip install genesyscloud
Authentication Setup
Genesys Cloud uses OAuth 2.0. For server-to-server integrations, the Client Credentials Grant is the standard flow. You must obtain an access token before making any API calls. The token expires after one hour, so production code should implement a refresh mechanism.
Python Authentication Code
import os
from purecloudplatformclientv2 import Configuration, ApiClient, ConversationApi
def get_api_client():
"""
Initializes and returns a configured PureCloud Platform API Client.
Uses environment variables for security.
"""
# Load configuration from environment variables
config = Configuration(
host="https://api.mypurecloud.com", # Adjust for your environment (e.g., api.us-gov.purecloud.com)
client_id=os.getenv("GENESYS_CLIENT_ID"),
client_secret=os.getenv("GENESYS_CLIENT_SECRET")
)
# The SDK handles token acquisition and caching automatically
api_client = ApiClient(configuration=config)
# Verify the client is ready by checking the token
if not api_client.auth_config.access_token:
raise Exception("Failed to authenticate. Check Client ID and Secret.")
return api_client
# Usage
try:
api_client = get_api_client()
print("Authentication successful.")
except Exception as e:
print(f"Authentication failed: {e}")
exit(1)
Note on Environments: If you are in the US Government Cloud, change the host to https://api.us-gov.purecloud.com. For other regions, use https://api.{region}.mypurecloud.com (e.g., api.eu.purecloud.com).
Implementation
Step 1: Locate the Active Conversation
To update a participant, you need two identifiers: the conversationId and the participantId. If you do not have these values, you must query the active conversations.
Endpoint: GET /api/v2/conversations
Scope: conversation:read
Python SDK Implementation
from purecloudplatformclientv2 import ConversationApi
def find_active_conversation(api_client: ApiClient, external_id: str) -> str:
"""
Finds an active conversation by external ID.
Returns the conversationId if found, None otherwise.
"""
conversation_api = ConversationApi(api_client)
# Query parameters
# active=true ensures we only look at live conversations
# externalId=... filters by a custom identifier passed during call initiation
try:
# The SDK method paginates automatically if needed, but we limit for speed
response = conversation_api.get_conversations(
active="true",
external_id=external_id,
page_size=10,
page_number=1
)
if response.entities and len(response.entities) > 0:
conversation = response.entities[0]
print(f"Found conversation: {conversation.id}")
return conversation.id
else:
print("No active conversation found with the given external ID.")
return None
except Exception as e:
print(f"Error fetching conversations: {e}")
return None
# Example Usage
external_id = "ORDER-12345"
conv_id = find_active_conversation(api_client, external_id)
if not conv_id:
exit(1)
Why this matters: You cannot update a participant in a conversation you cannot identify. Using externalId is the most robust way to link your CRM data to a Genesys conversation. If you are already inside a WebChat widget or a telephony callback, you may already possess the conversationId.
Step 2: Identify the Participant
Once you have the conversationId, you must identify which participant to update. In a voice call, there are typically two participants (agent and caller). In digital channels, there may be more.
Endpoint: GET /api/v2/conversations/{conversationId}/participants
Scope: conversation:read
Python SDK Implementation
from purecloudplatformclientv2 import ConversationApi
def get_participant_id(api_client: ApiClient, conversation_id: str, participant_identifier: str) -> str:
"""
Retrieves the participantId for a specific participant in a conversation.
participant_identifier can be an external ID or a name/email depending on channel.
"""
conversation_api = ConversationApi(api_client)
try:
# Fetch all participants in the conversation
response = conversation_api.get_conversations_conversation_participants(
conversation_id=conversation_id
)
if response.entities:
for participant in response.entities:
# Example: Match by external_id if you set it during creation
# Or match by name/email for digital channels
if participant.external_id and participant.external_id == participant_identifier:
print(f"Found participant ID: {participant.id}")
return participant.id
# Fallback: If you know the name (e.g., for webchat)
if participant.name and participant.name == participant_identifier:
print(f"Found participant by name: {participant.id}")
return participant.id
print("Participant not found in conversation.")
return None
else:
print("No participants found in conversation.")
return None
except Exception as e:
print(f"Error fetching participants: {e}")
return None
# Example Usage
# Assuming the caller has an external ID set during the call initiation
participant_ext_id = "CUST-9876"
part_id = get_participant_id(api_client, conv_id, participant_ext_id)
if not part_id:
exit(1)
Step 3: Update Participant Attributes
This is the core action. You will send a PATCH request to the participant endpoint. The body must contain only the fields you wish to update. Sending the entire participant object is not recommended and may cause errors if fields are immutable.
Endpoint: PATCH /api/v2/conversations/{conversationId}/participants/{participantId}
Scope: conversation:participant:write
Key Fields to Update
attributes: A map of key-value pairs. This is the standard place for custom data.external_id: Can be updated if needed.name: Can be updated (e.g., correcting a typo in WebChat).
Python SDK Implementation
from purecloudplatformclientv2 import ConversationApi, PatchParticipantRequest
def update_participant_attributes(api_client: ApiClient, conversation_id: str, participant_id: str, attributes: dict):
"""
Updates custom attributes for a participant in an active conversation.
"""
conversation_api = ConversationApi(api_client)
# Create the patch request body
# Only include fields that need to change
patch_body = PatchParticipantRequest(
attributes=attributes
)
try:
# Execute the PATCH request
response = conversation_api.patch_conversations_conversation_participant(
conversation_id=conversation_id,
participant_id=participant_id,
body=patch_body
)
print(f"Participant updated successfully. Status: {response}")
return True
except Exception as e:
# Handle specific HTTP errors
if hasattr(e, 'status') and e.status == 404:
print(f"Conversation or Participant not found: {e.body}")
elif hasattr(e, 'status') and e.status == 409:
print(f"Conflict: The participant state does not allow this update (e.g., conversation ended): {e.body}")
elif hasattr(e, 'status') and e.status == 429:
print(f"Rate limit exceeded. Retry after {e.headers.get('Retry-After', 'unknown')} seconds.")
else:
print(f"Error updating participant: {e}")
return False
# Example Usage
# Add custom attributes
new_attrs = {
"orderStatus": "Processing",
"vipLevel": "Gold",
"lastInteractionTime": "2023-10-27T10:00:00Z"
}
success = update_participant_attributes(api_client, conv_id, part_id, new_attrs)
Critical Note on Immutability: You cannot update certain fields via PATCH, such as id, self_uri, or conversation_id. Attempting to include these in the PatchParticipantRequest will result in a 400 Bad Request. Only send the fields you intend to modify.
Step 4: Handling Rate Limits and Retries
Genesys Cloud APIs enforce rate limits. A 429 Too Many Requests response indicates you have exceeded the limit for your organization or endpoint. Your code must handle this gracefully.
Python Retry Logic
import time
import random
def update_with_retry(api_client: ApiClient, conversation_id: str, participant_id: str, attributes: dict, max_retries=3):
"""
Updates participant attributes with exponential backoff retry logic for 429 errors.
"""
conversation_api = ConversationApi(api_client)
patch_body = PatchParticipantRequest(attributes=attributes)
for attempt in range(max_retries):
try:
response = conversation_api.patch_conversations_conversation_participant(
conversation_id=conversation_id,
participant_id=participant_id,
body=patch_body
)
print(f"Success on attempt {attempt + 1}")
return response
except Exception as e:
if hasattr(e, 'status') and e.status == 429:
retry_after = int(e.headers.get('Retry-After', 1))
# Add jitter to prevent thundering herd
wait_time = retry_after + random.uniform(0, 1)
print(f"Rate limited. Waiting {wait_time:.2f} seconds before retry...")
time.sleep(wait_time)
else:
# Non-429 error, do not retry
print(f"Non-retryable error: {e}")
raise e
print("Max retries exceeded.")
return None
# Usage
update_with_retry(api_client, conv_id, part_id, new_attrs)
Complete Working Example
Below is a complete, runnable Python script that integrates authentication, conversation lookup, participant identification, and attribute updating.
import os
import sys
import time
import random
from purecloudplatformclientv2 import (
Configuration,
ApiClient,
ConversationApi,
PatchParticipantRequest
)
# Configuration
CLIENT_ID = os.getenv("GENESYS_CLIENT_ID")
CLIENT_SECRET = os.getenv("GENESYS_CLIENT_SECRET")
ENVIRONMENT = os.getenv("GENESYS_ENV", "mypurecloud.com") # e.g., mypurecloud.com, us-gov.purecloud.com
def init_api_client():
"""Initializes the Genesys Cloud API Client."""
config = Configuration(
host=f"https://api.{ENVIRONMENT}",
client_id=CLIENT_ID,
client_secret=CLIENT_SECRET
)
api_client = ApiClient(configuration=config)
# Force token generation
if not api_client.auth_config.access_token:
raise ConnectionError("Failed to obtain access token.")
return api_client
def find_conversation(api_client: ApiClient, external_id: str) -> str:
"""Finds an active conversation by external ID."""
conversation_api = ConversationApi(api_client)
try:
response = conversation_api.get_conversations(
active="true",
external_id=external_id,
page_size=1,
page_number=1
)
if response.entities:
return response.entities[0].id
except Exception as e:
print(f"Error finding conversation: {e}")
return None
def find_participant(api_client: ApiClient, conversation_id: str, participant_external_id: str) -> str:
"""Finds a participant in a conversation by their external ID."""
conversation_api = ConversationApi(api_client)
try:
response = conversation_api.get_conversations_conversation_participants(
conversation_id=conversation_id
)
for p in response.entities:
if p.external_id == participant_external_id:
return p.id
except Exception as e:
print(f"Error finding participant: {e}")
return None
def update_attributes_with_retry(api_client: ApiClient, conversation_id: str, participant_id: str, attributes: dict, max_retries=3):
"""Updates participant attributes with retry logic for rate limits."""
conversation_api = ConversationApi(api_client)
patch_body = PatchParticipantRequest(attributes=attributes)
for attempt in range(max_retries):
try:
conversation_api.patch_conversations_conversation_participant(
conversation_id=conversation_id,
participant_id=participant_id,
body=patch_body
)
print(f"Attributes updated successfully.")
return True
except Exception as e:
if hasattr(e, 'status') and e.status == 429:
retry_after = int(e.headers.get('Retry-After', 1))
wait_time = retry_after + random.uniform(0, 1)
print(f"Rate limited. Retrying in {wait_time:.2f}s...")
time.sleep(wait_time)
else:
print(f"Update failed: {e}")
return False
return False
def main():
# 1. Setup
if not CLIENT_ID or not CLIENT_SECRET:
print("Error: GENESYS_CLIENT_ID and GENESYS_CLIENT_SECRET environment variables are required.")
sys.exit(1)
try:
api_client = init_api_client()
except Exception as e:
print(f"Authentication failed: {e}")
sys.exit(1)
# 2. Input Parameters
CONVERSATION_EXTERNAL_ID = "ORDER-555"
PARTICIPANT_EXTERNAL_ID = "CUST-123"
NEW_ATTRIBUTES = {
"priority": "High",
"queuePosition": "1",
"agentAssigned": "John Doe"
}
# 3. Execution
print(f"Searching for conversation: {CONVERSATION_EXTERNAL_ID}")
conv_id = find_conversation(api_client, CONVERSATION_EXTERNAL_ID)
if not conv_id:
print("Conversation not found. Exiting.")
sys.exit(1)
print(f"Searching for participant: {PARTICIPANT_EXTERNAL_ID}")
part_id = find_participant(api_client, conv_id, PARTICIPANT_EXTERNAL_ID)
if not part_id:
print("Participant not found. Exiting.")
sys.exit(1)
print(f"Updating attributes for participant {part_id}")
success = update_attributes_with_retry(api_client, conv_id, part_id, NEW_ATTRIBUTES)
if success:
print("Process completed successfully.")
else:
print("Process failed.")
if __name__ == "__main__":
main()
Common Errors & Debugging
Error: 404 Not Found
- Cause: The
conversationIdorparticipantIdis invalid, or the conversation has ended. - Fix: Verify the IDs. If the conversation ended, you cannot update participant attributes via the Conversations API. You must use the Analytics API to retrieve historical data.
- Code Check: Ensure
active="true"is used when searching for conversations.
Error: 409 Conflict
- Cause: The participant state does not allow the update. For example, attempting to update attributes after the participant has left the conversation.
- Fix: Check the
statusof the participant. If it isleftorclosed, the update will fail.
Error: 429 Too Many Requests
- Cause: You have exceeded the API rate limit for your organization.
- Fix: Implement exponential backoff with jitter. Check the
Retry-Afterheader in the response. - Code Check: Use the
update_with_retryfunction provided above.
Error: 400 Bad Request
- Cause: The request body contains invalid fields or data types.
- Fix: Ensure you are only sending updatable fields (e.g.,
attributes). Do not sendid,self_uri, orconversation_id. Ensure attribute values are strings.