diff --git a/api/core/workflow/nodes/trigger_webhook/entities.py b/api/core/workflow/nodes/trigger_webhook/entities.py index ef456d757d..edb7338473 100644 --- a/api/core/workflow/nodes/trigger_webhook/entities.py +++ b/api/core/workflow/nodes/trigger_webhook/entities.py @@ -21,7 +21,7 @@ class ContentType(StrEnum): FORM_DATA = "multipart/form-data" FORM_URLENCODED = "application/x-www-form-urlencoded" TEXT = "text/plain" - FORM = "form" + BINARY = "application/octet-stream" class WebhookParameter(BaseModel): diff --git a/api/core/workflow/nodes/trigger_webhook/node.py b/api/core/workflow/nodes/trigger_webhook/node.py index b26da26390..4d4cb01ecc 100644 --- a/api/core/workflow/nodes/trigger_webhook/node.py +++ b/api/core/workflow/nodes/trigger_webhook/node.py @@ -108,6 +108,9 @@ class TriggerWebhookNode(BaseNode): # For text/plain, the entire body is a single string parameter outputs[param_name] = str(webhook_data.get("body", {}).get("raw", "")) continue + elif self._node_data.content_type == ContentType.BINARY: + outputs[param_name] = webhook_data.get("body", {}).get("raw", b"") + continue if param_type == "file": # Get File object (already processed by webhook controller) diff --git a/api/services/webhook_service.py b/api/services/webhook_service.py index 6da573830b..2cba935166 100644 --- a/api/services/webhook_service.py +++ b/api/services/webhook_service.py @@ -97,12 +97,45 @@ class WebhookService: # Handle file uploads if request.files: data["files"] = cls._process_file_uploads(request.files, webhook_trigger) - else: - # Raw text data + elif "application/octet-stream" in content_type: + # Binary data - process as file using ToolFileManager + try: + file_content = request.get_data() + if file_content: + tool_file_manager = ToolFileManager() + + # Create file using ToolFileManager + tool_file = tool_file_manager.create_file_by_raw( + user_id=webhook_trigger.created_by, + tenant_id=webhook_trigger.tenant_id, + conversation_id=None, + file_binary=file_content, + mimetype="application/octet-stream", + ) + + # Build File object + mapping = { + "tool_file_id": tool_file.id, + "transfer_method": FileTransferMethod.TOOL_FILE.value, + } + file_obj = file_factory.build_from_mapping( + mapping=mapping, + tenant_id=webhook_trigger.tenant_id, + ) + data["body"] = {"raw": file_obj.to_dict()} + else: + data["body"] = {"raw": None} + except Exception: + logger.exception("Failed to process octet-stream data") + data["body"] = {"raw": None} + elif "text/plain" in content_type: + # Text data - store as raw string try: data["body"] = {"raw": request.get_data(as_text=True)} except Exception: data["body"] = {"raw": ""} + else: + raise ValueError(f"Unsupported Content-Type: {content_type}") return data @@ -275,25 +308,7 @@ class WebhookService: return validation_result else: - # For other unsupported content types, only validate existence of required parameters - body_params = node_data.get("body", []) - for body_param in body_params: - param_name = body_param.get("name", "") - param_type = body_param.get("type", SegmentType.STRING) - is_required = body_param.get("required", False) - - if not is_required: - continue - - # Check if parameter exists - if param_type == SegmentType.FILE: - file_obj = webhook_data.get("files", {}).get(param_name) - if not file_obj: - return {"valid": False, "error": f"Required file parameter missing: {param_name}"} - else: - body_data = webhook_data.get("body", {}) - if param_name not in body_data: - return {"valid": False, "error": f"Required body parameter missing: {param_name}"} + raise ValueError(f"Unsupported Content-Type for validation: {configured_content_type}") return {"valid": True} diff --git a/web/app/components/workflow/nodes/trigger-webhook/components/parameter-table.tsx b/web/app/components/workflow/nodes/trigger-webhook/components/parameter-table.tsx index 2b57cb1283..da113183fb 100644 --- a/web/app/components/workflow/nodes/trigger-webhook/components/parameter-table.tsx +++ b/web/app/components/workflow/nodes/trigger-webhook/components/parameter-table.tsx @@ -78,17 +78,19 @@ const ParameterTable: FC = ({ const handleDataChange = (data: GenericTableRow[]) => { // For text/plain, enforce single text body semantics: keep only first non-empty row and force string type + // For application/octet-stream, enforce single file body semantics: keep only first non-empty row and force file type const isTextPlain = isRequestBody && (contentType || '').toLowerCase() === 'text/plain' + const isOctetStream = isRequestBody && (contentType || '').toLowerCase() === 'application/octet-stream' const normalized = data .filter(row => typeof row.key === 'string' && (row.key as string).trim() !== '') .map(row => ({ name: String(row.key), - type: isTextPlain ? VarType.string : normalizeParameterType((row.type as string)), + type: isTextPlain ? VarType.string : isOctetStream ? VarType.file : normalizeParameterType((row.type as string)), required: Boolean(row.required), })) - const newParams: WebhookParameter[] = isTextPlain + const newParams: WebhookParameter[] = (isTextPlain || isOctetStream) ? normalized.slice(0, 1) : normalized diff --git a/web/app/components/workflow/nodes/trigger-webhook/panel.tsx b/web/app/components/workflow/nodes/trigger-webhook/panel.tsx index d84d5602de..bbdadc69a3 100644 --- a/web/app/components/workflow/nodes/trigger-webhook/panel.tsx +++ b/web/app/components/workflow/nodes/trigger-webhook/panel.tsx @@ -32,7 +32,7 @@ const CONTENT_TYPES = [ { name: 'application/json', value: 'application/json' }, { name: 'application/x-www-form-urlencoded', value: 'application/x-www-form-urlencoded' }, { name: 'text/plain', value: 'text/plain' }, - { name: 'forms', value: 'forms' }, + { name: 'application/octet-stream', value: 'application/octet-stream' }, { name: 'multipart/form-data', value: 'multipart/form-data' }, ] diff --git a/web/app/components/workflow/nodes/trigger-webhook/utils/parameter-type-utils.ts b/web/app/components/workflow/nodes/trigger-webhook/utils/parameter-type-utils.ts index fabe38c40b..f71b2f96d5 100644 --- a/web/app/components/workflow/nodes/trigger-webhook/utils/parameter-type-utils.ts +++ b/web/app/components/workflow/nodes/trigger-webhook/utils/parameter-type-utils.ts @@ -42,9 +42,9 @@ const CONTENT_TYPE_CONFIGS = { supportedTypes: [VarType.string, VarType.number, VarType.boolean] as const, description: 'Form data supports basic types', }, - 'forms': { - supportedTypes: [VarType.string, VarType.number, VarType.boolean] as const, - description: 'Form data supports basic types', + 'application/octet-stream': { + supportedTypes: [VarType.file] as const, + description: 'octet-stream supports only binary data', }, 'multipart/form-data': { supportedTypes: [VarType.string, VarType.number, VarType.boolean, VarType.file] as const,