File Upload Events
This page documents the WebSocket endpoint used to monitor and control file upload workflows for Object Records in Catalyst.
The endpoint supports bidirectional communication. After the WebSocket connection is established, the client can request presigned upload URLs, notify the backend when uploads are complete, and receive progress and monitoring updates as the backend processes uploaded files.
WARNING
This section documents technical and service-level WebSocket APIs. It should be used as implementation reference material and not treated as end-user documentation.
Endpoint overview
This page covers the following endpoint:
GET /ws/object-records/uploads/<upload_id>/
GET /ws/object-records/uploads/<upload_id>/
Connects the client to an upload process identified by upload_id.
Permissions
Requires authentication.
Authentication is based on the uuid query string parameter used when establishing the WebSocket connection.
Request
Upgrade request example
Request Method: GET
Connection: Upgrade
Upgrade: websocketConnection behaviour
When the connection is established, communication is bidirectional:
- the WebSocket server sends status, progress, and result messages to the client
- the client sends JSON messages to request upload actions or to confirm upload completion
Supported client messages
1. Request a presigned upload URL
To initiate an upload, the client sends a JSON message with the following fields:
| Key | Type | Notes |
|---|---|---|
| type | enum | request_presigned_url |
| file_name | string | Original file name |
| file_size | integer | File size in bytes |
| file_type | string | File content type, for example application/zip |
2. Notify the backend that a single-part upload is complete
After the client uploads a ZIP file using a previously returned single presigned URL, it must notify the backend by sending:
| Key | Type | Notes |
|---|---|---|
| type | enum | upload_complete |
| upload_id | uuid | Upload session identifier returned earlier |
| s3_key | string | Full S3 key where the file was uploaded |
3. Notify the backend that a multipart upload is complete
After the client uploads a ZIP file using multipart presigned URLs, it must notify the backend by sending:
| Key | Type | Notes |
|---|---|---|
| type | enum | complete_multipart_upload |
| parts | array | Array of uploaded part descriptors |
parts array
| Key | Type | Notes |
|---|---|---|
| part_number | int | Part number, starting from 1 |
| e_tag | string | ETag returned by S3 for the uploaded part |
Server responses
Connection established
When the WebSocket connection is successfully opened, the server returns:
- WebSocket upgrade status:
101 Switching Protocols
{
"type": "connection_established",
"message": "WebSocket connected successfully"
}Presigned URL response
When the client requests a presigned upload URL, the server responds with a presigned_url_ready message.
Example response
{
"type": "presigned_url_ready",
"message": {
"upload_id": "3c2b9b89-2f2a-497c-aa0a-1be5a8fd93c8",
"temp_upload_id": "temp_1749545666136_csyydasgl",
"presigned_url": "https://s3.amazonaws.com/advanced-uploader-bucket/uploads/1/0e6a1a81.zip?...",
"fields": {},
"s3_key": "some.domain.com/upload/1/0e6a1a81.zip",
"is_multipart": false,
"part_urls": [],
"s3_upload_id": null,
"chunk_size": null,
"total_parts": 1
}
}Response fields
| Key | Type | Notes |
|---|---|---|
| type | enum | Event type |
| message | object | Upload session data |
message
| Key | Type | Notes |
|---|---|---|
| upload_id | uuid | Internal backend ID for this upload session |
| temp_upload_id | string | Temporary client-side ID used to map the response to the originating request |
| presigned_url | string | Full S3 PUT presigned URL. For multipart uploads this is null |
| fields | object | Empty for PUT-based uploads |
| s3_key | string | Target key in the S3 bucket |
| is_multipart | boolean | Indicates whether multipart upload is required |
| part_urls | array | Used only for multipart uploads |
| s3_upload_id | string | Multipart upload identifier used by S3 |
| chunk_size | integer | Chunk size for multipart uploads |
| total_parts | integer | Total number of parts expected |
message.part_urls for multipart uploads
When is_multipart is true, the part_urls array contains presigned URLs for each upload part.
| Key | Type | Notes |
|---|---|---|
| url | string | Presigned URL for this part |
| part | int | Part number, starting from 1 |
Monitoring updates
After the file has been uploaded, scanned, and unpacked, the backend sends monitoring updates describing the processing state.
Monitoring update structure
| Key | Type | Notes |
|---|---|---|
| type | enum | monitoring_update |
| message | object | Monitoring data |
message
| Key | Type | Notes |
|---|---|---|
| attempt | int | Attempt number |
| timestamp | string | ISO 8601 timestamp |
There may also be additional fields depending on whether the message represents a success state, an error, or an intermediate monitoring attempt.
Example: success monitoring update
{
"type": "monitoring_update",
"message": {
"step_name": "extracting_file",
"av-status": "clean",
"extracted_files": 100,
"message": "Done Extracted 100 files",
"status": "success",
"attempt": 10,
"timestamp": "2025-07-10T14:30:45.123456"
}
}Progress updates
The backend also emits progress messages during different processing phases.
Extracting and saving files
During this phase, the backend emits periodic progress updates such as:
{
"type": "progress_update",
"message": {
"step_name": "saving_files",
"upload_id": "99b90581-4e29-4482-b25a-eea4d6dbe78f",
"is_complete": false,
"total_count": 2,
"progress_count": 1,
"timestamp": "2025-07-17T06:18:51.325683+00:00"
}
}Creating records
After antivirus scanning and file saving are completed, the backend begins creating Object Records and adding them to the target Object Class.
During this phase, the backend emits periodic progress messages such as:
{
"type": "progress_update",
"message": {
"step_name": "creating_records",
"upload_id": "3c2b9b89-2f2a-497c-aa0a-1be5a8fd93c8",
"is_complete": false,
"total_count": 120,
"progress_count": 23,
"timestamp": "2025-07-17T06:18:51.325683+00:00"
}
}Progress update fields
| Key | Type | Notes |
|---|---|---|
| type | enum | progress_update |
| message | object | Progress information |
message
| Key | Type | Notes |
|---|---|---|
| step_name | string | Processing step name |
| upload_id | uuid | Upload session ID |
| is_complete | boolean | Indicates whether the step is complete |
| total_count | integer | Total number of items expected |
| progress_count | integer | Number of processed items |
| timestamp | string | ISO 8601 timestamp |
Errors
| Error | Response code / close status | Message |
|---|---|---|
| Incorrect WebSocket URL path | Close: 1000 Normal Closure | "type": "error", "error_code": "ERR_INCORRECT_URL", "detail":"Incorrect WebSocket URL." |
upload_id is invalid, already used, or expired | Close: 1000 Normal Closure | "type": "error", "error_code": "ERR_INVALID_UUID", "detail":"A valid UUID is required." |
| Upload ID does not match or the requesting user is not the transaction creator | Close: 1000 Normal Closure | "type": "error", "error_code": "ERR_DOES_NOT_EXIST", "detail":"Not found." |
File type is not application/zip | Close: 1000 Normal Closure | "type": "error", "error_code": "ERR_UNSUPPORTED_FILE_TYPE", "detail":"File extension “{file_type}” is not allowed. Allowed extensions are: {allowed_types}." |
| Missing or malformed payload | Close: 1000 Normal Closure | "type": "error", "error_code": "ERR_INVALID_PAYLOAD", "detail": "Required fields: type, temp_upload_id, file_name, file_size, file_type, object_class, class_field" |
| File is too large | Close: 1000 Normal Closure | "type": "error", "error_code": "ERR_FILE_TOO_LARGE", "detail":"Max file size is {limit} GB." |
| File is empty or has size less than or equal to 0 bytes | Close: 1000 Normal Closure | "type": "error", "error_code": "ERR_FILE_EMPTY", "detail":"File size must be larger than 0." |
file_name, file_size, or file_type is null | Close: 1000 Normal Closure | "type": "error", "error_code": "ERR_NULL_FIELD", "detail":"Field “{field}” may not be null." |
file_name or file_type is empty | Close: 1000 Normal Closure | "type": "error", "error_code": "ERR_NULL_FIELD", "detail":"Field “{field}” may not be empty." |
| Unexpected server error during URL generation | Close: 1000 Normal Closure | "type": "error", "error_code": "ERR_SERVER_ERROR", "detail": "Upload could not be initialized. Please try again later." |
| Unexpected S3 error during URL generation | Close: 1000 Normal Closure | "type": "error", "error_code": "ERR_GENERATION_ERROR", "detail": "Failed to generate presigned URL" |
Invalid type value in request | Close: 1000 Normal Closure | "type": "error", "error_code": "ERR_INVALID_TYPE", "detail": "Type “{type}” is not allowed. Allowed types are: [{allowed_types}]." |
| Request message is JSON but not an expected dictionary/object | Close: 1000 Normal Closure | "type": "error", "error_code": "ERR_INVALID_TYPE", "detail": "Invalid message type. Expected a JSON object." |
| Multipart upload finalization failed in S3 | Close: 1000 Normal Closure | "type": "error", "error_code": "ERR_S3_FINALIZE_FAILED", "detail": "Could not finalize multipart upload. Please try again later." |
| Upload marked as complete before the file is available in S3 | Close: 1000 Normal Closure | "type": "error", "error_code": "ERR_FILE_INCOMPLETE", "detail": "Upload has not yet completed or file is not available." |
| Error during the handshake | 500 Internal Server Error |
Notes
WebSocket standard close/error definitions are described in RFC 6455.