Accessing Web Messaging Participant Attributes in Genesys Cloud Architect Inbound Flows
What You Will Build
- One sentence: The code demonstrates how to retrieve custom participant attributes attached to a Web Messaging conversation via the Analytics API.
- One sentence: This uses the Genesys Cloud API v2, specifically the Analytics Conversations Details endpoint.
- One sentence: The implementation is provided in Python using the
genesyscloudSDK and rawrequestsfor clarity.
Prerequisites
- OAuth Client Type: Service Account or Confidential Client with sufficient scopes.
- Required Scopes:
analytics:conversation:readis mandatory for retrieving conversation details.webchat:readis helpful for context but not strictly required for the analytics endpoint. - SDK Version:
genesyscloudPython SDK v2.0.0 or later. - Language/Runtime: Python 3.8+.
- External Dependencies:
pip install genesyscloud requests
Authentication Setup
Genesys Cloud uses OAuth 2.0. For server-side integrations, the Client Credentials Grant flow is the standard approach. You must store the access token and handle expiration, as tokens are valid for one hour.
The following Python snippet demonstrates a robust authentication wrapper. It handles the initial token request and provides a method to check validity before making API calls.
import os
import time
import requests
from typing import Optional
class GenesysAuth:
def __init__(self, environment: str, client_id: str, client_secret: str):
self.environment = environment
self.client_id = client_id
self.client_secret = client_secret
self.token_url = f"https://{environment}.mypurecloud.com/oauth/token"
self.access_token: Optional[str] = None
self.token_expiry: float = 0
def get_access_token(self) -> str:
"""
Retrieves an OAuth access token.
Returns cached token if valid, otherwise fetches a new one.
"""
if self.access_token and time.time() < self.token_expiry:
return self.access_token
payload = {
"grant_type": "client_credentials",
"client_id": self.client_id,
"client_secret": self.client_secret
}
response = requests.post(self.token_url, data=payload)
response.raise_for_status()
data = response.json()
self.access_token = data["access_token"]
# Subtract 60 seconds to ensure we refresh before hard expiration
self.token_expiry = time.time() + data["expires_in"] - 60
return self.access_token
Error Handling:
- 401 Unauthorized: Verify
client_idandclient_secretare correct. - 403 Forbidden: The service account lacks the
analytics:conversation:readscope. Add this scope in the Genesys Cloud Admin console under Platform > OAuth Applications.
Implementation
Step 1: Querying Conversations via Analytics API
To access participant attributes, you must first locate the specific conversation ID. In Web Messaging, the conversation is created when the user initiates the chat. The Analytics API (/api/v2/analytics/conversations/details/query) allows you to search for conversations by date range, channel type, and other filters.
The key parameter here is view. To retrieve participant-level data, including attributes, you must use the wrapup or details view. However, for real-time or recent historical data, the details view is more appropriate. Crucially, you must specify metrics to include participant-attributes.
def get_recent_webchat_conversations(auth: GenesysAuth, since: str, until: str) -> list:
"""
Queries the Analytics API for Web Messaging conversations.
Args:
auth: GenesysAuth instance.
since: Start of the query window in ISO 8601 format.
until: End of the query window in ISO 8601 format.
"""
url = f"https://{auth.environment}.mypurecloud.com/api/v2/analytics/conversations/details/query"
headers = {
"Authorization": f"Bearer {auth.get_access_token()}",
"Content-Type": "application/json"
}
# The body defines what data we want to retrieve
body = {
"view": "details",
"dateRange": {
"from": since,
"to": until
},
"groupBy": ["conversationId"],
"filter": [
{
"dimension": "channelType",
"operator": "equal",
"value": "webchat"
}
],
"metrics": [
"conversation-duration",
"wrap-up-duration"
],
# CRITICAL: Without this, participant attributes are not returned
"select": [
"conversationId",
"channelType",
"participants"
]
}
response = requests.post(url, headers=headers, json=body)
if response.status_code == 429:
# Implement retry logic here in production
print("Rate limited. Please retry later.")
return []
response.raise_for_status()
data = response.json()
# The response contains a list of entities under 'entities'
# Each entity represents a conversation
conversations = data.get("entities", [])
return conversations
Expected Response Structure:
The response is paginated. The entities array contains objects with conversationId. The participants field within each entity is an array of participant objects.
{
"pageSize": 100,
"entities": [
{
"conversationId": "123e4567-e89b-12d3-a456-426614174000",
"channelType": "webchat",
"participants": [
{
"participantId": "user-participant-id",
"role": "user",
"attributes": {
"customAttr": "valueFromWebSDK"
}
},
{
"participantId": "agent-participant-id",
"role": "agent",
"attributes": {}
}
]
}
]
}
Step 2: Extracting Participant Attributes
Once you have the conversation data, you must parse the participants array. In a Web Messaging flow, the “user” participant holds the attributes set by the Web Messaging SDK on the client side. The “agent” participant typically does not have these custom attributes unless explicitly set by the agent or a bot.
The following function extracts attributes specifically from the user participant. It also handles the case where multiple user participants might exist (rare, but possible in transfer scenarios) by prioritizing the initial user.
def extract_user_attributes(conversation_entity: dict) -> dict:
"""
Extracts custom attributes from the user participant in a conversation.
Args:
conversation_entity: A single conversation object from the Analytics API response.
Returns:
A dictionary of attributes. Empty dict if no user attributes found.
"""
participants = conversation_entity.get("participants", [])
user_attributes = {}
for participant in participants:
# Identify the user participant
if participant.get("role") == "user":
# Attributes are nested under the 'attributes' key
attrs = participant.get("attributes", {})
if attrs:
user_attributes = attrs
# Break if you only care about the first user participant
break
return user_attributes
Why view: details?
The summary view aggregates data and does not expose the participants array with granular attribute data. The details view provides the raw conversation structure. Note that details queries are more expensive and subject to stricter rate limits. Use groupBy wisely to reduce payload size.
Step 3: Handling Pagination and Large Datasets
The Analytics API returns a maximum of 100 entities per page by default. To ensure you capture all relevant conversations, you must implement pagination using the nextPageUrl provided in the response.
def get_all_webchat_conversations(auth: GenesysAuth, since: str, until: str) -> list:
"""
Fetches all webchat conversations within the date range, handling pagination.
"""
all_conversations = []
url = f"https://{auth.environment}.mypurecloud.com/api/v2/analytics/conversations/details/query"
headers = {
"Authorization": f"Bearer {auth.get_access_token()}",
"Content-Type": "application/json"
}
body = {
"view": "details",
"dateRange": {"from": since, "to": until},
"groupBy": ["conversationId"],
"filter": [{"dimension": "channelType", "operator": "equal", "value": "webchat"}],
"metrics": ["conversation-duration"],
"select": ["conversationId", "participants"],
"pageSize": 100
}
while url:
response = requests.post(url, headers=headers, json=body)
response.raise_for_status()
data = response.json()
all_conversations.extend(data.get("entities", []))
# Check for next page
if "nextPageUrl" in data and data["nextPageUrl"]:
url = data["nextPageUrl"]
# The next request is a GET to the nextPageUrl, not a POST
# The body is not used in subsequent GET requests for pagination
# However, the SDK or raw requests usually handle this by switching method
# For raw requests, we must switch to GET and drop the body
response = requests.get(url, headers=headers)
response.raise_for_status()
data = response.json()
all_conversations.extend(data.get("entities", []))
url = data.get("nextPageUrl")
else:
url = None
return all_conversations
Important Note on Pagination:
The first request is a POST. Subsequent requests to nextPageUrl are GET requests. The nextPageUrl contains encoded query parameters. Do not send the original JSON body with the GET request.
Complete Working Example
This script ties together authentication, querying, and attribute extraction. It prints the conversation ID and any custom attributes found for the user participant.
import os
import sys
from datetime import datetime, timedelta
# Assume GenesysAuth class is defined as in Step 1
# Assume get_all_webchat_conversations and extract_user_attributes are defined as in Step 3 & 2
def main():
# Configuration
ENVIRONMENT = os.getenv("GENESYS_ENVIRONMENT", "mycompany")
CLIENT_ID = os.getenv("GENESYS_CLIENT_ID")
CLIENT_SECRET = os.getenv("GENESYS_CLIENT_SECRET")
if not CLIENT_ID or not CLIENT_SECRET:
print("Error: GENESYS_CLIENT_ID and GENESYS_CLIENT_SECRET environment variables are required.")
sys.exit(1)
# Initialize Auth
auth = GenesysAuth(ENVIRONMENT, CLIENT_ID, CLIENT_SECRET)
# Define Date Range (Last 1 hour)
until = datetime.utcnow().isoformat() + "Z"
since = (datetime.utcnow() - timedelta(hours=1)).isoformat() + "Z"
print(f"Querying Web Messaging conversations from {since} to {until}")
try:
conversations = get_all_webchat_conversations(auth, since, until)
if not conversations:
print("No Web Messaging conversations found in the specified time range.")
return
print(f"Found {len(conversations)} conversations.")
for conv in conversations:
conv_id = conv.get("conversationId")
user_attrs = extract_user_attributes(conv)
if user_attrs:
print(f"Conversation ID: {conv_id}")
print(f" User Attributes: {user_attrs}")
else:
print(f"Conversation ID: {conv_id} - No custom user attributes found.")
except requests.exceptions.HTTPError as e:
print(f"HTTP Error occurred: {e.response.status_code} - {e.response.text}")
except Exception as e:
print(f"An unexpected error occurred: {str(e)}")
if __name__ == "__main__":
main()
Common Errors & Debugging
Error: 403 Forbidden on Analytics Endpoint
Cause: The OAuth client lacks the analytics:conversation:read scope.
Fix:
- Go to Genesys Cloud Admin Console.
- Navigate to Platform > OAuth Applications.
- Find your client.
- Edit the scopes and add
analytics:conversation:read. - Save and generate a new token.
Error: Empty participants Array
Cause: The view parameter was set to summary or wrapup without including participants in the select array, or the select array was omitted entirely.
Fix: Ensure the request body includes "select": ["conversationId", "participants"]. The view must be details.
Error: Attributes Not Present in Response
Cause:
- The attributes were not set correctly in the Web Messaging SDK on the client side.
- The attributes were set after the conversation ended, which does not update historical analytics data.
- The query date range does not include the time when the attributes were set.
Fix:
- Verify the Web SDK call:
genesys.cloud.webchat.setAttributes({ "key": "value" }). - Ensure the conversation is still active or the query window covers the interaction time.
- Analytics data is eventual consistent. There may be a slight delay (seconds to minutes) before attributes appear in the API.
Error: 429 Too Many Requests
Cause: The Analytics API has strict rate limits, especially for details views.
Fix: Implement exponential backoff. The response header Retry-After indicates the number of seconds to wait.
import time
def make_request_with_retry(url, headers, json_body=None, max_retries=3):
for attempt in range(max_retries):
response = requests.post(url, headers=headers, json=json_body)
if response.status_code == 429:
retry_after = int(response.headers.get('Retry-After', 5))
print(f"Rate limited. Waiting {retry_after} seconds...")
time.sleep(retry_after)
else:
return response
return response