How to handle file uploads from customers in Web Messaging — accepted MIME types and size limits
What You Will Build
- This tutorial demonstrates how to programmatically upload files to Genesys Cloud CX Web Messaging channels, validating MIME types and enforcing size limits before transmission.
- You will use the Genesys Cloud Platform API v2 endpoints for asset upload and the
genesys-cloudSDK to manage the file transfer lifecycle. - The implementation covers Python (using
requestsand the official SDK) and JavaScript (usingfetchand the official SDK).
Prerequisites
- OAuth Client Type: A Genesys Cloud Application with
webmessagingandfiles:readscopes. - SDK Version: Genesys Cloud Python SDK
>= 120.0.0or JavaScript SDK>= 120.0.0. - Runtime: Python 3.8+ or Node.js 16+.
- Dependencies:
- Python:
pip install requests genesys-cloud-purecloud-platform-client - JavaScript:
npm install @genesys/cloud-purecloud-platform-client
- Python:
Authentication Setup
File uploads require a valid access token with specific scopes. The standard OAuth2 client credentials flow is sufficient for server-side integrations. For client-side Web Messaging implementations, you typically rely on the embedded widget’s internal authentication, but if you are building a custom backend proxy or a bot-driven upload handler, you must manage the token lifecycle.
The required scope for uploading files associated with a conversation is webmessaging combined with files:read or files:write depending on the specific asset operation.
Python Authentication
import os
from purecloudplatform.client import Configuration, ApiClient
def get_authenticated_api_client() -> ApiClient:
"""
Creates an authenticated Genesys Cloud API client using environment variables.
"""
config = Configuration(
environment=os.getenv("GENESYS_ENVIRONMENT", "us-east-1"),
client_id=os.getenv("GENESYS_CLIENT_ID"),
client_secret=os.getenv("GENESYS_CLIENT_SECRET")
)
api_client = ApiClient(configuration=config)
return api_client
api_client = get_authenticated_api_client()
JavaScript Authentication
const { Configuration, ApiClient } = require('@genesys/cloud-purecloud-platform-client');
function getAuthenticatedApiClient() {
const config = new Configuration({
environment: process.env.GENESYS_ENVIRONMENT || 'us-east-1',
clientId: process.env.GENESYS_CLIENT_ID,
clientSecret: process.env.GENESYS_CLIENT_SECRET
});
const apiClient = new ApiClient(config);
return apiClient;
}
const apiClient = getAuthenticatedApiClient();
Implementation
Step 1: Validate File Constraints Before Upload
Genesys Cloud enforces strict limits on file uploads within Web Messaging. Before sending data over the network, your application must validate the file against these constraints to prevent 400 Bad Request errors.
Constraints:
- Maximum File Size: 10 MB (10,485,760 bytes).
- Accepted MIME Types:
- Images:
image/jpeg,image/png,image/gif,image/bmp,image/webp - Documents:
application/pdf,text/plain - Archives:
application/zip - Note: Executables, scripts, and HTML files are blocked by default for security reasons.
- Images:
Python Validation Logic
import os
import mimetypes
ALLOWED_MIME_TYPES = {
'image/jpeg', 'image/png', 'image/gif', 'image/bmp', 'image/webp',
'application/pdf', 'text/plain', 'application/zip'
}
MAX_FILE_SIZE = 10 * 1024 * 1024 # 10 MB
def validate_file(file_path: str) -> tuple[bool, str]:
"""
Validates a file against Genesys Cloud Web Messaging constraints.
Returns (is_valid, error_message).
"""
if not os.path.exists(file_path):
return False, "File does not exist."
file_size = os.path.getsize(file_path)
if file_size > MAX_FILE_SIZE:
return False, f"File size {file_size} bytes exceeds limit of {MAX_FILE_SIZE} bytes."
# Guess MIME type from filename
mime_type, _ = mimetypes.guess_type(file_path)
if not mime_type:
return False, "Could not determine MIME type from filename."
if mime_type not in ALLOWED_MIME_TYPES:
return False, f"MIME type '{mime_type}' is not allowed. Allowed: {', '.join(ALLOWED_MIME_TYPES)}"
return True, "Valid"
JavaScript Validation Logic
const ALLOWED_MIME_TYPES = [
'image/jpeg', 'image/png', 'image/gif', 'image/bmp', 'image/webp',
'application/pdf', 'text/plain', 'application/zip'
];
const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10 MB
function validateFile(fileBuffer, fileName) {
if (!fileBuffer || fileBuffer.length === 0) {
return { isValid: false, error: "File buffer is empty." };
}
if (fileBuffer.length > MAX_FILE_SIZE) {
return { isValid: false, error: `File size exceeds limit of ${MAX_FILE_SIZE} bytes.` };
}
// Simple MIME detection based on extension
const extension = fileName.split('.').pop().toLowerCase();
let mimeType = '';
switch(extension) {
case 'jpg': case 'jpeg': mimeType = 'image/jpeg'; break;
case 'png': mimeType = 'image/png'; break;
case 'gif': mimeType = 'image/gif'; break;
case 'bmp': mimeType = 'image/bmp'; break;
case 'webp': mimeType = 'image/webp'; break;
case 'pdf': mimeType = 'application/pdf'; break;
case 'txt': mimeType = 'text/plain'; break;
case 'zip': mimeType = 'application/zip'; break;
default: mimeType = 'application/octet-stream';
}
if (!ALLOWED_MIME_TYPES.includes(mimeType)) {
return { isValid: false, error: `MIME type '${mimeType}' is not allowed.` };
}
return { isValid: true, error: null, mimeType };
}
Step 2: Upload the File Asset
In Genesys Cloud, you do not attach a file directly to a message payload in the same way you might with a standard HTTP multipart form post to a generic endpoint. Instead, you upload the file to the Genesys Cloud Assets service, which returns a fileId. You then reference this fileId in the Web Messaging conversation API.
The endpoint for uploading is POST /api/v2/files/assets.
Required Headers:
Content-Type: Must match the actual file MIME type (e.g.,application/pdf).Authorization: Bearer token.
Python File Upload
import requests
from purecloudplatform.client.rest import ApiException
def upload_file_asset(api_client: ApiClient, file_path: str, mime_type: str) -> str:
"""
Uploads a file to Genesys Cloud Assets and returns the file ID.
"""
# Get the base URL from the configuration
base_url = api_client.configuration.host
# The endpoint for asset upload
upload_url = f"{base_url}/api/v2/files/assets"
# Prepare headers
headers = {
'Content-Type': mime_type,
'Authorization': f"Bearer {api_client.configuration.access_token}"
}
try:
with open(file_path, 'rb') as f:
file_data = f.read()
# Send the file
response = requests.post(
upload_url,
data=file_data,
headers=headers
)
if response.status_code == 200:
result = response.json()
return result.get('id')
else:
raise Exception(f"Upload failed with status {response.status_code}: {response.text}")
except ApiException as e:
raise Exception(f"API Error: {e.body}")
except Exception as e:
raise e
JavaScript File Upload
const fs = require('fs');
async function uploadFileAsset(apiClient, filePath, mimeType) {
const baseUrl = apiClient.configuration.host;
const uploadUrl = `${baseUrl}/api/v2/files/assets`;
const fileBuffer = fs.readFileSync(filePath);
try {
const response = await fetch(uploadUrl, {
method: 'POST',
headers: {
'Content-Type': mimeType,
'Authorization': `Bearer ${apiClient.configuration.accessToken}`
},
body: fileBuffer
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Upload failed with status ${response.status}: ${errorText}`);
}
const result = await response.json();
return result.id;
} catch (error) {
throw new Error(`Failed to upload file: ${error.message}`);
}
}
Step 3: Send the File in a Web Messaging Conversation
Once you have the fileId, you must send a message in the Web Messaging conversation that references this asset. This is done via the POST /api/v2/conversations/messages endpoint.
You must ensure the conversationId is valid and active. The message body requires the type to be file and the attachments array to contain the fileId.
OAuth Scope: webmessaging
Python Send Message
from purecloudplatform.client import ConversationApi, MessageApi
from purecloudplatform.client.rest import ApiException
from purecloudplatform.client.model import ConversationMessagePost, ConversationMessageAttachment
def send_file_message(api_client: ApiClient, conversation_id: str, file_id: str, file_name: str) -> dict:
"""
Sends a file message in an existing Web Messaging conversation.
"""
message_api = MessageApi(api_client)
# Create the attachment object
attachment = ConversationMessageAttachment(
file_id=file_id,
file_name=file_name
)
# Create the message payload
message_payload = ConversationMessagePost(
type='file',
attachments=[attachment]
)
try:
# Send the message
response = message_api.post_conversation_message(
conversation_id=conversation_id,
body=message_payload
)
return response.to_dict()
except ApiException as e:
if e.status == 400:
raise Exception(f"Bad Request: Invalid conversation ID or file ID. Details: {e.body}")
elif e.status == 403:
raise Exception(f"Forbidden: Insufficient permissions. Details: {e.body}")
else:
raise Exception(f"API Error: {e.body}")
JavaScript Send Message
const { MessageApi } = require('@genesys/cloud-purecloud-platform-client');
async function sendFileMessage(apiClient, conversationId, fileId, fileName) {
const messageApi = new MessageApi(apiClient);
const messagePayload = {
type: 'file',
attachments: [
{
fileId: fileId,
fileName: fileName
}
]
};
try {
const response = await messageApi.postConversationMessage(conversationId, messagePayload);
return response.body;
} catch (error) {
if (error.status === 400) {
throw new Error(`Bad Request: Invalid conversation ID or file ID. Details: ${error.body}`);
} else if (error.status === 403) {
throw new Error(`Forbidden: Insufficient permissions. Details: ${error.body}`);
} else {
throw new Error(`API Error: ${error.body}`);
}
}
}
Complete Working Example
Below is a complete Python script that validates, uploads, and sends a file in a Web Messaging conversation.
import os
import sys
import mimetypes
from purecloudplatform.client import Configuration, ApiClient, MessageApi
import requests
# Configuration
GENESYS_ENVIRONMENT = os.getenv("GENESYS_ENVIRONMENT", "us-east-1")
GENESYS_CLIENT_ID = os.getenv("GENESYS_CLIENT_ID")
GENESYS_CLIENT_SECRET = os.getenv("GENESYS_CLIENT_SECRET")
CONVERSATION_ID = os.getenv("GENESYS_CONVERSATION_ID")
FILE_PATH = os.getenv("FILE_PATH", "sample.pdf")
ALLOWED_MIME_TYPES = {
'image/jpeg', 'image/png', 'image/gif', 'image/bmp', 'image/webp',
'application/pdf', 'text/plain', 'application/zip'
}
MAX_FILE_SIZE = 10 * 1024 * 1024 # 10 MB
def init_api_client():
config = Configuration(
environment=GENESYS_ENVIRONMENT,
client_id=GENESYS_CLIENT_ID,
client_secret=GENESYS_CLIENT_SECRET
)
return ApiClient(configuration=config)
def validate_file(file_path):
if not os.path.exists(file_path):
raise FileNotFoundError(f"File not found: {file_path}")
file_size = os.path.getsize(file_path)
if file_size > MAX_FILE_SIZE:
raise ValueError(f"File size {file_size} exceeds limit of {MAX_FILE_SIZE}")
mime_type, _ = mimetypes.guess_type(file_path)
if not mime_type or mime_type not in ALLOWED_MIME_TYPES:
raise ValueError(f"MIME type '{mime_type}' is not allowed.")
return mime_type
def upload_file(api_client, file_path, mime_type):
base_url = api_client.configuration.host
upload_url = f"{base_url}/api/v2/files/assets"
headers = {
'Content-Type': mime_type,
'Authorization': f"Bearer {api_client.configuration.access_token}"
}
with open(file_path, 'rb') as f:
file_data = f.read()
response = requests.post(upload_url, data=file_data, headers=headers)
if response.status_code != 200:
raise RuntimeError(f"Upload failed: {response.status_code} - {response.text}")
return response.json()['id']
def send_message(api_client, conversation_id, file_id, file_name):
message_api = MessageApi(api_client)
from purecloudplatform.client.model import ConversationMessagePost, ConversationMessageAttachment
attachment = ConversationMessageAttachment(
file_id=file_id,
file_name=file_name
)
message_payload = ConversationMessagePost(
type='file',
attachments=[attachment]
)
response = message_api.post_conversation_message(
conversation_id=conversation_id,
body=message_payload
)
return response
def main():
if not all([GENESYS_CLIENT_ID, GENESYS_CLIENT_SECRET, CONVERSATION_ID]):
print("Error: Missing environment variables.")
sys.exit(1)
api_client = init_api_client()
try:
# Step 1: Validate
print(f"Validating {FILE_PATH}...")
mime_type = validate_file(FILE_PATH)
print(f"MIME Type: {mime_type}")
# Step 2: Upload
print("Uploading file...")
file_id = upload_file(api_client, FILE_PATH, mime_type)
print(f"File ID: {file_id}")
# Step 3: Send
file_name = os.path.basename(FILE_PATH)
print(f"Sending file to conversation {CONVERSATION_ID}...")
send_message(api_client, CONVERSATION_ID, file_id, file_name)
print("Message sent successfully.")
except Exception as e:
print(f"Error: {e}")
sys.exit(1)
if __name__ == "__main__":
main()
Common Errors & Debugging
Error: 400 Bad Request - Invalid MIME Type
- Cause: The
Content-Typeheader in the upload request does not match the actual file content, or the MIME type is not in the allowed list. - Fix: Ensure the
mimetypes.guess_typeresult is correct. If the file has no extension, explicitly set the correct MIME type in the upload headers. Verify the MIME type is inALLOWED_MIME_TYPES.
Error: 400 Bad Request - File Too Large
- Cause: The file exceeds the 10 MB limit.
- Fix: Implement client-side validation before upload. Compress the file if possible (e.g., convert PNG to JPEG, zip multiple files).
Error: 403 Forbidden - Insufficient Scopes
- Cause: The OAuth token lacks the
webmessagingorfiles:writescope. - Fix: Regenerate the access token with the correct scopes. Ensure the Application in Genesys Cloud Admin has these scopes granted.
Error: 404 Not Found - Conversation Not Found
- Cause: The
conversationIdprovided is invalid or the conversation has ended/expired. - Fix: Verify the conversation ID is from an active Web Messaging session. Check the conversation status in the Genesys Cloud Admin console.
Error: 429 Too Many Requests
- Cause: Rate limiting due to excessive upload attempts.
- Fix: Implement exponential backoff retry logic. Do not retry immediately; wait 1-2 seconds before the first retry, doubling the wait time for subsequent retries.