Access Web Messaging Participant Attributes in Architect Inbound Message Flows

Access Web Messaging Participant Attributes in Architect Inbound Message Flows

What You Will Build

  • A Python script that injects custom attributes into a Genesys Cloud Web Messaging session using the Engagement API.
  • An explanation of how to reference those attributes inside an Architect Inbound Message flow using the data object.
  • A verification method using the Conversations API to confirm attribute persistence.

Prerequisites

  • OAuth Client Type: Service Account or Public Client (for Web Messaging SDK).
  • Required Scopes:
    • webchat:write (to send messages and set attributes via Engagement API).
    • conversation:read (to verify attributes via Conversations API).
    • routing:analytics:query (optional, for historical data).
  • SDK Version: genesys-cloud-purecloud-sdk v2.20.0+ (Python).
  • Runtime: Python 3.9+.
  • Dependencies:
    pip install genesys-cloud-purecloud-sdk requests
    

Authentication Setup

Genesys Cloud Web Messaging uses a two-layer authentication model. The end-user browser authenticates with the Engagement API using a temporary token, while your backend integration (if any) uses standard OAuth 2.0. For this tutorial, we will simulate the client-side attribute setting using a Python script that mimics the Web Messaging SDK’s behavior via the Engagement API.

If you are building a backend service to verify these attributes, you need a standard OAuth token.

import os
import time
import requests
from genesyscloud import PureCloudPlatformClientV2
from genesyscloud.platformclient import platform_client

# Configuration
CLIENT_ID = os.getenv("GENESYS_CLIENT_ID")
CLIENT_SECRET = os.getenv("GENESYS_CLIENT_SECRET")
ENVIRONMENT = os.getenv("GENESYS_ENVIRONMENT", "us-east-1")

def get_oauth_token():
    """
    Retrieves an OAuth token for backend API calls.
    Required for verifying attributes via Conversations API.
    """
    auth_url = f"https://api.{ENVIRONMENT}.mypurecloud.com/oauth/token"
    payload = {
        "grant_type": "client_credentials",
        "client_id": CLIENT_ID,
        "client_secret": CLIENT_SECRET
    }
    
    response = requests.post(auth_url, data=payload)
    if response.status_code != 200:
        raise Exception(f"Failed to obtain OAuth token: {response.text}")
    
    return response.json()["access_token"]

# Initialize SDK for backend verification
def init_sdk():
    config = PureCloudPlatformClientV2.Configuration()
    config.client_id = CLIENT_ID
    config.client_secret = CLIENT_SECRET
    config.environment = PureCloudPlatformClientV2.Environment(ENVIRONMENT)
    return PureCloudPlatformClientV2.PureCloudPlatformClientV2(config)

Implementation

Step 1: Inject Custom Attributes via the Engagement API

The Web Messaging SDK (genesys-cloud-messaging-sdk) exposes methods to set user attributes. Under the hood, this calls the Engagement API. When you set an attribute, it is scoped to the specific conversation and participant.

In Architect, these attributes are accessible within the Inbound Message Flow context. They are not stored in the user’s profile unless explicitly saved to a profile via a separate action. They exist in the transient session data.

To simulate a user setting an attribute (e.g., order_id or preferred_language) before or during a chat, we use the Engagement API endpoint for sending messages. The Web Messaging SDK encapsulates this, but for programmatic testing or backend injection, we can call the API directly.

Note: The Engagement API requires a session token (access_token) obtained from https://api.{env}.mypurecloud.com/engagement/oauth/token. This token is short-lived and tied to a specific session ID.

import json

def get_engagement_token(session_id: str, external_id: str) -> str:
    """
    Obtains a short-lived engagement token for a specific Web Messaging session.
    This mimics the initial handshake done by the Web Messaging SDK.
    """
    auth_url = f"https://api.{ENVIRONMENT}.mypurecloud.com/engagement/oauth/token"
    
    payload = {
        "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
        "session_id": session_id,
        "external_id": external_id,
        "scope": "webchat:write"
    }
    
    # In a real browser scenario, this happens automatically.
    # For backend simulation, you must have a valid session_id.
    # This example assumes you already have a session_id from a previous login.
    
    response = requests.post(auth_url, json=payload)
    if response.status_code != 200:
        raise Exception(f"Failed to get engagement token: {response.text}")
    
    return response.json()["access_token"]

def set_web_messaging_attribute(session_id: str, external_id: str, key: str, value: str):
    """
    Sends a 'message' event that includes custom data.
    In Web Messaging, attributes are often set via the 'user' object in the session
    or by sending a specific 'set-user-attribute' event if supported by the SDK version.
    
    However, the most reliable way to pass data into Architect from Web Messaging
    is through the 'custom' field in the initial message or via the SDK's setUserAttributes method.
    
    This function simulates the SDK's internal call to update the session data.
    """
    engagement_token = get_engagement_token(session_id, external_id)
    
    # The Engagement API endpoint for Web Messaging
    api_url = f"https://api.{ENVIRONMENT}.mypurecloud.com/engagement/webchat/v1/sessions/{session_id}/messages"
    
    headers = {
        "Authorization": f"Bearer {engagement_token}",
        "Content-Type": "application/json",
        "X-Genesys-Session-Id": session_id
    }
    
    # Payload structure for setting attributes via a message event
    # The Web Messaging SDK translates setUserAttributes into this format
    payload = {
        "type": "message",
        "content": {
            "type": "text",
            "text": "" # Empty text, we are only setting data
        },
        "user": {
            "id": external_id,
            "name": "Test User",
            "attributes": {
                key: value
            }
        }
    }
    
    response = requests.post(api_url, json=payload, headers=headers)
    
    if response.status_code not in [200, 201]:
        raise Exception(f"Failed to set attribute: {response.status_code} - {response.text}")
        
    return response.json()

Step 2: Configure the Architect Inbound Message Flow

Once the attribute is set by the client (browser or backend simulation), it becomes available in the Architect Inbound Message Flow.

  1. Open Genesys Cloud Architect.
  2. Create or edit an Inbound Message Flow.
  3. Drag a Set Variable node onto the canvas.
  4. In the Expression field, use the following syntax to access the attribute:
data.user.attributes["order_id"]

Explanation of the Data Path:

  • data: The root object containing all context for the current interaction.
  • user: The object representing the end-user (participant).
  • attributes: A map (dictionary) of key-value pairs set by the client SDK or the Engagement API.
  • ["order_id"]: The specific key you set in Step 1.

Important: If the attribute does not exist, data.user.attributes["order_id"] will evaluate to null. You must handle this case in your flow to avoid errors in downstream nodes.

Step 3: Verify Attribute Persistence via Conversations API

To confirm that the attribute was successfully attached to the conversation and is visible to backend systems, you can query the Conversations API. This is critical for debugging issues where Architect sees the data but your CRM integration does not.

def verify_conversation_attributes(conversation_id: str):
    """
    Retrieves the conversation details to verify participant attributes.
    """
    token = get_oauth_token()
    api_client = init_sdk()
    
    # Use the Conversations API to get participant details
    # Note: The SDK method get_conversations_conversation_participant requires the conversation ID and participant ID
    # We first need to list participants to get the participant ID
    
    try:
        # Get all participants in the conversation
        api_response = api_client.conversations_api.get_conversations_conversation_participants(
            conversation_id=conversation_id,
            expand=["attributes"] # Crucial: expand attributes to see custom data
        )
        
        if not api_response.entities:
            print("No participants found.")
            return

        for participant in api_response.entities:
            if participant.type == "user": # End-user participant
                print(f"Participant ID: {participant.id}")
                print(f"Type: {participant.type}")
                
                # Check for custom attributes
                if participant.attributes:
                    print("Custom Attributes:")
                    for key, value in participant.attributes.items():
                        print(f"  {key}: {value}")
                else:
                    print("No custom attributes found.")
                    
    except Exception as e:
        print(f"Error retrieving conversation: {e}")

Complete Working Example

This script simulates the full lifecycle: obtaining a backend token, simulating a Web Messaging session (assuming a pre-existing session ID for demonstration), setting an attribute, and verifying it.

Prerequisite: You must have an active Web Messaging session ID. In a real application, this is generated by the Web Messaging SDK when the widget loads. For this tutorial, you can generate one by initiating a chat in the Genesys Cloud Admin Console or using the Web Messaging SDK in a browser and inspecting the network tab for the session_id.

import os
import time
import requests
from genesyscloud import PureCloudPlatformClientV2
from genesyscloud.platformclient import platform_client

# --- Configuration ---
CLIENT_ID = os.getenv("GENESYS_CLIENT_ID")
CLIENT_SECRET = os.getenv("GENESYS_CLIENT_SECRET")
ENVIRONMENT = os.getenv("GENESYS_ENVIRONMENT", "us-east-1")
SESSION_ID = os.getenv("GENESYS_SESSION_ID") # Replace with an active session ID
EXTERNAL_ID = "test-user-123"

# --- Helper Functions ---

def get_oauth_token():
    auth_url = f"https://api.{ENVIRONMENT}.mypurecloud.com/oauth/token"
    payload = {
        "grant_type": "client_credentials",
        "client_id": CLIENT_ID,
        "client_secret": CLIENT_SECRET
    }
    response = requests.post(auth_url, data=payload)
    if response.status_code != 200:
        raise Exception(f"OAuth Failed: {response.text}")
    return response.json()["access_token"]

def init_sdk():
    config = PureCloudPlatformClientV2.Configuration()
    config.client_id = CLIENT_ID
    config.client_secret = CLIENT_SECRET
    config.environment = PureCloudPlatformClientV2.Environment(ENVIRONMENT)
    return PureCloudPlatformClientV2.PureCloudPlatformClientV2(config)

def get_engagement_token(session_id: str, external_id: str) -> str:
    auth_url = f"https://api.{ENVIRONMENT}.mypurecloud.com/engagement/oauth/token"
    payload = {
        "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
        "session_id": session_id,
        "external_id": external_id,
        "scope": "webchat:write"
    }
    response = requests.post(auth_url, json=payload)
    if response.status_code != 200:
        raise Exception(f"Engagement Token Failed: {response.text}")
    return response.json()["access_token"]

def set_web_messaging_attribute(session_id: str, external_id: str, key: str, value: str):
    engagement_token = get_engagement_token(session_id, external_id)
    api_url = f"https://api.{ENVIRONMENT}.mypurecloud.com/engagement/webchat/v1/sessions/{session_id}/messages"
    
    headers = {
        "Authorization": f"Bearer {engagement_token}",
        "Content-Type": "application/json",
        "X-Genesys-Session-Id": session_id
    }
    
    payload = {
        "type": "message",
        "content": {
            "type": "text",
            "text": "" 
        },
        "user": {
            "id": external_id,
            "name": "Test User",
            "attributes": {
                key: value
            }
        }
    }
    
    response = requests.post(api_url, json=payload, headers=headers)
    if response.status_code not in [200, 201]:
        raise Exception(f"Set Attribute Failed: {response.status_code} - {response.text}")
    print("Attribute set successfully via Engagement API.")

def verify_conversation_attributes(conversation_id: str):
    api_client = init_sdk()
    try:
        api_response = api_client.conversations_api.get_conversations_conversation_participants(
            conversation_id=conversation_id,
            expand=["attributes"]
        )
        
        for participant in api_response.entities:
            if participant.type == "user":
                print(f"--- Participant: {participant.id} ---")
                if participant.attributes:
                    for k, v in participant.attributes.items():
                        print(f"  {k}: {v}")
                else:
                    print("  No attributes.")
    except Exception as e:
        print(f"Error: {e}")

# --- Main Execution ---

if __name__ == "__main__":
    if not SESSION_ID:
        print("ERROR: Set GENESYS_SESSION_ID environment variable.")
        exit(1)

    # 1. Set a custom attribute
    CUSTOM_KEY = "order_number"
    CUSTOM_VALUE = "ORD-998877"
    
    print(f"Setting attribute '{CUSTOM_KEY}' to '{CUSTOM_VALUE}'...")
    set_web_messaging_attribute(SESSION_ID, EXTERNAL_ID, CUSTOM_KEY, CUSTOM_VALUE)
    
    # 2. Wait for replication (Genesys Cloud is eventually consistent)
    print("Waiting for data replication...")
    time.sleep(5)
    
    # 3. Verify via Conversations API
    # Note: You need the Conversation ID associated with the Session ID.
    # In a real app, you get this from the SDK's onConversationCreated event.
    # For this demo, we assume you know the Conversation ID or can map it.
    # Placeholder: Replace with actual Conversation ID
    CONVERSATION_ID = os.getenv("GENESYS_CONVERSATION_ID")
    
    if CONVERSATION_ID:
        print(f"Verifying attributes in Conversation ID: {CONVERSATION_ID}")
        verify_conversation_attributes(CONVERSATION_ID)
    else:
        print("Note: Set GENESYS_CONVERSATION_ID to verify via Conversations API.")
        print("Architect Flow Reference: data.user.attributes['order_number']")

Common Errors & Debugging

Error: 401 Unauthorized on Engagement API

  • Cause: The engagement token is expired or invalid. Engagement tokens are short-lived (typically 15-30 minutes).
  • Fix: Regenerate the token using get_engagement_token. Ensure the session_id and external_id match the original session creation.

Error: Attribute Not Visible in Architect

  • Cause: The attribute was set after the conversation was already routed to an agent, or the flow does not re-evaluate the data object.
  • Fix:
    1. Ensure the attribute is set before the message is sent to the agent.
    2. In Architect, use a Set Variable node at the start of the flow to capture data.user.attributes["key"].
    3. If the attribute changes during the conversation, use a Re-evaluate node or ensure the flow is designed to handle dynamic data updates.

Error: 429 Too Many Requests

  • Cause: Rate limiting on the Engagement API.
  • Fix: Implement exponential backoff. The Engagement API has strict rate limits for message sending.

Error: data.user.attributes is null in Architect

  • Cause: The attribute key does not exist.
  • Fix: Use a conditional check in Architect:
    data.user.attributes["order_number"] != null
    
    Or use the coalesce function if available in your expression context:
    coalesce(data.user.attributes["order_number"], "UNKNOWN")
    

Official References