Handling Customer File Attachments in Genesys Cloud Web Messaging
What You Will Build
- You will build a backend service that receives file uploads from the Genesys Cloud Web Messaging SDK, validates MIME types and file sizes, and stores them securely.
- This tutorial uses the Genesys Cloud Web Messaging REST API for file handling and the Genesys Cloud Python SDK for authentication and webhook configuration.
- The implementation is written in Python using FastAPI for the web server and the
requestslibrary for API interactions.
Prerequisites
- OAuth Client Type: Machine-to-Machine (M2M) Application.
- Required Scopes:
webchat:conversation:file:read(to retrieve file metadata)webchat:conversation:file:write(to upload files if your architecture requires server-side proxying)webchat:conversation:read(to inspect conversation context)
- SDK Version:
genesys-cloud-purecloud-platform-client>= 1.0.0 - Runtime: Python 3.9+
- Dependencies:
fastapiuvicornpython-multipartrequestsgenesys-cloud-purecloud-platform-client
Authentication Setup
Genesys Cloud APIs require OAuth 2.0 authentication. For server-side processing of file uploads, you will use a Machine-to-Machine (M2M) flow. This flow exchanges a client ID and client secret for an access token.
You must cache this token and handle expiration. The token typically expires after one hour.
import requests
import time
from typing import Optional
class GenesysAuth:
def __init__(self, client_id: str, client_secret: str, base_url: str = "https://api.mypurecloud.com"):
self.client_id = client_id
self.client_secret = client_secret
self.base_url = base_url
self._token: Optional[str] = None
self._token_expiry: float = 0
def get_access_token(self) -> str:
"""
Retrieves an OAuth access token. Caches the token until it expires.
"""
current_time = time.time()
# Return cached token if still valid (with 5-minute buffer)
if self._token and current_time < (self._token_expiry - 300):
return self._token
# Request new token
token_url = f"{self.base_url}/oauth/token"
auth_header = f"{self.client_id}:{self.client_secret}"
headers = {
"Authorization": f"Basic {auth_header}",
"Content-Type": "application/x-www-form-urlencoded"
}
data = {
"grant_type": "client_credentials"
}
try:
response = requests.post(token_url, headers=headers, data=data)
response.raise_for_status()
token_data = response.json()
self._token = token_data["access_token"]
# expires_in is in seconds
self._token_expiry = current_time + token_data["expires_in"]
return self._token
except requests.exceptions.RequestException as e:
raise RuntimeError(f"Failed to obtain OAuth token: {e}") from e
Implementation
Step 1: Define Allowed MIME Types and Size Limits
Before processing any file, you must define strict validation rules. Genesys Cloud Web Messaging supports specific MIME types. You should validate against this list on your server to prevent malicious uploads.
Commonly accepted types include:
image/jpeg,image/png,image/gifapplication/pdftext/plain
The maximum file size for Web Messaging attachments is generally 10MB per file, though this can vary by organization settings. We will enforce a 10MB limit in code.
from fastapi import FastAPI, UploadFile, File, HTTPException
from fastapi.responses import JSONResponse
import mimetypes
app = FastAPI()
# Configuration constants
MAX_FILE_SIZE = 10 * 1024 * 1024 # 10 MB in bytes
ALLOWED_MIMES = {
"image/jpeg",
"image/png",
"image/gif",
"application/pdf",
"text/plain"
}
def validate_file(file: UploadFile) -> None:
"""
Validates the uploaded file against MIME type and size constraints.
"""
# Check size
# Note: FastAPI buffers the file in memory or temp file depending on config.
# For large files, check content-length header if available, or read chunks.
file.file.seek(0, 2) # Seek to end
file_size = file.file.tell()
file.file.seek(0) # Reset pointer
if file_size > MAX_FILE_SIZE:
raise HTTPException(
status_code=413,
detail=f"File size {file_size} bytes exceeds maximum allowed size of {MAX_FILE_SIZE} bytes."
)
# Check MIME type
# FastAPI UploadFile.filename is user-controlled and unreliable.
# We rely on the content-type header if provided, or guess from content.
mime_type = file.content_type
if not mime_type:
# Fallback: guess from filename (less secure)
mime_type, _ = mimetypes.guess_type(file.filename)
if mime_type not in ALLOWED_MIMES:
raise HTTPException(
status_code=415,
detail=f"MIME type '{mime_type}' is not allowed. Allowed types: {', '.join(ALLOWED_MIMES)}"
)
Step 2: Configure the Web Messaging File Upload Endpoint
In Genesys Cloud Web Messaging, when a user uploads a file, the SDK does not send the file body directly to the conversation API in the initial message. Instead, it often uses a pre-signed URL or a specific upload endpoint if configured. However, for custom integrations or when handling files via a webhook that triggers on file upload events, you need to retrieve the file content.
The standard flow for custom file handling involves:
- The Web Messaging SDK uploads the file to Genesys Cloud’s storage.
- Genesys Cloud generates a file ID.
- Your backend retrieves the file using the File API.
Alternatively, if you are using a Web Messaging Integration with a custom File Upload URL, Genesys Cloud will POST the file directly to your endpoint. This tutorial covers the latter scenario, which is common for secure internal storage requirements.
You must configure your Web Messaging Deployment in Genesys Cloud to point to your custom endpoint.
# Import necessary components
from genesyscloud.platformclient.v2 import (
Configuration,
ConversationWebchatApi,
ConversationWebchatMessageApi
)
# Initialize Genesys API Client (using the Auth class from Step 1)
# Note: In production, initialize this once at startup, not per request.
@app.post("/webhook/file-upload")
async def handle_file_upload(
file: UploadFile = File(...),
conversation_id: str = None, # Passed as query param or header by Genesys
message_id: str = None # Passed as query param or header by Genesys
):
"""
Receives a file upload from Genesys Cloud Web Messaging.
"""
try:
# Step 1: Validate the file
validate_file(file)
# Step 2: Read the file content
contents = await file.read()
# Step 3: Process the file (e.g., save to S3, analyze with AI, etc.)
# For this example, we will log the metadata and return success.
print(f"Received file: {file.filename}, Size: {len(contents)} bytes, Type: {file.content_type}")
# Step 4: Update the Genesys Conversation to confirm receipt
# This is optional but good for UX (e.g., changing file status to 'processed')
await update_genesis_file_status(conversation_id, message_id, "processed")
return JSONResponse(
status_code=200,
content={"status": "success", "message": "File processed successfully"}
)
except HTTPException as e:
# Re-raise HTTP exceptions to return proper status codes to Genesys
raise e
except Exception as e:
# Log unexpected errors
print(f"Error processing file: {e}")
return JSONResponse(
status_code=500,
content={"status": "error", "message": "Internal server error"}
)
Step 3: Update Genesys Conversation File Status
After your backend processes the file, you may want to update the conversation record in Genesys Cloud to reflect the processing status. This requires calling the Genesys Cloud API.
You need to retrieve the file object associated with the message and update its status. The ConversationWebchatApi is used to manage web chat conversations.
async def update_genesis_file_status(conversation_id: str, message_id: str, status: str) -> None:
"""
Updates the status of a file attachment in a Genesys Cloud Web Chat conversation.
"""
if not conversation_id or not message_id:
# If IDs are not provided, we cannot update the status.
# In a real webhook, Genesys provides these in headers or query params.
return
# Initialize Configuration
config = Configuration()
config.host = "https://api.mypurecloud.com"
# Use the Auth class to get a valid token
auth_manager = GenesysAuth(client_id="YOUR_CLIENT_ID", client_secret="YOUR_CLIENT_SECRET")
access_token = auth_manager.get_access_token()
config.access_token = access_token
# Create API instance
webchat_api = ConversationWebchatApi(config)
try:
# Note: Genesys Cloud API for webchat files is specific.
# There is no direct "update file status" endpoint in the public API for webchat files
# in the same way as voice/media files.
# Instead, we often update the conversation activity or send a system message.
# Alternative: Send a system message confirming file receipt
message_api = ConversationWebchatMessageApi(config)
# Construct the message
from genesyscloud.platformclient.v2.models.conversation_webchat_message import ConversationWebchatMessage
body = ConversationWebchatMessage(
conversationId=conversation_id,
type="system",
text=f"File '{message_id}' has been processed successfully."
)
# Post the message
message_api.post_conversation_webchat_message(body=body)
except Exception as e:
# Log error but do not fail the file upload receipt
print(f"Failed to update Genesys conversation status: {e}")
Complete Working Example
This is the full, copy-pasteable Python script. Save it as main.py. You will need to install dependencies via pip install fastapi uvicorn python-multipart requests genesys-cloud-purecloud-platform-client.
import time
import mimetypes
from typing import Optional
import requests
import uvicorn
from fastapi import FastAPI, UploadFile, File, HTTPException, Query
from fastapi.responses import JSONResponse
from genesyscloud.platformclient.v2 import Configuration, ConversationWebchatMessageApi
from genesyscloud.platformclient.v2.models.conversation_webchat_message import ConversationWebchatMessage
# --- Configuration ---
GENESYS_CLIENT_ID = "YOUR_CLIENT_ID"
GENESYS_CLIENT_SECRET = "YOUR_CLIENT_SECRET"
GENESYS_BASE_URL = "https://api.mypurecloud.com"
MAX_FILE_SIZE = 10 * 1024 * 1024 # 10 MB
ALLOWED_MIMES = {
"image/jpeg",
"image/png",
"image/gif",
"application/pdf",
"text/plain"
}
app = FastAPI(title="Genesys Cloud Web Messaging File Handler")
# --- Authentication Module ---
class GenesysAuth:
def __init__(self, client_id: str, client_secret: str, base_url: str):
self.client_id = client_id
self.client_secret = client_secret
self.base_url = base_url
self._token: Optional[str] = None
self._token_expiry: float = 0
def get_access_token(self) -> str:
current_time = time.time()
if self._token and current_time < (self._token_expiry - 300):
return self._token
token_url = f"{self.base_url}/oauth/token"
auth_header = f"{self.client_id}:{self.client_secret}"
headers = {
"Authorization": f"Basic {auth_header}",
"Content-Type": "application/x-www-form-urlencoded"
}
data = {"grant_type": "client_credentials"}
try:
response = requests.post(token_url, headers=headers, data=data)
response.raise_for_status()
token_data = response.json()
self._token = token_data["access_token"]
self._token_expiry = current_time + token_data["expires_in"]
return self._token
except requests.exceptions.RequestException as e:
raise RuntimeError(f"Failed to obtain OAuth token: {e}") from e
# Global Auth Instance
auth_manager = GenesysAuth(GENESYS_CLIENT_ID, GENESYS_CLIENT_SECRET, GENESYS_BASE_URL)
# --- Validation Logic ---
def validate_file(file: UploadFile) -> None:
file.file.seek(0, 2)
file_size = file.file.tell()
file.file.seek(0)
if file_size > MAX_FILE_SIZE:
raise HTTPException(
status_code=413,
detail=f"File size {file_size} bytes exceeds maximum allowed size of {MAX_FILE_SIZE} bytes."
)
mime_type = file.content_type
if not mime_type:
mime_type, _ = mimetypes.guess_type(file.filename)
if mime_type not in ALLOWED_MIMES:
raise HTTPException(
status_code=415,
detail=f"MIME type '{mime_type}' is not allowed. Allowed types: {', '.join(ALLOWED_MIMES)}"
)
# --- API Endpoints ---
@app.post("/webhook/file-upload")
async def handle_file_upload(
file: UploadFile = File(...),
conversation_id: str = Query(None),
message_id: str = Query(None)
):
"""
Endpoint to receive file uploads from Genesys Cloud Web Messaging.
"""
try:
validate_file(file)
contents = await file.read()
# Placeholder for actual file processing (e.g., save to DB/S3)
print(f"Processing file: {file.filename} ({len(contents)} bytes)")
# Confirm receipt in Genesys Conversation
if conversation_id:
await send_system_message(conversation_id, f"File '{file.filename}' received.")
return JSONResponse(
status_code=200,
content={"status": "success"}
)
except HTTPException as e:
raise e
except Exception as e:
return JSONResponse(
status_code=500,
content={"status": "error", "message": str(e)}
)
async def send_system_message(conversation_id: str, text: str) -> None:
"""
Sends a system message to the Genesys Cloud Web Chat conversation.
"""
try:
config = Configuration()
config.host = GENESYS_BASE_URL
config.access_token = auth_manager.get_access_token()
message_api = ConversationWebchatMessageApi(config)
body = ConversationWebchatMessage(
conversationId=conversation_id,
type="system",
text=text
)
message_api.post_conversation_webchat_message(body=body)
except Exception as e:
print(f"Error sending system message: {e}")
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)
Common Errors & Debugging
Error: 413 Payload Too Large
- Cause: The uploaded file exceeds the
MAX_FILE_SIZEdefined in your code or the default limit of your web server (e.g., Nginx, Apache). - Fix: Increase
MAX_FILE_SIZEin the code if your business logic allows larger files. Ensure your reverse proxy (if used) also allows larger bodies. For Nginx, addclient_max_body_size 10M;to your server block.
Error: 415 Unsupported Media Type
- Cause: The file’s MIME type is not in
ALLOWED_MIMES. This often happens with images that have incorrect extensions or unknown formats like.webpif not explicitly allowed. - Fix: Add the required MIME type to the
ALLOWED_MIMESset. Verify that the client is sending the correctContent-Typeheader.
Error: 401 Unauthorized
- Cause: The OAuth token is expired or invalid.
- Fix: Ensure your
GenesysAuthclass is correctly caching and refreshing tokens. Check that the M2M application in Genesys Cloud has thewebchat:conversation:readscope assigned.
Error: 429 Too Many Requests
- Cause: You are hitting Genesys Cloud API rate limits while sending system messages or checking status.
- Fix: Implement exponential backoff in your
requestscalls to Genesys Cloud. Cache tokens aggressively to avoid unnecessary token refresh calls.