diff --git a/Jenkinsfile b/Jenkinsfile
index ac7739f9f116a11e1108240bd5d0154242f8a39f..e57f8378b4b14030ce9bec891496876a98e124ea 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -13,7 +13,7 @@ pipeline {
         SH_SUCCESS_STATUS_CODE = 0
 
         VORGANG_MANAGER_INTERFACE_VERSION="2.0.0"
-        FORMCYCLE_INTERFACE_VERSION="2.0.0"//readFormCycleInterfaceVersion()
+        FORMCYCLE_INTERFACE_VERSION="2.2.0-SNAPSHOT"//readFormCycleInterfaceVersion()
     }
 
     options {
@@ -37,7 +37,7 @@ pipeline {
 
             steps {
                 script {
-                    echo "Using FormCacle Interface Version: ${FORMCYCLE_INTERFACE_VERSION}"
+                    echo "Using FORMCYCLE Interface Version: ${FORMCYCLE_INTERFACE_VERSION}"
 
                     cloneVorgangManagerRepo()
                 }
@@ -57,7 +57,7 @@ pipeline {
             steps {
                 configFileProvider([configFile(fileId: 'maven-settings', variable: 'MAVEN_SETTINGS')]) {
                     sh 'mvn --version'
-                    sh 'mvn -f intelliform-adapter/formcycle-adapter/formcycle-adapter-interface -s $MAVEN_SETTINGS -Djava.version=11 install'
+                    sh 'mvn -f eingang-manager/formcycle-adapter/formcycle-adapter-interface -s $MAVEN_SETTINGS -Djava.version=11 install'
                 }
             }
         }
@@ -147,7 +147,9 @@ Void cloneVorgangManagerRepo() {
         
     }
     dir("vorgang-manager") {
-        sh 'git checkout tags/${VORGANG_MANAGER_INTERFACE_VERSION} -b branch'
+        if ( !isSnapshotVersion([VORGANG_MANAGER_INTERFACE_VERSION]) ) {
+            sh 'git checkout tags/${VORGANG_MANAGER_INTERFACE_VERSION} -b branch'
+        }
     }
     configureGit("vorgang-manager")
 }
@@ -155,13 +157,15 @@ Void cloneVorgangManagerRepo() {
 Void cloneEingangAdapterRepo() {
     withCredentials([usernamePassword(credentialsId: 'jenkins-gitea-access-token', passwordVariable: 'TOKEN', usernameVariable: 'USER')]) {
         
-            sh 'git clone https://${USER}:${TOKEN}@git.ozg-sh.de/mgm/intelliform-adapter.git'
+            sh 'git clone https://${USER}:${TOKEN}@git.ozg-sh.de/ozgcloud-app/eingang-manager.git'
         
     }
-    dir("intelliform-adapter") {
-        sh 'git checkout tags/${FORMCYCLE_INTERFACE_VERSION} -b branch'
+    dir("eingang-manager") {
+        if ( !isSnapshotVersion([FORMCYCLE_INTERFACE_VERSION]) ) {
+            sh 'git checkout tags/${FORMCYCLE_INTERFACE_VERSION} -b branch'
+        }
     }
-    configureGit("intelliform-adapter")
+    configureGit("eingang-manager")
 }
 
 Void configureGit(String directory) {
@@ -177,3 +181,17 @@ Void configureGit(String directory) {
 String readFormCycleInterfaceVersion() {
     return sh(returnStdout: true, script: 'xmlstarlet sel -N w="http://maven.apache.org/POM/4.0.0" -t -v "//w:project/w:version" -n pom.xml').trim()
 }
+
+Boolean isSnapshotVersion(List versions) {
+    return matchRegexVersion(versions, SNAPSHOT_REGEX)
+}
+
+Boolean matchRegexVersion(List versions, String regex) {
+    for (version in versions) {
+        if ( !(version ==~ regex) ) {
+            return false
+        }
+    }
+
+    return true
+}
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 8fd4464c9bf7e61c2c5f83e2cd91c745062deec4..5bcd40c3ef7c003d5bb26bc0522965ad646c654c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -16,11 +16,12 @@
 		<!-- Version of FORMCYCLE to built against. -->
 		<xfc.version>7.4.0</xfc.version>
 
-		<formcycle-adapter-interface.version>2.0.0</formcycle-adapter-interface.version>
-		<lombok.version>1.18.26</lombok.version>
-		<junit-jupiter.version>5.6.0</junit-jupiter.version>
-		<mockito.version>5.1.1</mockito.version>
-		<assertj.version>3.24.2</assertj.version>
+		<formcycle-adapter-interface.version>2.2.0-SNAPSHOT</formcycle-adapter-interface.version>
+		<lombok.version>1.18.30</lombok.version>
+		<junit-jupiter.version>5.10.1</junit-jupiter.version>
+		<mockito.version>5.8.0</mockito.version>
+		<assertj.version>3.25.0</assertj.version>
+		<htmlcleaner.version>2.29</htmlcleaner.version>
 
 		<!-- Maven plugin versions -->
 		<maven-surefire-plugin.version>2.22.2</maven-surefire-plugin.version>
@@ -29,7 +30,6 @@
 		<maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version>
 		<fc-server-maven-plugin.version>7.4.0</fc-server-maven-plugin.version>
 		<fc-deploy-plugin-maven-plugin.version>7.0.1</fc-deploy-plugin-maven-plugin.version>
-		<htmlcleaner.version>2.27</htmlcleaner.version>
 	</properties>
 
 	<!-- Dependencies required by this plugin. -->
@@ -53,6 +53,12 @@
 			<groupId>de.ozgcloud.eingang</groupId>
 			<artifactId>formcycle-adapter-interface</artifactId>
 			<version>${formcycle-adapter-interface.version}</version>
+			<exclusions>
+				<exclusion>
+					<groupId>com.google.api.grpc</groupId>
+					<artifactId>proto-google-common-protos</artifactId>
+				</exclusion>
+			</exclusions>
 		</dependency>
 
 		<dependency>
@@ -96,8 +102,6 @@
 	</dependencies>
 
 	<build>
-		<finalName>${project.artifactId}</finalName>
-
 		<plugins>
 			<!-- Configure the compilation process. At least Java 11 is required. -->
 			<plugin>
@@ -166,26 +170,6 @@
 				</configuration>
 			</plugin>
 
-			<!-- Configure how the JAR is created, including manifest entries -->
-			<plugin>
-				<groupId>org.apache.maven.plugins</groupId>
-				<artifactId>maven-jar-plugin</artifactId>
-				<configuration>
-					<archive>
-						<manifest>
-							<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
-						</manifest>
-						<manifestEntries>
-							<formcycle-version-requirement>${xfc.version}</formcycle-version-requirement>
-							<Build-Timestamp>${maven.build.timestamp}</Build-Timestamp>
-							<Implementation-Title>${project.groupId}:${project.artifactId}</Implementation-Title>
-							<Implementation-Vendor-Id>${project.groupId}</Implementation-Vendor-Id>
-							<Implementation-Version>${project.version}</Implementation-Version>
-						</manifestEntries>
-					</archive>
-				</configuration>
-			</plugin>
-
 			<!-- Configure how the final plugin JAR is created. -->
 			<!-- It needs to be a fat JAR with the dependencies required by this plugin. -->
 			<plugin>
@@ -206,6 +190,9 @@
 							</descriptorRefs>
 							<appendAssemblyId>false</appendAssemblyId>
 							<archive>
+								<manifest>
+									<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
+								</manifest>
 								<manifestEntries>
 									<Build-Timestamp>${maven.build.timestamp}</Build-Timestamp>
 									<Implementation-Vendor-Id>${project.groupId}</Implementation-Vendor-Id>
diff --git a/src/main/java/de/ozgcloud/formcycle/ExecutionResult.java b/src/main/java/de/ozgcloud/formcycle/ExecutionResult.java
index 281ddf1a92ebe4a46b0e31565462c11daf9403f8..6f848b606668e7e63187dff6ef3207eb618c41a9 100644
--- a/src/main/java/de/ozgcloud/formcycle/ExecutionResult.java
+++ b/src/main/java/de/ozgcloud/formcycle/ExecutionResult.java
@@ -1,20 +1,19 @@
 package de.ozgcloud.formcycle;
 
-import java.util.Map;
-
-import org.apache.commons.lang3.StringUtils;
+import java.util.List;
 
+import de.ozgcloud.formcycle.errorhandling.Warning;
 import lombok.Builder;
+import lombok.Getter;
+import lombok.Singular;
 
 @Builder
+@Getter
 public class ExecutionResult {
 
-	public static final String VORGANGNUMMER_PROPERTY_KEY = "vorgangnummer";
-
 	private final String vorgangnummer;
 
-	public Map<String, String> get() {
-		return Map.of(VORGANGNUMMER_PROPERTY_KEY, StringUtils.trimToEmpty(vorgangnummer));
-	}
+	@Singular
+	public List<Warning> warnings;
 
 }
diff --git a/src/main/java/de/ozgcloud/formcycle/HttpPostRequestBuilder.java b/src/main/java/de/ozgcloud/formcycle/HttpPostRequestBuilder.java
index 6ce0d9e6c13b4c722eb9e59bfb0310c4a1d2db29..508171fb362afba401f5e75dccad5a55076a4bd9 100644
--- a/src/main/java/de/ozgcloud/formcycle/HttpPostRequestBuilder.java
+++ b/src/main/java/de/ozgcloud/formcycle/HttpPostRequestBuilder.java
@@ -39,7 +39,7 @@ class HttpPostRequestBuilder {
 
 	static final String FORMCYCLE_ENDPOINT = "formData";
 	static final String FORM_DATA_KEY = "formData";
-	static final ContentType PROTOBUF_CONTENT_TYPE = ContentType.create("application/protobuf");
+	static final ContentType PROTOBUF_CONTENT_TYPE = ContentType.create("application/x-protobuf");
 	static final String ATTACHMENT_KEY = "attachments";
 	static final String REPRESENTATION_KEY = "representations";
 	static final Pattern URL_WITH_ENDPOINT_PATTERN = Pattern.compile("^((https?://)?[^/]+)(/" + FORMCYCLE_ENDPOINT + ")$");
diff --git a/src/main/java/de/ozgcloud/formcycle/OzgCloudFormDataHttpClient.java b/src/main/java/de/ozgcloud/formcycle/OzgCloudFormDataHttpClient.java
index c21d5586b7a09d71f1ec2a0ecebacb7cf1547f1f..aba16e632765ca91c136bd088716b900c59c399a 100644
--- a/src/main/java/de/ozgcloud/formcycle/OzgCloudFormDataHttpClient.java
+++ b/src/main/java/de/ozgcloud/formcycle/OzgCloudFormDataHttpClient.java
@@ -26,18 +26,13 @@ import static java.util.Objects.*;
 import static org.apache.commons.lang3.StringUtils.*;
 
 import java.io.IOException;
-import java.io.InputStream;
-import java.nio.charset.StandardCharsets;
 import java.util.Collection;
 import java.util.Optional;
 
-import org.apache.commons.io.IOUtils;
 import org.apache.http.HttpHost;
-import org.apache.http.HttpStatus;
 import org.apache.http.auth.AuthScope;
 import org.apache.http.auth.UsernamePasswordCredentials;
 import org.apache.http.client.config.RequestConfig;
-import org.apache.http.client.methods.CloseableHttpResponse;
 import org.apache.http.client.methods.HttpPost;
 import org.apache.http.client.protocol.HttpClientContext;
 import org.apache.http.conn.routing.HttpRoutePlanner;
@@ -47,12 +42,12 @@ import org.apache.http.impl.client.CloseableHttpClient;
 import org.apache.http.impl.client.HttpClientBuilder;
 import org.apache.http.impl.conn.DefaultProxyRoutePlanner;
 import org.apache.http.protocol.HttpContext;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import de.ozgcloud.eingang.formcycle.FormCycleConfirmationResponse;
 import de.ozgcloud.eingang.formcycle.FormCycleFormData;
 import de.ozgcloud.formcycle.attachment.FormcycleAttachment;
+import de.ozgcloud.formcycle.client.OzgCloudResponseHandler;
+import de.ozgcloud.formcycle.errorhandling.OzgCloudRequestException;
 import lombok.RequiredArgsConstructor;
 
 @RequiredArgsConstructor
@@ -60,10 +55,11 @@ public class OzgCloudFormDataHttpClient {
 
 	static final int HTTPCLIENT_TIMEOUT = 10 * 60 * 1000;
 
-	private static final Logger LOG = LoggerFactory.getLogger(OzgCloudFormDataHttpClient.class);
-
 	private final OzgCloudConfig config;
 	private final SystemPropertiesProvider systemPropertiesProvider;
+	private final PortParser portParser;
+	private final OzgCloudResponseHandler responseHandler;
+
 	private Optional<ProxyConfig> systemProxyConfig;
 
 	private BasicCredentialsProvider credentialsProvider;
@@ -71,15 +67,15 @@ public class OzgCloudFormDataHttpClient {
 	private HttpHost proxyHost;
 
 	public FormCycleConfirmationResponse send(FormCycleFormData formData, Collection<FormcycleAttachment> attachments,
-			Collection<FormcycleAttachment> representations) throws IOException {
-
-		try (
-				var closeableHttpClient = createCloseableClient();
-				var closeableResponse = closeableHttpClient.execute(buildPostRequest(formData, attachments, representations), getContext())) {
-			return parseResponse(closeableResponse);
-		} catch (Exception e) {
-			LOG.info("Used OzgCloudConfig: {}", config);
+			Collection<FormcycleAttachment> representations) {
+
+		try (var closeableHttpClient = createCloseableClient()) {
+			return closeableHttpClient.execute(buildPostRequest(formData, attachments, representations), responseHandler, getContext());
+		} catch (OzgCloudRequestException e) {
+			e.setOzgCloudConfig(config);
 			throw e;
+		} catch (IOException e) {
+			throw new OzgCloudRequestException("Cannot send data to formcycle adapter in OZG cloud.", config, e);
 		}
 	}
 
@@ -101,7 +97,7 @@ public class OzgCloudFormDataHttpClient {
 
 	ProxyConfig getProxyConfig() {
 		var proxyConfig = config.getProxyConfig();
-		if (isNoneBlank(proxyConfig.getHost())) {
+		if (nonNull(proxyConfig) && isNoneBlank(proxyConfig.getHost())) {
 			return proxyConfig;
 		}
 		if (isNull(systemProxyConfig)) {
@@ -123,7 +119,7 @@ public class OzgCloudFormDataHttpClient {
 		}
 		return ProxyConfig.builder()
 				.host(systemProxyHost)
-				.port(Utils.parsePort(systemPropertiesProvider.getHttpsProxyPort()))
+				.port(portParser.parse(systemPropertiesProvider.getHttpsProxyPort()))
 				.user(systemPropertiesProvider.getHttpsProxyUser())
 				.password(systemPropertiesProvider.getHttpsProxyPassword())
 				.build();
@@ -136,7 +132,7 @@ public class OzgCloudFormDataHttpClient {
 		}
 		return ProxyConfig.builder()
 				.host(systemProxyHost)
-				.port(Utils.parsePort(systemPropertiesProvider.getHttpProxyPort()))
+				.port(portParser.parse(systemPropertiesProvider.getHttpProxyPort()))
 				.user(systemPropertiesProvider.getHttpProxyUser())
 				.password(systemPropertiesProvider.getHttpProxyPassword())
 				.build();
@@ -204,37 +200,4 @@ public class OzgCloudFormDataHttpClient {
 		return clientContext;
 	}
 
-	FormCycleConfirmationResponse parseResponse(CloseableHttpResponse response) throws IOException {
-		checkResponseStatus(response);
-		InputStream responseContent = response.getEntity().getContent();
-		return FormCycleConfirmationResponse.parseFrom(responseContent);
-	}
-
-	private void checkResponseStatus(CloseableHttpResponse response) {
-		var statusLine = response.getStatusLine();
-		if (statusLine.getStatusCode() != HttpStatus.SC_OK) {
-			throw new UnexpectedStatusCodeException(response);
-		}
-	}
-
-	static class UnexpectedStatusCodeException extends RuntimeException {
-		private static final String MESSAGE = "Unexcepted Status received: %s";
-
-		UnexpectedStatusCodeException(CloseableHttpResponse response) {
-			super(buildFullMessage(response));
-		}
-
-		private static String buildFullMessage(CloseableHttpResponse response) {
-			try {
-				StringBuilder messageBuilder = new StringBuilder(String.format(MESSAGE, response.getStatusLine())).append("\n");
-
-				IOUtils.readLines(response.getEntity().getContent(), StandardCharsets.UTF_8)
-						.stream().forEach(line -> messageBuilder.append(line).append("\n"));
-
-				return messageBuilder.toString();
-			} catch (IOException e) {
-				return MESSAGE + "\nError reading body";
-			}
-		}
-	}
 }
diff --git a/src/main/java/de/ozgcloud/formcycle/OzgCloudPlugin.java b/src/main/java/de/ozgcloud/formcycle/OzgCloudPlugin.java
new file mode 100644
index 0000000000000000000000000000000000000000..4046d0db6298ac9c0747f1b83ff2216f02b45167
--- /dev/null
+++ b/src/main/java/de/ozgcloud/formcycle/OzgCloudPlugin.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2023 Das Land Schleswig-Holstein vertreten durch das
+ * Ministerium für Energiewende, Klimaschutz, Umwelt und Natur
+ * Zentrales IT-Management
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+package de.ozgcloud.formcycle;
+
+import static de.ozgcloud.formcycle.errorhandling.NodeThrewExceptionFactory.*;
+import static org.apache.commons.lang3.StringUtils.*;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.Supplier;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import de.ozgcloud.formcycle.attachment.AttachmentMapper;
+import de.ozgcloud.formcycle.client.OzgCloudResponseHandler;
+import de.ozgcloud.formcycle.errorhandling.OzgPluginError;
+import de.ozgcloud.formcycle.errorhandling.OzgPluginSoftError;
+import de.ozgcloud.formcycle.errorhandling.NodeThrewExceptionFactory;
+import de.ozgcloud.formcycle.errorhandling.OzgCloudRequestException;
+import de.ozgcloud.formcycle.errorhandling.TechnicalException;
+import de.ozgcloud.formcycle.formdata.OzgCloudFormDataMapper;
+import de.ozgcloud.formcycle.formdata.PluginFormDataAdapter;
+import de.xima.fc.beans.interfaces.IGuiIcon;
+import de.xima.fc.entities.Attachment;
+import de.xima.fc.exceptions.AbstractAbruptCompletionException;
+import de.xima.fc.interfaces.plugin.lifecycle.IPluginInitializeData;
+import de.xima.fc.interfaces.workflow.execution.IFileValueDescriptor;
+import de.xima.fc.interfaces.workflow.execution.INormalCompletionResult;
+import de.xima.fc.interfaces.workflow.mixin.IElementCategory;
+import de.xima.fc.interfaces.workflow.mixin.IKeyValueSummarizableNode;
+import de.xima.fc.interfaces.workflow.mixin.ISummaryKeyValueModel;
+import de.xima.fc.interfaces.workflow.params.IGetElementPrototypesParams;
+import de.xima.fc.interfaces.workflow.params.IGetElementSummaryParams;
+import de.xima.fc.interfaces.workflow.params.INodeExecutionParams;
+import de.xima.fc.interfaces.workflow.value.IUnionValueDescriptor;
+import de.xima.fc.interfaces.workflow.value.IValueBuilder;
+import de.xima.fc.interfaces.workflow.value.IValueDescriptor;
+import de.xima.fc.interfaces.workflow.value.IValueDescriptorFactory;
+import de.xima.fc.mdl.ui.GuiIcon;
+import de.xima.fc.workflow.FileValueDescriptor;
+import de.xima.fc.workflow.SummaryKeyValueModelBuilder;
+import de.xima.fc.workflow.enums.EWorkflowElementCategory;
+import de.xima.fc.workflow.mixin.IPluginActionNodeHandler;
+
+public final class OzgCloudPlugin
+		implements IPluginActionNodeHandler<OzgPluginWorkflowNodeProperties>, IKeyValueSummarizableNode<OzgPluginWorkflowNodeProperties> {
+
+	static final String VORGANGNUMMER_PROPERTY_KEY = "vorgangnummer";
+
+	private final transient PortParser portParser = new PortParser();
+	private transient OzgPluginPropertiesSupplier ozgPluginPropertiesSupplier;
+	private transient IPluginInitializeData initializeData;
+
+	@Override
+	public void initialize(IPluginInitializeData initializeData) {
+		this.initializeData = initializeData;
+		this.ozgPluginPropertiesSupplier = new OzgPluginPropertiesSupplier(initializeData, portParser);
+	}
+
+	@Override
+	public String getPropertiesViewXhtmlName() {
+		return "OzgCloudPlugin.xhtml";
+	}
+
+	@Override
+	public String getName() {
+		return "de.ozgcloud.formcycle.OzgCloudPlugin";
+	}
+
+	@Override
+	public IElementCategory getMainCategory(IGetElementPrototypesParams params) {
+		// Main category where this node is shown in the drawer panel to the left of the
+		// designer.
+		return EWorkflowElementCategory.MAIN_ACTION;
+	}
+
+	@Override
+	public IElementCategory getSubCategory(IGetElementPrototypesParams params) {
+		// Sub category where this node is shown in the drawer panel to the left of the
+		// designer.
+		return EWorkflowElementCategory.SUB_ACTION_COMMON;
+	}
+
+	@Override
+	public IGuiIcon getPrototypeIcon(IGetElementPrototypesParams params) {
+		// Optional icon for this workflow node. Shown next to the node's name.
+		return GuiIcon.valueOf("fa-question");
+	}
+
+	@Override
+	public IFileValueDescriptor getFileValueDescriptor() {
+		// Implement this method when this node can return files.
+		// This node can then be selected by other actions that require files.
+		// If this node does not provide files, remove this method
+		return new FileValueDescriptor("OzgCloudPlugin.files");
+	}
+
+	@Override
+	public IValueDescriptor<?, ? extends IValueBuilder<?>> getSuccessValueDescriptor(IValueDescriptorFactory factory) {
+		// Data returned by this node when it is executed
+		// Users can access this data e.g. via placeholders, for example:
+		// [%$Action.RESULT.name%]
+		// [%$Action.RESULT.items.name%]
+		// [%$Action.RESULT.items.state%]
+		return factory.recordBuilder().requiredProperty(VORGANGNUMMER_PROPERTY_KEY, factory.string()).build();
+	}
+
+	@Override
+	public IUnionValueDescriptor<String> getErrorValueDescriptor(IValueDescriptorFactory factory) {
+		// For each error that can occur, additional data can be provided
+		// Users can access this data e.g. via placeholders, for example:
+		// [%$Action.ERROR_CODE%]
+		// [%$Action.ERROR_MESSAGE%]
+		// [%$Action.ERROR.file%]
+		// [%$Action.ERROR.requiredPermissions[0]%]
+		// [%$Action.ERROR.message%]
+		return factory.unionStringBuilder() //
+				.add(OzgPluginError.INTERNAL_ERROR.name(),
+						factory.recordBuilder().requiredProperty(EXCEPTION_ID_KEY, factory.string()).build())
+				.add(OzgPluginError.OZGCLOUD_REQUEST_FAILED.name(),
+						factory.recordBuilder().requiredProperty(EXCEPTION_ID_KEY, factory.string()).build())
+				.build();
+	}
+
+	@Override
+	public ISummaryKeyValueModel getElementSummaryKeyValueModel(IGetElementSummaryParams<OzgPluginWorkflowNodeProperties> params) {
+		// A simple summary table with the most important fields from the properties
+		// This is shown when the user clicks on the info icon on a node in the designer.
+		return new SummaryKeyValueModelBuilder().build();
+	}
+
+	@Override
+	public INormalCompletionResult execute(INodeExecutionParams<OzgPluginWorkflowNodeProperties> params) throws AbstractAbruptCompletionException {
+		try {
+			return buildSuccessResult(createPluginExecutor(params).execute(), params);
+		} catch (OzgCloudRequestException e) {
+			throw createExceptionFactory(params).createOzgCloudRequestException(e);
+		} catch (TechnicalException e) {
+			throw createExceptionFactory(params).createInternalException(e);
+		} catch (Exception e) {
+			throw createExceptionFactory(params).createInternalException("Unexpected error occurred", e);
+		}
+	}
+
+	OzgPluginExecutor createPluginExecutor(INodeExecutionParams<OzgPluginWorkflowNodeProperties> params) {
+		return new OzgPluginExecutor(new OzgCloudFormDataMapper(), createOzgCloudClient(params), new AttachmentMapper(),
+				createFormDataAdapter(params), createAttachmentSupplier(params));
+	}
+
+	OzgCloudFormDataHttpClient createOzgCloudClient(INodeExecutionParams<OzgPluginWorkflowNodeProperties> params) {
+		return new OzgCloudFormDataHttpClient(buildOzgCloudConfig(params.getData()), new SystemPropertiesProvider(), portParser,
+				new OzgCloudResponseHandler(new ObjectMapper()));
+	}
+
+	OzgCloudConfig buildOzgCloudConfig(OzgPluginWorkflowNodeProperties formProperties) {
+		var ozgCloudConfigBuilder = OzgCloudConfig.builder().eingangsAdapterUrl(getEingangsAdapterUrl(formProperties));
+		ozgPluginPropertiesSupplier.getProxyProperties().ifPresent(ozgCloudConfigBuilder::proxyConfig);
+		return ozgCloudConfigBuilder.build();
+	}
+
+	String getEingangsAdapterUrl(OzgPluginWorkflowNodeProperties formProperties) {
+		var eingangsAdapterUrl = formProperties.isUseCustomEingangsAdapterUrl()
+				? formProperties.getEingangsAdapterUrl()
+				: ozgPluginPropertiesSupplier.getEingangsAdapterUrl();
+
+		if (isBlank(eingangsAdapterUrl)) {
+			throw new TechnicalException("Eingangsadapter URL cannot be blank");
+		}
+		return eingangsAdapterUrl;
+	}
+
+	PluginFormDataAdapter createFormDataAdapter(INodeExecutionParams<OzgPluginWorkflowNodeProperties> params) {
+		return new PluginFormDataAdapter(params.getWorkflowContext().env(), params.getData().getOrganisationsEinheitId());
+	}
+
+	private Supplier<List<Attachment>> createAttachmentSupplier(INodeExecutionParams<OzgPluginWorkflowNodeProperties> params) {
+		var formRecord = params.getWorkflowContext().env().getFormRecord();
+		return () -> Optional.ofNullable(formRecord.getAttachments()).orElse(List.of());
+	}
+
+	NodeThrewExceptionFactory createExceptionFactory(INodeExecutionParams<OzgPluginWorkflowNodeProperties> params) {
+		return new NodeThrewExceptionFactory(params.throwingException());
+	}
+
+	INormalCompletionResult buildSuccessResult(ExecutionResult pluginExecutionResult, INodeExecutionParams<OzgPluginWorkflowNodeProperties> params) {
+		var resultBuilder = params.normalResult();
+		resultBuilder.success(Map.of(VORGANGNUMMER_PROPERTY_KEY, pluginExecutionResult.getVorgangnummer()));
+		pluginExecutionResult.getWarnings()
+				.forEach(warning -> resultBuilder.softError(warning.getErrorCode(), warning.getMessage(), warning.getValue(), warning.getCause()));
+		return resultBuilder.build();
+	}
+
+	@Override
+	public Class<OzgPluginError> getErrorCodeClass() {
+		// Class of the enumeration with the possible types of errors that may occur.
+		return OzgPluginError.class;
+	}
+
+	@Override
+	public Class<OzgPluginSoftError> getSoftErrorCodeClass() {
+		// Class of the enumeration with the possible types of soft errors that may occur.
+		return OzgPluginSoftError.class;
+	}
+
+	@Override
+	public IPluginInitializeData getPluginInitializeData() {
+		return initializeData;
+	}
+
+}
diff --git a/src/main/java/de/ozgcloud/formcycle/PluginBundleProperties.java b/src/main/java/de/ozgcloud/formcycle/OzgPluginBundleProperties.java
similarity index 97%
rename from src/main/java/de/ozgcloud/formcycle/PluginBundleProperties.java
rename to src/main/java/de/ozgcloud/formcycle/OzgPluginBundleProperties.java
index bb3f81373257dfee3f913df329deb7a37276a8ff..a93d7172bdd050a50db828f357627623cce874ce 100644
--- a/src/main/java/de/ozgcloud/formcycle/PluginBundleProperties.java
+++ b/src/main/java/de/ozgcloud/formcycle/OzgPluginBundleProperties.java
@@ -36,7 +36,7 @@ import de.xima.fc.plugin.config.IBundleProperties;
 import de.xima.fc.plugin.models.config.BundleConfigGroupItem;
 import de.xima.fc.plugin.models.config.BundleConfigParam;
 
-public class PluginBundleProperties implements IBundleProperties {
+public class OzgPluginBundleProperties implements IBundleProperties {
 
 	private static final boolean CRYPTIC_VALUE = true;
 	private static final boolean MANDATORY = true;
diff --git a/src/main/java/de/ozgcloud/formcycle/WorkflowElementNodeExecutor.java b/src/main/java/de/ozgcloud/formcycle/OzgPluginExecutor.java
similarity index 68%
rename from src/main/java/de/ozgcloud/formcycle/WorkflowElementNodeExecutor.java
rename to src/main/java/de/ozgcloud/formcycle/OzgPluginExecutor.java
index 492b79f0f8ebbdadd1a35472bc7b93317946f33b..7d493ea10dfa343fb4e07770f33f7772c739ba34 100644
--- a/src/main/java/de/ozgcloud/formcycle/WorkflowElementNodeExecutor.java
+++ b/src/main/java/de/ozgcloud/formcycle/OzgPluginExecutor.java
@@ -24,7 +24,6 @@ package de.ozgcloud.formcycle;
 
 import static java.util.Objects.*;
 
-import java.io.IOException;
 import java.util.EnumMap;
 import java.util.HashSet;
 import java.util.List;
@@ -38,45 +37,43 @@ import org.slf4j.LoggerFactory;
 import de.ozgcloud.formcycle.attachment.AttachmentMapper;
 import de.ozgcloud.formcycle.attachment.AttachmentType;
 import de.ozgcloud.formcycle.attachment.FormcycleAttachment;
-import de.ozgcloud.formcycle.errorhandling.NodeThrewExceptionFactory;
+import de.ozgcloud.formcycle.errorhandling.OzgPluginSoftError;
+import de.ozgcloud.formcycle.errorhandling.Warning;
 import de.ozgcloud.formcycle.formdata.OzgCloudFormDataMapper;
 import de.ozgcloud.formcycle.formdata.PluginFormDataAdapter;
 import de.xima.fc.entities.Attachment;
-import de.xima.fc.exceptions.AbstractAbruptCompletionException;
-import de.xima.fc.exceptions.NodeThrewException;
 import lombok.RequiredArgsConstructor;
 
 @RequiredArgsConstructor
-public final class WorkflowElementNodeExecutor {
+public final class OzgPluginExecutor {
 
-	private static final Logger LOG = LoggerFactory.getLogger(WorkflowElementNodeExecutor.class);
+	private static final Logger LOG = LoggerFactory.getLogger(OzgPluginExecutor.class);
 
 	private final OzgCloudFormDataMapper ozgCloudFormDataMapper;
 	private final OzgCloudFormDataHttpClient ozgHttpClient;
-	private final NodeThrewExceptionFactory exceptionFactory;
 	private final AttachmentMapper attachmentMapper;
 	private final PluginFormDataAdapter pluginFormDataAdapter;
 	private final Supplier<List<Attachment>> attachmentsSupplier;
 
-	public ExecutionResult execute() throws AbstractAbruptCompletionException {
+	public ExecutionResult execute() {
 		LOG.debug("Executing plugin WorkflowElementNodePlugin");
 		var formData = pluginFormDataAdapter.readFormData();
-		var formcycleFormData = ozgCloudFormDataMapper.map(formData);
 		var attachments = getAttachedFiles(formData.getAttachmentUuids());
+		var resultBuilder = ExecutionResult.builder();
+		if (isNull(attachments.get(AttachmentType.REPRESENTATION))) {
+			resultBuilder.warning(
+					buildWarning("Representation is missing. Ensure workflow is configured to attach form view to form data."));
+		}
 
-		try {
-			var formCycleConfirmationResponse = ozgHttpClient.send(formcycleFormData, attachments.get(AttachmentType.ATTACHMENT),
-					attachments.get(AttachmentType.REPRESENTATION));
-			LOG.debug("Formcycle adapter response: {}", formCycleConfirmationResponse);
-
-			return ExecutionResult.builder().vorgangnummer(formCycleConfirmationResponse.getVorgangNummer()).build();
+		var formcycleFormData = ozgCloudFormDataMapper.map(formData);
+		var formCycleConfirmationResponse = ozgHttpClient.send(formcycleFormData, attachments.get(AttachmentType.ATTACHMENT),
+				attachments.get(AttachmentType.REPRESENTATION));
+		LOG.debug("Formcycle adapter response: {}", formCycleConfirmationResponse);
 
-		} catch (IOException | RuntimeException e) {
-			throw exceptionFactory.createCannotSendException("Cannot send data to formcycle adapter in OZG cloud.", e);
-		}
+		return resultBuilder.vorgangnummer(formCycleConfirmationResponse.getVorgangNummer()).build();
 	}
 
-	Map<AttachmentType, Set<FormcycleAttachment>> getAttachedFiles(Set<String> uploadFieldUuids) throws NodeThrewException {
+	Map<AttachmentType, Set<FormcycleAttachment>> getAttachedFiles(Set<String> uploadFieldUuids) {
 		var resultMap = new EnumMap<AttachmentType, Set<FormcycleAttachment>>(AttachmentType.class);
 		for (Attachment attachment : attachmentsSupplier.get()) {
 			if (uploadFieldUuids.contains(attachment.getUUID())) {
@@ -85,10 +82,10 @@ public final class WorkflowElementNodeExecutor {
 			}
 			resultMap.computeIfAbsent(AttachmentType.REPRESENTATION, e -> new HashSet<>()).add(attachmentMapper.map(attachment));
 		}
-		if (isNull(resultMap.get(AttachmentType.REPRESENTATION))) {
-			LOG.warn("Representation is missing. Activate checkbox 'An den Vorgang anhaengen' in 'Word-Datei befuellen' plugin");
-		}
 		return resultMap;
 	}
 
+	Warning buildWarning(String message) {
+		return Warning.builder().errorCode(OzgPluginSoftError.MISSING_REPRESENTATION).message(message).build();
+	}
 }
diff --git a/src/main/java/de/ozgcloud/formcycle/PluginPropertiesMapper.java b/src/main/java/de/ozgcloud/formcycle/OzgPluginPropertiesSupplier.java
similarity index 54%
rename from src/main/java/de/ozgcloud/formcycle/PluginPropertiesMapper.java
rename to src/main/java/de/ozgcloud/formcycle/OzgPluginPropertiesSupplier.java
index a721b9770ec8b35e522888bfe83807c9d86c3147..33df4b7e80594fd61e27e2ec4e5600533dd5158c 100644
--- a/src/main/java/de/ozgcloud/formcycle/PluginPropertiesMapper.java
+++ b/src/main/java/de/ozgcloud/formcycle/OzgPluginPropertiesSupplier.java
@@ -26,21 +26,40 @@ package de.ozgcloud.formcycle;
 import static java.util.Objects.*;
 import static org.apache.commons.lang3.StringUtils.*;
 
+import java.util.Optional;
 import java.util.Properties;
 
-import de.ozgcloud.formcycle.errorhandling.UserErrorDispatcher;
+import de.ozgcloud.formcycle.errorhandling.TechnicalException;
+import de.xima.fc.interfaces.plugin.lifecycle.IPluginInitializeData;
 import lombok.RequiredArgsConstructor;
 
 @RequiredArgsConstructor
-public class PluginPropertiesMapper {
+public class OzgPluginPropertiesSupplier {
 
-	private final UserErrorDispatcher userErrorDispatcher;
+	private final IPluginInitializeData pluginData;
+	private final PortParser portParser;
 
-	public String getEingangsAdapterUrl(Properties pluginProperties) {
-		return pluginProperties.getProperty(OzgCloudConfig.KEY_EINGANGSADAPTER_URL);
+	public String getEingangsAdapterUrl() {
+		return pluginData.getProperties().getProperty(OzgCloudConfig.KEY_EINGANGSADAPTER_URL);
 	}
 
-	public ProxyConfig mapProxyProperties(Properties pluginProperties) {
+	public Optional<ProxyConfig> getProxyProperties() {
+		var pluginProperties = pluginData.getProperties();
+		validateProxyConfig(pluginProperties);
+		var proxyHostProperty = pluginProperties.getProperty(ProxyConfig.KEY_PROXY_HOST);
+		return isBlank(proxyHostProperty) ? Optional.empty() : Optional.of(buildProxyConfig(pluginProperties));
+	}
+
+	void validateProxyConfig(Properties pluginProperties) {
+		var proxyHostProperty = pluginProperties.getProperty(ProxyConfig.KEY_PROXY_HOST);
+		var port = getPort(pluginProperties);
+		if ((isBlank(proxyHostProperty) && nonNull(port))
+				|| (isNotBlank(proxyHostProperty) && isNull(port))) {
+			throw new TechnicalException("Proxy configuration is invalid. Both host and port must be set or none of them.");
+		}
+	}
+
+	ProxyConfig buildProxyConfig(Properties pluginProperties) {
 		return ProxyConfig.builder()
 				.host(pluginProperties.getProperty(ProxyConfig.KEY_PROXY_HOST))
 				.port(getPort(pluginProperties))
@@ -51,14 +70,7 @@ public class PluginPropertiesMapper {
 
 	Integer getPort(Properties pluginProperties) {
 		var portProperty = pluginProperties.getProperty(ProxyConfig.KEY_PROXY_PORT);
-		if (isBlank(portProperty)) {
-			return null;
-		}
-		var port = Utils.parsePort(portProperty);
-		if (isNull(port)) {
-			userErrorDispatcher.sendWrongProxyPortFormatMessage();
-		}
-		return port;
+		return isBlank(portProperty) ? null : portParser.parse(portProperty);
 	}
 
 }
diff --git a/src/main/java/de/ozgcloud/formcycle/WorkflowElementNodeProps.java b/src/main/java/de/ozgcloud/formcycle/OzgPluginWorkflowNodeProperties.java
similarity index 96%
rename from src/main/java/de/ozgcloud/formcycle/WorkflowElementNodeProps.java
rename to src/main/java/de/ozgcloud/formcycle/OzgPluginWorkflowNodeProperties.java
index 7773fe8affacf6d7424e3116f32839bf1f2676a6..8ec21b80a0d5c71675739afd10ed4ea9d44d2cf3 100644
--- a/src/main/java/de/ozgcloud/formcycle/WorkflowElementNodeProps.java
+++ b/src/main/java/de/ozgcloud/formcycle/OzgPluginWorkflowNodeProperties.java
@@ -54,7 +54,7 @@ import lombok.Setter;
 		withValues = {"true"},
 		requiredProperties = {"eingangsAdapterUrl"}
 )
-public final class WorkflowElementNodeProps extends BaseActionProps {
+public final class OzgPluginWorkflowNodeProperties extends BaseActionProps {
 
 	@NotEmpty
 	@Size(max = 20)
diff --git a/src/main/java/de/ozgcloud/formcycle/Utils.java b/src/main/java/de/ozgcloud/formcycle/PortParser.java
similarity index 77%
rename from src/main/java/de/ozgcloud/formcycle/Utils.java
rename to src/main/java/de/ozgcloud/formcycle/PortParser.java
index 6811cb2fd299332479d1371d9bf244289bc67121..ceb6bf39b8ae2e65fcfee487c7ede366a11d1063 100644
--- a/src/main/java/de/ozgcloud/formcycle/Utils.java
+++ b/src/main/java/de/ozgcloud/formcycle/PortParser.java
@@ -27,17 +27,18 @@ import static org.apache.commons.lang3.StringUtils.*;
 import java.util.Optional;
 import java.util.regex.Pattern;
 
-import lombok.NoArgsConstructor;
-
-@NoArgsConstructor(access = lombok.AccessLevel.PRIVATE)
-public class Utils {
+public class PortParser {
 
 	private static final Pattern PORT_PATTERN = Pattern.compile("\\s*\\d{1,5}\\s*");
 
-	public static Integer parsePort(String portString) {
-		if (isBlank(portString) || !PORT_PATTERN.matcher(portString).matches()) {
+	public Integer parse(String portString) {
+		if (isBlank(portString)) {
 			return null;
 		}
-		return Optional.of(Integer.parseInt(portString.trim())).filter(port -> port > 0 && port < 65536).orElse(null);
+		if (!PORT_PATTERN.matcher(portString).matches()) {
+			throw new IllegalArgumentException("Port must be a number");
+		}
+		return Optional.of(Integer.parseInt(portString.trim())).filter(port -> port > 0 && port < 65536)
+				.orElseThrow(() -> new IllegalArgumentException("Port must be a number between 1 and 65535."));
 	}
 }
diff --git a/src/main/java/de/ozgcloud/formcycle/ProxyConfig.java b/src/main/java/de/ozgcloud/formcycle/ProxyConfig.java
index 6e2bec1b895cafb8c5d68e1e4fb64ab268acddea..a4c0f4397a633818a6f89dded5fc6cc2bfecdd63 100644
--- a/src/main/java/de/ozgcloud/formcycle/ProxyConfig.java
+++ b/src/main/java/de/ozgcloud/formcycle/ProxyConfig.java
@@ -31,7 +31,7 @@ import lombok.ToString;
 
 @Builder
 @Getter
-@ToString(exclude = "password")
+@ToString
 public class ProxyConfig implements Serializable {
 
 	private static final long serialVersionUID = 1L;
@@ -54,6 +54,8 @@ public class ProxyConfig implements Serializable {
 	private String host;
 	private Integer port;
 	private String user;
+
+	@ToString.Exclude
 	private String password;
 
 }
diff --git a/src/main/java/de/ozgcloud/formcycle/WorkflowElementEvent.java b/src/main/java/de/ozgcloud/formcycle/WorkflowElementEvent.java
deleted file mode 100644
index 52163587766e07f2fe7ac889fbdbce88d917b86c..0000000000000000000000000000000000000000
--- a/src/main/java/de/ozgcloud/formcycle/WorkflowElementEvent.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (C) 2023 Das Land Schleswig-Holstein vertreten durch das
- * Ministerium für Energiewende, Klimaschutz, Umwelt und Natur
- * Zentrales IT-Management
- *
- * Lizenziert unter der EUPL, Version 1.2 oder - sobald
- * diese von der Europäischen Kommission genehmigt wurden -
- * Folgeversionen der EUPL ("Lizenz");
- * Sie dürfen dieses Werk ausschließlich gemäß
- * dieser Lizenz nutzen.
- * Eine Kopie der Lizenz finden Sie hier:
- *
- * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
- *
- * Sofern nicht durch anwendbare Rechtsvorschriften
- * gefordert oder in schriftlicher Form vereinbart, wird
- * die unter der Lizenz verbreitete Software "so wie sie
- * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
- * ausdrücklich oder stillschweigend - verbreitet.
- * Die sprachspezifischen Genehmigungen und Beschränkungen
- * unter der Lizenz sind dem Lizenztext zu entnehmen.
- */
-package de.ozgcloud.formcycle;
-
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-
-import de.xima.fc.entities.Benutzer;
-import de.xima.fc.entities.Mandant;
-import de.xima.fc.entities.Projekt;
-import de.xima.fc.entities.Vorgang;
-import de.xima.fc.workflow.event.AFormRecordEventData;
-
-/**
- * Custom event for the workflow trigger that is initiated by the HTTP servlet action plugin when an HTTP request is
- * made.
- * @author unspecified
- */
-@SuppressWarnings("serial")
-public final class WorkflowElementEvent extends AFormRecordEventData {
-  private final String type;
-  private final Map<String, List<String>> requestParams;
-
-  /**
-   * Creates a new event data instance with the given data.
-   * @param client Client who owns the form record.
-   * @param user User who initiated the event.
-   * @param locale Request locale to use for locale-sensitive operations.
-   * @param project Project to which the form record belongs.
-   * @param formRecord Form record for which to trigger events.
-   * @param type Custom type of the event, a workflow trigger reacts only to a given type.
-   * @param requestParams HTTP parameters of the request that triggered this event.
-   */
-  public WorkflowElementEvent(Mandant client, Benutzer user, Locale locale, Projekt project, Vorgang formRecord, String type, Map<String, List<String>> requestParams) {
-    super(client, user, locale, project, formRecord);
-    this.requestParams = requestParams;
-    this.type = type;
-  }
-  
-  /**
-   * @return HTTP parameters of the request that triggered this event.
-   */
-  public Map<String, List<String>> getRequestParams() {
-    return requestParams;
-  }
-
-  /**
-   * @return The type of this event. A trigger only reacts to a specific type.
-   */
-  public String getType() {
-    return type;
-  }
-
-  @Override
-  public Boolean isAnonymize() {
-    return false;
-  }
-}
diff --git a/src/main/java/de/ozgcloud/formcycle/WorkflowElementNodeBean.java b/src/main/java/de/ozgcloud/formcycle/WorkflowElementNodeBean.java
deleted file mode 100644
index e6cd2a94e44229d209653893e4ed9a435876f386..0000000000000000000000000000000000000000
--- a/src/main/java/de/ozgcloud/formcycle/WorkflowElementNodeBean.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2023 Das Land Schleswig-Holstein vertreten durch das
- * Ministerium für Energiewende, Klimaschutz, Umwelt und Natur
- * Zentrales IT-Management
- *
- * Lizenziert unter der EUPL, Version 1.2 oder - sobald
- * diese von der Europäischen Kommission genehmigt wurden -
- * Folgeversionen der EUPL ("Lizenz");
- * Sie dürfen dieses Werk ausschließlich gemäß
- * dieser Lizenz nutzen.
- * Eine Kopie der Lizenz finden Sie hier:
- *
- * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
- *
- * Sofern nicht durch anwendbare Rechtsvorschriften
- * gefordert oder in schriftlicher Form vereinbart, wird
- * die unter der Lizenz verbreitete Software "so wie sie
- * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
- * ausdrücklich oder stillschweigend - verbreitet.
- * Die sprachspezifischen Genehmigungen und Beschränkungen
- * unter der Lizenz sind dem Lizenztext zu entnehmen.
- */
-package de.ozgcloud.formcycle;
-
-import java.util.Properties;
-import java.util.concurrent.atomic.AtomicReference;
-
-import javax.faces.view.ViewScoped;
-import javax.inject.Named;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import de.xima.fc.interfaces.plugin.lifecycle.IPluginInitializeBeanData;
-import de.xima.fc.interfaces.plugin.lifecycle.helper.IPluginFileHelper;
-import de.xima.fc.interfaces.plugin.lifecycle.helper.IPluginResourceHelper;
-import de.xima.fc.plugin.exception.FCPluginException;
-import de.xima.fc.workflow.mixin.INodePropertyPluginBean;
-
-/**
- * Controller bean for the custom UI when editing the properties of the workflow node.
- * A bean is not required, often it suffices to add an XHTML page without a bean. This
- * class is a stub that that you can expand when you do not a bean, such as when you
- * want to enrich your UI with AJAX-enhanced logic.
- * @author unspecified
- */
-@Named
-@ViewScoped
-public final class WorkflowElementNodeBean implements INodePropertyPluginBean<WorkflowElementNodeProps> {
-
-  private static final Logger LOG = LoggerFactory.getLogger(WorkflowElementNodeBean.class);
-
-  private transient AtomicReference<IPluginInitializeBeanData> initializeBeanData;
-
-  /**
-   * A method that could be used the the action for some command button in your UI.
-   */
-  public void controllerAction() {
-    // Add custom logic here when the button was pressed...
-    LOG.info("Controller action was called");
-  }
-  
-  @Override
-  public IPluginFileHelper getFileHelper() {
-    return initializeBeanData.get().getFileHelper();
-  }
-
-  @Override
-  public Properties getProperties() {
-    return initializeBeanData.get().getProperties();
-  }
-
-  @Override
-  public IPluginResourceHelper getResourceHelper() {
-    return initializeBeanData.get().getResourceHelper();
-  }
-
-  @Override
-  public void initialize(IPluginInitializeBeanData initializeBeanData) throws FCPluginException {
-    this.initializeBeanData = new AtomicReference<>(initializeBeanData);
-  }
-}
diff --git a/src/main/java/de/ozgcloud/formcycle/WorkflowElementNodePlugin.java b/src/main/java/de/ozgcloud/formcycle/WorkflowElementNodePlugin.java
deleted file mode 100644
index a315fcf08060e2b447bf7809e1f9517bac2c35ee..0000000000000000000000000000000000000000
--- a/src/main/java/de/ozgcloud/formcycle/WorkflowElementNodePlugin.java
+++ /dev/null
@@ -1,269 +0,0 @@
-/*
- * Copyright (C) 2023 Das Land Schleswig-Holstein vertreten durch das
- * Ministerium für Energiewende, Klimaschutz, Umwelt und Natur
- * Zentrales IT-Management
- *
- * Lizenziert unter der EUPL, Version 1.2 oder - sobald
- * diese von der Europäischen Kommission genehmigt wurden -
- * Folgeversionen der EUPL ("Lizenz");
- * Sie dürfen dieses Werk ausschließlich gemäß
- * dieser Lizenz nutzen.
- * Eine Kopie der Lizenz finden Sie hier:
- *
- * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
- *
- * Sofern nicht durch anwendbare Rechtsvorschriften
- * gefordert oder in schriftlicher Form vereinbart, wird
- * die unter der Lizenz verbreitete Software "so wie sie
- * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
- * ausdrücklich oder stillschweigend - verbreitet.
- * Die sprachspezifischen Genehmigungen und Beschränkungen
- * unter der Lizenz sind dem Lizenztext zu entnehmen.
- */
-package de.ozgcloud.formcycle;
-
-import static org.apache.commons.lang3.StringUtils.*;
-
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.util.List;
-import java.util.Locale;
-import java.util.Optional;
-import java.util.function.Supplier;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import de.ozgcloud.formcycle.attachment.AttachmentMapper;
-import de.ozgcloud.formcycle.errorhandling.EWorkflowElementNodeError;
-import de.ozgcloud.formcycle.errorhandling.EWorkflowElementNodeSoftError;
-import de.ozgcloud.formcycle.errorhandling.NodeThrewExceptionFactory;
-import de.ozgcloud.formcycle.errorhandling.UserErrorDispatcher;
-import de.ozgcloud.formcycle.formdata.OzgCloudFormDataMapper;
-import de.ozgcloud.formcycle.formdata.PluginFormDataAdapter;
-import de.xima.fc.beans.interfaces.IGuiIcon;
-import de.xima.fc.entities.Attachment;
-import de.xima.fc.exceptions.AbstractAbruptCompletionException;
-import de.xima.fc.exceptions.NodeThrewException;
-import de.xima.fc.interfaces.plugin.lifecycle.IPluginInitializeData;
-import de.xima.fc.interfaces.workflow.elements.IElementHelpLocation;
-import de.xima.fc.interfaces.workflow.execution.IFileValueDescriptor;
-import de.xima.fc.interfaces.workflow.execution.INormalCompletionResult;
-import de.xima.fc.interfaces.workflow.execution.IWorkflowExecutionEnvironmentData;
-import de.xima.fc.interfaces.workflow.mixin.IElementCategory;
-import de.xima.fc.interfaces.workflow.mixin.IKeyValueSummarizableNode;
-import de.xima.fc.interfaces.workflow.mixin.ISummaryKeyValueModel;
-import de.xima.fc.interfaces.workflow.params.IGetElementPrototypesParams;
-import de.xima.fc.interfaces.workflow.params.IGetElementSummaryParams;
-import de.xima.fc.interfaces.workflow.params.INodeExecutionParams;
-import de.xima.fc.interfaces.workflow.value.IUnionValueDescriptor;
-import de.xima.fc.interfaces.workflow.value.IValueBuilder;
-import de.xima.fc.interfaces.workflow.value.IValueDescriptor;
-import de.xima.fc.interfaces.workflow.value.IValueDescriptorFactory;
-import de.xima.fc.mdl.ui.GuiIcon;
-import de.xima.fc.workflow.ElementHelpLocation;
-import de.xima.fc.workflow.FileValueDescriptor;
-import de.xima.fc.workflow.SummaryKeyValueModelBuilder;
-import de.xima.fc.workflow.enums.EWorkflowElementCategory;
-import de.xima.fc.workflow.mixin.INodePropertyPluginBean;
-import de.xima.fc.workflow.mixin.IPluginActionNodeHandler;
-
-public final class WorkflowElementNodePlugin
-		implements IPluginActionNodeHandler<WorkflowElementNodeProps>, IKeyValueSummarizableNode<WorkflowElementNodeProps> {
-
-	private static final Logger LOG = LoggerFactory.getLogger(WorkflowElementNodePlugin.class);
-
-	private transient IPluginInitializeData initializeData;
-	private transient PluginPropertiesMapper pluginPropertiesMapper;
-
-	@Override
-	public void initialize(IPluginInitializeData initializeData) {
-		this.initializeData = initializeData;
-		this.pluginPropertiesMapper = createPropertiesMapper();
-	}
-
-	PluginPropertiesMapper createPropertiesMapper() {
-		return new PluginPropertiesMapper(new UserErrorDispatcher());
-	}
-
-	@Override
-	public String getPropertiesViewXhtmlName() {
-		return "WorkflowElementNode.xhtml";
-	}
-
-	@Override
-	public String getName() {
-		return "de.ozgcloud.formcycle.WorkflowElementNodePlugin";
-	}
-
-	@Override
-	public IElementCategory getMainCategory(IGetElementPrototypesParams params) {
-		// Main category where this node is shown in the drawer panel to the left of the
-		// designer.
-		return EWorkflowElementCategory.MAIN_ACTION;
-	}
-
-	@Override
-	public IElementCategory getSubCategory(IGetElementPrototypesParams params) {
-		// Sub category where this node is shown in the drawer panel to the left of the
-		// designer.
-		return EWorkflowElementCategory.SUB_ACTION_COMMON;
-	}
-
-	@Override
-	public IElementHelpLocation getHelpPageLocation(Locale locale) throws IOException {
-		// Optional HTML page documenting how to use this workflow node.
-		// Use ElementHelpLocation#forExternalLink if you host the help page on an external server
-		// Use ElementHelpLocation#forOfficialHelpPage when you host the help page on the FORMCYCLE wiki
-		final var path = String.format("WEB-INF/properties/WorkflowElementNodeHelp_%s.html", locale.toLanguageTag());
-		return ElementHelpLocation.forHtmlFile(getClass().getClassLoader(), path, StandardCharsets.UTF_8);
-	}
-
-	@Override
-	public IGuiIcon getPrototypeIcon(IGetElementPrototypesParams params) {
-		// Optional icon for this workflow node. Shown next to the node's name.
-		return GuiIcon.valueOf("fa-question");
-	}
-
-	@Override
-	public IFileValueDescriptor getFileValueDescriptor() {
-		// Implement this method when this node can return files.
-		// This node can then be selected by other actions that require files.
-		// If this node does not provide files, remove this method
-		return new FileValueDescriptor("WorkflowElementNodePlugin.files");
-	}
-
-	@Override
-	public IValueDescriptor<?, ? extends IValueBuilder<?>> getSuccessValueDescriptor(IValueDescriptorFactory factory) {
-		// Data returned by this node when it is executed
-		// Users can access this data e.g. via placeholders, for example:
-		// [%$Action.RESULT.name%]
-		// [%$Action.RESULT.items.name%]
-		// [%$Action.RESULT.items.state%]
-		return factory.recordBuilder().requiredProperty(ExecutionResult.VORGANGNUMMER_PROPERTY_KEY, factory.string()).build();
-	}
-
-	@Override
-	public IUnionValueDescriptor<String> getErrorValueDescriptor(IValueDescriptorFactory factory) {
-		// For each error that can occur, additional data can be provided
-		// Users can access this data e.g. via placeholders, for example:
-		// [%$Action.ERROR_CODE%]
-		// [%$Action.ERROR_MESSAGE%]
-		// [%$Action.ERROR.file%]
-		// [%$Action.ERROR.requiredPermissions[0]%]
-		// [%$Action.ERROR.message%]
-		// TODO das ist dafür gedacht, Fehler an der Oberfläche zu zeigen, was wir eigentlich nicht brauchen man könnte es einfach löschen
-		return factory.unionStringBuilder() //
-				.add(EWorkflowElementNodeError.OZGCLOUD_CANNOT_SEND.name(), f -> f.recordBuilder()
-						.requiredProperty(NodeThrewExceptionFactory.ERROR_MESSAGE_KEY, f.string())
-						.build()
-				)
-				.add(EWorkflowElementNodeError.PLUGIN_CANNOT_GET_ATTACHMENT.name(), f -> f.recordBuilder()
-						.requiredProperty(NodeThrewExceptionFactory.ERROR_MESSAGE_KEY, f.string())
-						.build()
-				)
-				.add(EWorkflowElementNodeError.PLUGIN_CONFIG_ERROR.name(), f -> f.recordBuilder()
-						.requiredProperty(NodeThrewExceptionFactory.ERROR_MESSAGE_KEY, v -> v.string())
-						.build())
-				.build();
-	}
-
-	@Override
-	public IUnionValueDescriptor<String> getSoftErrorValueDescriptor(IValueDescriptorFactory factory) {
-		// For each error that can occur, additional data can be provided
-		// Users can access this data e.g. via placeholders, for example:
-		// [%$Action.ERROR_CODE%]
-		// [%$Action.ERROR_MESSAGE%]
-		// [%$Action.ERROR.file%]
-		// [%$Action.ERROR.requiredPermissions[0]%]
-		// [%$Action.ERROR.message%]
-		return factory.unionStringBuilder() //
-				.add(EWorkflowElementNodeSoftError.WRONG_HOST_NAME.name(), f -> f.recordBuilder() //
-						.requiredProperty("expectedHostName", v -> v.string()) //
-						.build()) //
-				.add(EWorkflowElementNodeSoftError.USER_DOES_NOT_EXIST.name(), f -> f.string()) //
-				.add(EWorkflowElementNodeError.PLUGIN_CONFIG_ERROR.name(), f -> f.recordBuilder()
-						.requiredProperty(NodeThrewExceptionFactory.ERROR_MESSAGE_KEY, v -> v.string())
-						.build())
-				.build();
-	}
-
-	@Override
-	public ISummaryKeyValueModel getElementSummaryKeyValueModel(IGetElementSummaryParams<WorkflowElementNodeProps> params) {
-		// A simple summary table with the most important fields from the properties
-		// This is shown when the user clicks on the info icon on a node in the designer.
-		return new SummaryKeyValueModelBuilder().build();
-	}
-
-	@Override
-	public INormalCompletionResult execute(INodeExecutionParams<WorkflowElementNodeProps> params) throws AbstractAbruptCompletionException {
-		var workflowExecutionEnvironmentData = params.getWorkflowContext().env();
-		var formProperties = params.getData();
-		PluginFormDataAdapter pluginFormDataAdapter = new PluginFormDataAdapter(workflowExecutionEnvironmentData,
-				formProperties.getOrganisationsEinheitId());
-		var ozgCloudFormDataMapper = new OzgCloudFormDataMapper();
-		var exceptionFactory = new NodeThrewExceptionFactory(params.throwingException());
-		var attachmentsAdapter = new AttachmentMapper(exceptionFactory);
-		var pluginExecutor = new WorkflowElementNodeExecutor(ozgCloudFormDataMapper, createOzgClient(formProperties, exceptionFactory),
-				exceptionFactory,
-				attachmentsAdapter, pluginFormDataAdapter, createAttachmentSupplier(workflowExecutionEnvironmentData));
-		var executionResult = pluginExecutor.execute();
-		return params.normalResult().success(executionResult.get()).build();
-	}
-
-	// TODO hier braucht man die Factory eigentlich nicht, es würde reichen einfaches Exception zu werfen
-	OzgCloudFormDataHttpClient createOzgClient(WorkflowElementNodeProps formProperties, NodeThrewExceptionFactory exceptionFactory)
-			throws NodeThrewException {
-		var ozgCloudConfig = buildOzgCloudConfig(formProperties, exceptionFactory);
-		return new OzgCloudFormDataHttpClient(ozgCloudConfig, new SystemPropertiesProvider());
-	}
-
-	OzgCloudConfig buildOzgCloudConfig(WorkflowElementNodeProps formProperties, NodeThrewExceptionFactory exceptionFactory)
-			throws NodeThrewException {
-		var eingangsAdapterUrl = getEingangsAdapterUrl(formProperties);
-		if (isBlank(eingangsAdapterUrl)) {
-			throw exceptionFactory.createConfigurationException("Eingangsadapter URL cannot be blank");
-		}
-
-		return OzgCloudConfig.builder()
-				.eingangsAdapterUrl(eingangsAdapterUrl)
-				.proxyConfig(pluginPropertiesMapper.mapProxyProperties(initializeData.getProperties()))
-				.build();
-	}
-
-	String getEingangsAdapterUrl(WorkflowElementNodeProps formProperties) {
-		if (formProperties.isUseCustomEingangsAdapterUrl()) {
-			return formProperties.getEingangsAdapterUrl();
-		}
-		return pluginPropertiesMapper.getEingangsAdapterUrl(initializeData.getProperties());
-	}
-
-	private Supplier<List<Attachment>> createAttachmentSupplier(IWorkflowExecutionEnvironmentData environmentData) {
-		return () -> Optional.ofNullable(environmentData.getFormRecord().getAttachments()).orElse(List.of());
-	}
-
-	@Override
-	public Class<EWorkflowElementNodeError> getErrorCodeClass() {
-		// Class of the enumeration with the possible types of errors that may occur.
-		return EWorkflowElementNodeError.class;
-	}
-
-	@Override
-	public Class<EWorkflowElementNodeSoftError> getSoftErrorCodeClass() {
-		// Class of the enumeration with the possible types of soft errors that may occur.
-		return EWorkflowElementNodeSoftError.class;
-	}
-
-	@Override
-	public Class<? extends INodePropertyPluginBean<WorkflowElementNodeProps>> getMainPluginBeanClass() {
-		// List of beans that should be available in the XHTML page.
-		// Often you do not need a bean, in this case you can just remove this method.
-		return WorkflowElementNodeBean.class;
-	}
-
-	@Override
-	public IPluginInitializeData getPluginInitializeData() {
-		return initializeData;
-	}
-
-}
diff --git a/src/main/java/de/ozgcloud/formcycle/attachment/AttachmentMapper.java b/src/main/java/de/ozgcloud/formcycle/attachment/AttachmentMapper.java
index ae19865354b23f3757a91d29411320866b848f6c..1f9b3e7fc20ed98c2cb768835ad464ee475ea12d 100644
--- a/src/main/java/de/ozgcloud/formcycle/attachment/AttachmentMapper.java
+++ b/src/main/java/de/ozgcloud/formcycle/attachment/AttachmentMapper.java
@@ -30,27 +30,22 @@ import java.net.URLConnection;
 import org.apache.http.entity.ContentType;
 import org.springframework.util.MimeTypeUtils;
 
-import de.ozgcloud.formcycle.errorhandling.NodeThrewExceptionFactory;
+import de.ozgcloud.formcycle.errorhandling.TechnicalException;
 import de.xima.fc.entities.Attachment;
-import de.xima.fc.exceptions.NodeThrewException;
-import lombok.RequiredArgsConstructor;
 
-@RequiredArgsConstructor
 public class AttachmentMapper {
 
 	static final String CANNOT_READ_ATTACHED_FILE = "Cannot read attached file. It doesn't exist.";
 	static final String FILENAME_IS_NULL = "File name cannot be null.";
 
-	private final NodeThrewExceptionFactory exceptionFactory;
-
-	public FormcycleAttachment map(Attachment pluginAttachment) throws NodeThrewException {
+	public FormcycleAttachment map(Attachment pluginAttachment) {
 		if (isNull(pluginAttachment.getFileEntity().getDaten())) {
-			throw exceptionFactory.createCannotGetAttachmentException(CANNOT_READ_ATTACHED_FILE);
+			throw new TechnicalException(CANNOT_READ_ATTACHED_FILE);
 		}
 		return buildAttachment(pluginAttachment);
 	}
 
-	FormcycleAttachment buildAttachment(Attachment pluginAttachment) throws NodeThrewException {
+	FormcycleAttachment buildAttachment(Attachment pluginAttachment) {
 		return FormcycleAttachment.builder()
 				.uuid(pluginAttachment.getUUID())
 				.fileName(pluginAttachment.getDateiName())
@@ -58,9 +53,9 @@ public class AttachmentMapper {
 				.content(new ByteArrayInputStream(pluginAttachment.getFileEntity().getDaten())).build();
 	}
 
-	private ContentType getContentType(String name) throws NodeThrewException {
+	ContentType getContentType(String name) {
 		if(isNull(name)) {
-			throw exceptionFactory.createCannotGetAttachmentException(FILENAME_IS_NULL);
+			throw new TechnicalException(FILENAME_IS_NULL);
 		}
 		String contentType = requireNonNullElse(URLConnection.guessContentTypeFromName(name), MimeTypeUtils.APPLICATION_OCTET_STREAM_VALUE);
 		return ContentType.create(contentType);
diff --git a/src/main/java/de/ozgcloud/formcycle/client/HttpUtils.java b/src/main/java/de/ozgcloud/formcycle/client/HttpUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..9facc53751e94895278c8b405964aff777d7c8df
--- /dev/null
+++ b/src/main/java/de/ozgcloud/formcycle/client/HttpUtils.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch das
+ * Ministerium für Energiewende, Klimaschutz, Umwelt und Natur
+ * Zentrales IT-Management
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+
+package de.ozgcloud.formcycle.client;
+
+import org.apache.http.HttpStatus;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class HttpUtils {
+
+	public static boolean isError(int statusCode) {
+		return is400Error(statusCode) || is500Error(statusCode);
+	}
+
+	public static boolean is400Error(int statusCode) {
+		return statusCode == HttpStatus.SC_BAD_REQUEST
+				|| statusCode == HttpStatus.SC_UNAUTHORIZED
+				|| statusCode == HttpStatus.SC_PAYMENT_REQUIRED
+				|| statusCode == HttpStatus.SC_FORBIDDEN
+				|| statusCode == HttpStatus.SC_NOT_FOUND
+				|| statusCode == HttpStatus.SC_METHOD_NOT_ALLOWED
+				|| statusCode == HttpStatus.SC_NOT_ACCEPTABLE
+				|| statusCode == HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED
+				|| statusCode == HttpStatus.SC_REQUEST_TIMEOUT
+				|| statusCode == HttpStatus.SC_CONFLICT
+				|| statusCode == HttpStatus.SC_GONE
+				|| statusCode == HttpStatus.SC_LENGTH_REQUIRED
+				|| statusCode == HttpStatus.SC_PRECONDITION_FAILED
+				|| statusCode == HttpStatus.SC_REQUEST_TOO_LONG
+				|| statusCode == HttpStatus.SC_REQUEST_URI_TOO_LONG
+				|| statusCode == HttpStatus.SC_UNSUPPORTED_MEDIA_TYPE
+				|| statusCode == HttpStatus.SC_REQUESTED_RANGE_NOT_SATISFIABLE
+				|| statusCode == HttpStatus.SC_EXPECTATION_FAILED
+				|| statusCode == HttpStatus.SC_INSUFFICIENT_SPACE_ON_RESOURCE
+				|| statusCode == HttpStatus.SC_METHOD_FAILURE
+				|| statusCode == HttpStatus.SC_UNPROCESSABLE_ENTITY
+				|| statusCode == HttpStatus.SC_LOCKED
+				|| statusCode == HttpStatus.SC_FAILED_DEPENDENCY
+				|| statusCode == HttpStatus.SC_TOO_MANY_REQUESTS;
+	}
+
+	public static boolean is500Error(int statusCode) {
+		return statusCode == HttpStatus.SC_INTERNAL_SERVER_ERROR
+				|| statusCode == HttpStatus.SC_NOT_IMPLEMENTED
+				|| statusCode == HttpStatus.SC_BAD_GATEWAY
+				|| statusCode == HttpStatus.SC_SERVICE_UNAVAILABLE
+				|| statusCode == HttpStatus.SC_GATEWAY_TIMEOUT
+				|| statusCode == HttpStatus.SC_HTTP_VERSION_NOT_SUPPORTED
+				|| statusCode == HttpStatus.SC_INSUFFICIENT_STORAGE;
+	}
+}
diff --git a/src/main/java/de/ozgcloud/formcycle/client/OzgCloudErrorDto.java b/src/main/java/de/ozgcloud/formcycle/client/OzgCloudErrorDto.java
new file mode 100644
index 0000000000000000000000000000000000000000..8e5b545ef71d75168ffa85d8902711ce70bac37d
--- /dev/null
+++ b/src/main/java/de/ozgcloud/formcycle/client/OzgCloudErrorDto.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch das
+ * Ministerium für Energiewende, Klimaschutz, Umwelt und Natur
+ * Zentrales IT-Management
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+
+package de.ozgcloud.formcycle.client;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class OzgCloudErrorDto {
+
+	private String message;
+	private String exceptionId;
+
+}
diff --git a/src/main/java/de/ozgcloud/formcycle/client/OzgCloudResponseHandler.java b/src/main/java/de/ozgcloud/formcycle/client/OzgCloudResponseHandler.java
new file mode 100644
index 0000000000000000000000000000000000000000..bef4b00047cd42f55a25420c164bd3818f8ce0ba
--- /dev/null
+++ b/src/main/java/de/ozgcloud/formcycle/client/OzgCloudResponseHandler.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch das
+ * Ministerium für Energiewende, Klimaschutz, Umwelt und Natur
+ * Zentrales IT-Management
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+
+package de.ozgcloud.formcycle.client;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.client.ResponseHandler;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import de.ozgcloud.eingang.formcycle.FormCycleConfirmationResponse;
+import de.ozgcloud.formcycle.errorhandling.OzgCloudRequestException;
+import lombok.RequiredArgsConstructor;
+
+@RequiredArgsConstructor
+public class OzgCloudResponseHandler implements ResponseHandler<FormCycleConfirmationResponse> {
+
+	static final String MESSAGE = "Unexpected Status received: %s";
+
+	private final ObjectMapper objectMapper;
+
+	@Override
+	public FormCycleConfirmationResponse handleResponse(HttpResponse response) throws IOException {
+		checkResponseStatus(response);
+		return buildResponse(response);
+	}
+
+	FormCycleConfirmationResponse buildResponse(HttpResponse response) throws IOException {
+		return FormCycleConfirmationResponse.parseFrom(response.getEntity().getContent());
+	}
+
+	void checkResponseStatus(HttpResponse response) {
+		var statusCode = response.getStatusLine().getStatusCode();
+		if (HttpUtils.isError(statusCode)) {
+			var receivedError = getOzgCloudError(response.getEntity());
+			throw new OzgCloudRequestException(receivedError.getMessage(), receivedError.getExceptionId());
+		}
+		if (statusCode != HttpStatus.SC_OK) {
+			throw new OzgCloudRequestException(buildFullMessage(response));
+		}
+	}
+
+	OzgCloudErrorDto getOzgCloudError(HttpEntity entity) {
+		try {
+			return objectMapper.readValue(entity.getContent(), OzgCloudErrorDto.class);
+		} catch (IOException e) {
+			throw new OzgCloudRequestException("Cannot read error message.", e);
+		}
+	}
+
+	String buildFullMessage(HttpResponse response) {
+		StringBuilder messageBuilder = new StringBuilder(String.format(MESSAGE, response.getStatusLine()));
+		try {
+			IOUtils.readLines(response.getEntity().getContent(), StandardCharsets.UTF_8).forEach(line -> messageBuilder.append("\n").append(line));
+		} catch (IOException e) {
+			messageBuilder.append("\nError reading body");
+		}
+		return messageBuilder.toString();
+	}
+}
diff --git a/src/main/java/de/ozgcloud/formcycle/errorhandling/ExceptionUtil.java b/src/main/java/de/ozgcloud/formcycle/errorhandling/ExceptionUtil.java
new file mode 100644
index 0000000000000000000000000000000000000000..7135603c2485b5888826f3b1b774d88bc3fda74c
--- /dev/null
+++ b/src/main/java/de/ozgcloud/formcycle/errorhandling/ExceptionUtil.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch das
+ * Ministerium für Energiewende, Klimaschutz, Umwelt und Natur
+ * Zentrales IT-Management
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+
+package de.ozgcloud.formcycle.errorhandling;
+
+import java.util.UUID;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class ExceptionUtil {
+
+	private static final String TEMPLATE_MESSAGE_WITH_EXCEPTION_ID = "%s (ExceptionId: %s)";
+
+	public static String formatMessageWithExceptionId(String message) {
+		return formatMessageWithExceptionId(message, createExceptionId());
+	}
+
+	public static String formatMessageWithExceptionId(String message, String exceptionId) {
+		return String.format(TEMPLATE_MESSAGE_WITH_EXCEPTION_ID, message, exceptionId);
+	}
+
+	public static String createExceptionId() {
+		return UUID.randomUUID().toString();
+	}
+}
diff --git a/src/main/java/de/ozgcloud/formcycle/errorhandling/NodeThrewExceptionFactory.java b/src/main/java/de/ozgcloud/formcycle/errorhandling/NodeThrewExceptionFactory.java
index 97e6f5053665915e04020c5961c7af14f7791be9..a5eb26eccb8b77997b412b2155c16764b22211d0 100644
--- a/src/main/java/de/ozgcloud/formcycle/errorhandling/NodeThrewExceptionFactory.java
+++ b/src/main/java/de/ozgcloud/formcycle/errorhandling/NodeThrewExceptionFactory.java
@@ -1,8 +1,8 @@
 package de.ozgcloud.formcycle.errorhandling;
 
-import static de.ozgcloud.formcycle.errorhandling.EWorkflowElementNodeError.*;
+import static de.ozgcloud.formcycle.errorhandling.OzgPluginError.*;
 
-import com.alibaba.fastjson.JSONObject;
+import java.util.Map;
 
 import de.xima.fc.exceptions.NodeThrewException;
 import de.xima.fc.interfaces.workflow.params.INodeThrewExceptionBuilder;
@@ -11,25 +11,27 @@ import lombok.RequiredArgsConstructor;
 @RequiredArgsConstructor
 public class NodeThrewExceptionFactory {
 
-	public static final String ERROR_MESSAGE_KEY = "message";
+	public static final String EXCEPTION_ID_KEY = "exceptionId";
 
 	private final INodeThrewExceptionBuilder exceptionBuilder;
 
-	public NodeThrewException createCannotSendException(String message, Exception cause) {
-		final var data = new JSONObject();
-		data.put(ERROR_MESSAGE_KEY, message);
-		return exceptionBuilder.error(OZGCLOUD_CANNOT_SEND.name(), data).message(message).cause(cause).build();
+	public NodeThrewException createInternalException(TechnicalException e) {
+		return exceptionBuilder.error(INTERNAL_ERROR.name(), createDataMap(e.getExceptionId())).message(e.getMessage()).cause(e)
+				.build();
 	}
 
-	public NodeThrewException createCannotGetAttachmentException(String message) {
-		final var data = new JSONObject();
-		data.put(ERROR_MESSAGE_KEY, message);
-		return exceptionBuilder.error(PLUGIN_CANNOT_GET_ATTACHMENT.name(), data).message(message).build();
+	public NodeThrewException createInternalException(String message, Exception e) {
+		return exceptionBuilder.error(INTERNAL_ERROR.name(), createDataMap(ExceptionUtil.createExceptionId()))
+				.message(ExceptionUtil.formatMessageWithExceptionId(message)).cause(e).build();
 	}
 
-	public NodeThrewException createConfigurationException(String message) {
-		final var data = new JSONObject();
-		data.put(ERROR_MESSAGE_KEY, message);
-		return exceptionBuilder.error(PLUGIN_CONFIG_ERROR.name(), data).message(message).build();
+	public NodeThrewException createOzgCloudRequestException(OzgCloudRequestException e) {
+		return exceptionBuilder.error(OZGCLOUD_REQUEST_FAILED.name(), createDataMap(e.getExceptionId())).message(e.getMessageWithConfig()).cause(e)
+				.build();
 	}
+
+	Map<String, String> createDataMap(String exceptionId) {
+		return Map.of(EXCEPTION_ID_KEY, exceptionId);
+	}
+
 }
diff --git a/src/main/java/de/ozgcloud/formcycle/errorhandling/OzgCloudRequestException.java b/src/main/java/de/ozgcloud/formcycle/errorhandling/OzgCloudRequestException.java
new file mode 100644
index 0000000000000000000000000000000000000000..21b60e93617325fe15e7ef5bc5b8213d6364e07a
--- /dev/null
+++ b/src/main/java/de/ozgcloud/formcycle/errorhandling/OzgCloudRequestException.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch das
+ * Ministerium für Energiewende, Klimaschutz, Umwelt und Natur
+ * Zentrales IT-Management
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+
+package de.ozgcloud.formcycle.errorhandling;
+
+import static java.util.Objects.*;
+
+import de.ozgcloud.formcycle.OzgCloudConfig;
+import lombok.Setter;
+
+@Setter
+public class OzgCloudRequestException extends TechnicalException {
+
+	private OzgCloudConfig ozgCloudConfig; // NOSONAR
+
+	public OzgCloudRequestException(String message) {
+		super(message);
+	}
+
+	public OzgCloudRequestException(String message, Throwable cause) {
+		super(message, cause);
+	}
+
+	public OzgCloudRequestException(String message, String exceptionId) {
+		super(message, exceptionId);
+	}
+
+	public OzgCloudRequestException(String msg, OzgCloudConfig ozgCloudConfig, Throwable cause) {
+		super(msg, cause);
+		this.ozgCloudConfig = ozgCloudConfig;
+	}
+
+	public String getMessageWithConfig() {
+		var messageBuilder = new StringBuilder(super.getMessage());
+		if (isNull(ozgCloudConfig)) {
+			return messageBuilder.toString();
+		}
+		messageBuilder.append("\nPlugin configuration:\n")
+				.append("\t").append(OzgCloudConfig.KEY_EINGANGSADAPTER_URL).append(": ").append(ozgCloudConfig.getEingangsAdapterUrl());
+		if (nonNull(ozgCloudConfig.getProxyConfig())) {
+			messageBuilder.append("\n\t").append(ozgCloudConfig.getProxyConfig());
+		}
+		return messageBuilder.toString();
+	}
+}
diff --git a/src/main/java/de/ozgcloud/formcycle/errorhandling/EWorkflowElementNodeError.java b/src/main/java/de/ozgcloud/formcycle/errorhandling/OzgPluginError.java
similarity index 89%
rename from src/main/java/de/ozgcloud/formcycle/errorhandling/EWorkflowElementNodeError.java
rename to src/main/java/de/ozgcloud/formcycle/errorhandling/OzgPluginError.java
index 9c8eb36f290e517cc72377d91073a5130bc3bb93..4763c2ad9f1ff686720f13adc2768077ab1e9e6a 100644
--- a/src/main/java/de/ozgcloud/formcycle/errorhandling/EWorkflowElementNodeError.java
+++ b/src/main/java/de/ozgcloud/formcycle/errorhandling/OzgPluginError.java
@@ -22,6 +22,6 @@
  */
 package de.ozgcloud.formcycle.errorhandling;
 
-public enum EWorkflowElementNodeError {
-	OZGCLOUD_CANNOT_SEND, PLUGIN_CANNOT_GET_ATTACHMENT, PLUGIN_CONFIG_ERROR
+public enum OzgPluginError {
+	INTERNAL_ERROR, OZGCLOUD_REQUEST_FAILED
 }
diff --git a/src/main/java/de/ozgcloud/formcycle/errorhandling/EWorkflowElementNodeSoftError.java b/src/main/java/de/ozgcloud/formcycle/errorhandling/OzgPluginSoftError.java
similarity index 84%
rename from src/main/java/de/ozgcloud/formcycle/errorhandling/EWorkflowElementNodeSoftError.java
rename to src/main/java/de/ozgcloud/formcycle/errorhandling/OzgPluginSoftError.java
index d83bbe21fe781c045228fe74a5265c7000dea089..6fe907a27509f4a8f7d7b18939edb47cb5dd3d09 100644
--- a/src/main/java/de/ozgcloud/formcycle/errorhandling/EWorkflowElementNodeSoftError.java
+++ b/src/main/java/de/ozgcloud/formcycle/errorhandling/OzgPluginSoftError.java
@@ -29,10 +29,9 @@ package de.ozgcloud.formcycle.errorhandling;
  * practice to define meaningful errors. They are shown in the workflow designer.
  * @author unspecified
  */
-public enum EWorkflowElementNodeSoftError {
-  /** No action was performed because an URL with the wrong host name was entered */
-  WRONG_HOST_NAME,
+public enum OzgPluginSoftError {
+
+  /** Indicates that representation of a formular was not attached to form data */
+  MISSING_REPRESENTATION
 
-  /** No action was performed because the user does not exist. */
-  USER_DOES_NOT_EXIST;
 }
diff --git a/src/main/java/de/ozgcloud/formcycle/errorhandling/UserErrorDispatcher.java b/src/main/java/de/ozgcloud/formcycle/errorhandling/TechnicalException.java
similarity index 56%
rename from src/main/java/de/ozgcloud/formcycle/errorhandling/UserErrorDispatcher.java
rename to src/main/java/de/ozgcloud/formcycle/errorhandling/TechnicalException.java
index f71e3c7be6031d638eead686c4136f54e249cb76..033d4f1620d33464546e96dcd591a5866d96f3bc 100644
--- a/src/main/java/de/ozgcloud/formcycle/errorhandling/UserErrorDispatcher.java
+++ b/src/main/java/de/ozgcloud/formcycle/errorhandling/TechnicalException.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 Das Land Schleswig-Holstein vertreten durch das
+ * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch das
  * Ministerium für Energiewende, Klimaschutz, Umwelt und Natur
  * Zentrales IT-Management
  *
@@ -23,23 +23,32 @@
 
 package de.ozgcloud.formcycle.errorhandling;
 
-import java.util.Optional;
+import lombok.Getter;
 
-import javax.faces.context.FacesContext;
+@Getter
+public class TechnicalException extends RuntimeException {
 
-import org.omnifaces.util.Messages;
 
-import de.ozgcloud.formcycle.ProxyConfig;
+	private final String exceptionId;
 
-public class UserErrorDispatcher {
+	public TechnicalException(String message) {
+		super(message);
+		exceptionId = ExceptionUtil.createExceptionId();
+	}
+
+	public TechnicalException(String message, Throwable cause) {
+		super(message, cause);
+		exceptionId = ExceptionUtil.createExceptionId();
+	}
 
-	static final String PORT_FORMAT_ERROR_MESSAGE = "\"{0}\" hat ein falsches Format";
-	static final String PORT_FORMAT_ERROR_DESCRIPTION = "Port muss eine Zahl zwischen 1 und 65535 sein";
+	public TechnicalException(String message, String exceptionId) {
+		super(message);
+		this.exceptionId = exceptionId;
+	}
 
-	public void sendWrongProxyPortFormatMessage() {
-		Optional.ofNullable(FacesContext.getCurrentInstance())
-				.ifPresent(ctx -> Messages.create(PORT_FORMAT_ERROR_MESSAGE, ProxyConfig.KEY_PROXY_PORT).detail(PORT_FORMAT_ERROR_DESCRIPTION).error()
-						.add());
+	@Override
+	public String getMessage() {
+		return ExceptionUtil.formatMessageWithExceptionId(super.getMessage(), exceptionId);
 	}
 
 }
diff --git a/src/main/java/de/ozgcloud/formcycle/errorhandling/Warning.java b/src/main/java/de/ozgcloud/formcycle/errorhandling/Warning.java
new file mode 100644
index 0000000000000000000000000000000000000000..42be81a725587f9eb8f0ee4e5e0960418af74dc8
--- /dev/null
+++ b/src/main/java/de/ozgcloud/formcycle/errorhandling/Warning.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch das
+ * Ministerium für Energiewende, Klimaschutz, Umwelt und Natur
+ * Zentrales IT-Management
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+
+package de.ozgcloud.formcycle.errorhandling;
+
+import lombok.Builder;
+import lombok.Getter;
+
+@Builder
+@Getter
+public class Warning {
+
+	private OzgPluginSoftError errorCode;
+	private String message;
+	private Object value;
+	private Exception cause;
+
+	public String getErrorCode() {
+		return errorCode.name();
+	}
+}
diff --git a/src/main/resources/WEB-INF/properties/WorkflowElementNodeHelp_de.html b/src/main/resources/WEB-INF/properties/WorkflowElementNodeHelp_de.html
deleted file mode 100644
index 75333f4d8bd53a97c5118da2ab14472c8b2e2fd5..0000000000000000000000000000000000000000
--- a/src/main/resources/WEB-INF/properties/WorkflowElementNodeHelp_de.html
+++ /dev/null
@@ -1,30 +0,0 @@
-<!DOCTYPE html>
-<html>
-
-<head>
-<meta charset="utf-8">
-<title>Hilfeseite: WorkflowElementNode</title>
-<meta name="author" content="unspecified">
-<meta name="description" content="Hilfeseite WorkflowElementNode">
-<meta name="viewport" content="width=device-width, initial-scale=1">
-</head>
-
-<body>
-<body>
-<body>
-	<main>
-		<h1>Plugin WorkflowElementNode</h1>
-		<h2>Einführung</h2>
-		<section>TODO</section>
-		<h2>Konfiguration</h2>
-		<section>TODO</section>
-		<h2>Verwendung</h2>
-		<section>TODO</section>
-	</main>
-
-	<footer>
-		<p>by unspecified</p>
-	</footer>
-</body>
-
-</html>
diff --git a/src/main/resources/WEB-INF/properties/WorkflowElementTriggerHelp_de.html b/src/main/resources/WEB-INF/properties/WorkflowElementTriggerHelp_de.html
deleted file mode 100644
index 7685599bd5a0ee907e3b6554875597ea1e8c3ef8..0000000000000000000000000000000000000000
--- a/src/main/resources/WEB-INF/properties/WorkflowElementTriggerHelp_de.html
+++ /dev/null
@@ -1,30 +0,0 @@
-<!DOCTYPE html>
-<html>
-
-<head>
-<meta charset="utf-8">
-<title>Hilfeseite: WorkflowElementTrigger</title>
-<meta name="author" content="unspecified">
-<meta name="description" content="Hilfeseite für WorkflowElementTrigger">
-<meta name="viewport" content="width=device-width, initial-scale=1">
-</head>
-
-<body>
-<body>
-<body>
-	<main>
-		<h1>Plugin WorkflowElementTrigger</h1>
-		<h2>Einführung</h2>
-		<section>TODO</section>
-		<h2>Konfiguration</h2>
-		<section>TODO</section>
-		<h2>Verwendung</h2>
-		<section>TODO</section>
-	</main>
-
-	<footer>
-		<p>by unspecified</p>
-	</footer>
-</body>
-
-</html>
diff --git a/src/main/resources/WEB-INF/properties/i18n_de.properties b/src/main/resources/WEB-INF/properties/i18n_de.properties
index 1131798a70782701fe542fec1926bcb95bce330a..010bc7487784e9d87f009b2b17b7fcc8a7933ec0 100644
--- a/src/main/resources/WEB-INF/properties/i18n_de.properties
+++ b/src/main/resources/WEB-INF/properties/i18n_de.properties
@@ -1,41 +1,20 @@
+de.ozgcloud.formcycle.OzgCloudPlugin.name     = OZG-Cloud Plugin
+de.ozgcloud.formcycle.OzgCloudPlugin.desc     = Plugin zum Senden von Formulardaten an die OZG-Cloud
+de.ozgcloud.formcycle.OzgCloudPlugin.sublabel = Sendet Formulardaten an die OZG-Cloud
 
-de.ozgcloud.formcycle.WorkflowElementNodePlugin.desc                                         = Das ist ein Plugin, welches zeigt, wie man eine Node zum Workflow hinzuf\u00FCgt.
-de.ozgcloud.formcycle.WorkflowElementNodePlugin.errVal.OZGCLOUD_CANNOT_SEND.message      	= Weitere Information zum Fehler
-de.ozgcloud.formcycle.WorkflowElementNodePlugin.errcode.OZGCLOUD_CANNOT_SEND                 = Die Formulardaten konnten nicht gesendet werden.
-de.ozgcloud.formcycle.WorkflowElementNodePlugin.name                                         = OZG-Cloud Plugin
 # Nachrichten fuer die UI der Aktion
-de.ozgcloud.formcycle.WorkflowElementNodePlugin.retval.success.count                         = Anzahl der zur\u00FCckgelieferten Datens\u00E4tze (Sachprozesse)
-de.ozgcloud.formcycle.WorkflowElementNodePlugin.retval.success.items[i].active               = Ob der Sachprozesse aktiv ist
-de.ozgcloud.formcycle.WorkflowElementNodePlugin.retval.success.items[i].name                 = Name des Sachprozesses
-de.ozgcloud.formcycle.WorkflowElementNodePlugin.retval.success.items[i].state.APPROVED       = Wenn der Sachprozess akzeptiert wurde
-de.ozgcloud.formcycle.WorkflowElementNodePlugin.retval.success.items[i].state.PENDING        = Wenn der Sachprozesse in Bearbeitung ist
-de.ozgcloud.formcycle.WorkflowElementNodePlugin.retval.success.items[i].state.REJECTED       = Wenn der Sachprozesse abgelehnt wurde
-de.ozgcloud.formcycle.WorkflowElementNodePlugin.searchtext                                   = wundervoll plugin aktion
-de.ozgcloud.formcycle.WorkflowElementNodePlugin.softErrVal.WRONG_HOST_NAME.expectedHostName  = Der erwartete Hostname, der benutzt werden sollte.
-de.ozgcloud.formcycle.WorkflowElementNodePlugin.softerrcode.USER_DOES_NOT_EXIST              = Wenn es keinen Nutzer gibt.
-de.ozgcloud.formcycle.WorkflowElementNodePlugin.softerrcode.WRONG_HOST_NAME                  = Wenn der Hostname nicht unterst\u00FCtzt wird.
-de.ozgcloud.formcycle.WorkflowElementNodePlugin.sublabel                                     = Sendet Formulardaten an die OZG-Cloud
-de.ozgcloud.formcycle.WorkflowElementNodePlugin.title                                        = Sendet Formulardaten an die OZG-Cloud
-de.ozgcloud.formcycle.plugin.WorkflowElementTriggerPlugin.data.requestParameters['key'][i]          = i-ter Wert des HTTP-Parameters mit dem Namen 'key'. Ein HTTP-Parameter kann mehrfach angegeben werden und damit mehrere Werte haben.
-# Nachrichten fuer die UI des Triggers
-de.ozgcloud.formcycle.plugin.WorkflowElementTriggerPlugin.data.type                                 = Typ der vom HTTP-Request \u00FCbermittelt wurde
-de.ozgcloud.formcycle.plugin.WorkflowElementTriggerPlugin.desc                                      = Das ist ein Plugin, welches zeigt, wie man ein Ereignis zum Workflow hinzuf\u00FCgt.
-de.ozgcloud.formcycle.plugin.WorkflowElementTriggerPlugin.name                                      = WorkflowElementTrigger
-de.ozgcloud.formcycle.plugin.WorkflowElementTriggerPlugin.searchtext                                = wundervoll plugin ereignis
-de.ozgcloud.formcycle.plugin.WorkflowElementTriggerPlugin.sublabel                                  = Ein tolles Ereignis zum Ausprobieren
-de.ozgcloud.formcycle.plugin.WorkflowElementTriggerPlugin.title                                     = Tats\u00E4chlich ist dieses Ereignis so toll, dass Sie dieses nie wieder entfernen wollen!
-de.ozgcloud.formcycle.plugin.HttpServletActionPlugin.name                                         = HTTP-Request f\u00FCr Ereignis
+de.ozgcloud.formcycle.OzgCloudPlugin.retval.success.vorgangnummer                 = Dem Vorgang zugewiesene Nummer in OZG-Cloud
 
-WorkflowElementNode.desc                  = Dieses Plugin sendet Formulardaten an die OZG-Cloud
-WorkflowElementNode.name                  = OZG-Cloud Plugin
+# Errors
+de.ozgcloud.formcycle.OzgCloudPlugin.errcode.INTERNAL_ERROR      	            = Fehlercode, wenn ein interner Fehler aufgetreten ist.
+de.ozgcloud.formcycle.OzgCloudPlugin.errVal.INTERNAL_ERROR.exceptionId          = ID, um Fehler im System nachverfolgen zu k�nnen.
+de.ozgcloud.formcycle.OzgCloudPlugin.errcode.OZGCLOUD_REQUEST_FAILED             = Fehlercode, wenn die Formulardaten an OZG-Cloud nicht gesendet werden konnten.
+de.ozgcloud.formcycle.OzgCloudPlugin.errVal.OZGCLOUD_REQUEST_FAILED.exceptionId = ID, um Fehler im System nachverfolgen zu k�nnen.
 
-WorkflowElementNodeProps.section_settings = Einstellungen
-WorkflowElementNodeProps.eingangsAdapterUrl = URL f�r die OZG-Cloud
-WorkflowElementNodeProps.useCustomEingangsAdapterUrl = Nutze formularspezifische Eingangsadapter-URL
-WorkflowElementNodeProps.organisationsEinheitId = ID der zust�ndigen Organisationseinheit
+# Soft Errors
+de.ozgcloud.formcycle.OzgCloudPlugin.softerrcode.MISSING_REPRESENTATION          = Weicher Fehlercode, wenn Formular Representation nicht angeh�ngt werden konnte.
 
-WorkflowElementTrigger.desc                  = Dieses Ereignis tritt ein, wenn ein HTTP-Request an das Servlet-Plugin gestellt wird.
-WorkflowElementTrigger.name                  = Mein Plugin f\u00FCr einen Workflow-Trigger
-
-WorkflowElementTriggerProps.section_settings = Einstellungen
-WorkflowElementTriggerProps.type             = Request-Typ
+OzgPluginWorkflowNodeProperties.section_settings = Einstellungen
+OzgPluginWorkflowNodeProperties.eingangsAdapterUrl = URL f�r die OZG-Cloud
+OzgPluginWorkflowNodeProperties.useCustomEingangsAdapterUrl = Nutze formularspezifische Eingangsadapter-URL
+OzgPluginWorkflowNodeProperties.organisationsEinheitId = ID der zust�ndigen Organisationseinheit
\ No newline at end of file
diff --git a/src/main/resources/WEB-INF/ui/WorkflowElementNode.xhtml b/src/main/resources/WEB-INF/ui/OzgCloudPlugin.xhtml
similarity index 83%
rename from src/main/resources/WEB-INF/ui/WorkflowElementNode.xhtml
rename to src/main/resources/WEB-INF/ui/OzgCloudPlugin.xhtml
index 99fa3fa6574bf751f528e90ebf2896c76db72033..eb5e4711cc1c3ece83d4527582f38e7986556556 100644
--- a/src/main/resources/WEB-INF/ui/WorkflowElementNode.xhtml
+++ b/src/main/resources/WEB-INF/ui/OzgCloudPlugin.xhtml
@@ -14,14 +14,14 @@
 	<xi:namingContainer id="WorkflowElementNode">
 
 		<xi:newWorkflowActionBase id="base" value="#{model}"
-			legend="#{msg['WorkflowElementNode.name']}" />
+			legend="#{msg['de.ozgcloud.formcycle.OzgCloudPlugin.name']}"/>
 
-		<p:fieldset legend="#{msg['WorkflowElementNodeProps.section_settings']}"
+		<p:fieldset legend="#{msg['OzgPluginWorkflowNodeProperties.section_settings']}"
 			styleClass="fc-fieldset">
 
 			<xi:inputText id="organisationsEinheitId" value="#{model.organisationsEinheitId}"
 						  forceIndicateRequired="true"
-						  label="#{msg['WorkflowElementNodeProps.organisationsEinheitId']}">
+						  label="#{msg['OzgPluginWorkflowNodeProperties.organisationsEinheitId']}">
 				<p:ajax event="change" partialSubmit="true"
 						listener="#{elementPropertiesBean.storeCurrent}" process="@this"
 						update=":flowchartForm:flowchart" global="false" />
@@ -30,7 +30,7 @@
             <xi:selectBooleanCheckbox
                     id="chkRememberMe"
                     value="#{model.useCustomEingangsAdapterUrl}"
-                    label="#{msg['WorkflowElementNodeProps.useCustomEingangsAdapterUrl']}">
+                    label="#{msg['OzgPluginWorkflowNodeProperties.useCustomEingangsAdapterUrl']}">
                 <p:ajax type="change" process="@this" update="wfPropertiesForm:WorkflowElementNode:panelId"/>
 				<p:ajax event="change" partialSubmit="true"
 						listener="#{elementPropertiesBean.storeCurrent}" process="@this"
@@ -41,7 +41,7 @@
                     <xi:inputText id="eingangsAdapterCustomUrl-id"
                                  value="#{model.eingangsAdapterUrl}"
                                  forceIndicateRequired="true"
-                                 label="#{msg['WorkflowElementNodeProps.eingangsAdapterUrl']}">
+                                 label="#{msg['OzgPluginWorkflowNodeProperties.eingangsAdapterUrl']}">
                         <p:ajax event="change" partialSubmit="true"
                                 listener="#{elementPropertiesBean.storeCurrent}" process="@this"
                                 update=":flowchartForm:flowchart" global="false"/>
diff --git a/src/main/resources/WEB-INF/ui/WorkflowElementTrigger.xhtml b/src/main/resources/WEB-INF/ui/WorkflowElementTrigger.xhtml
deleted file mode 100644
index 40d2b3531dfb93a411bd276c13e0d55250a636ea..0000000000000000000000000000000000000000
--- a/src/main/resources/WEB-INF/ui/WorkflowElementTrigger.xhtml
+++ /dev/null
@@ -1,20 +0,0 @@
-<ui:composition xmlns="http://www.w3.org/1999/xhtml"
-	xmlns:h="http://xmlns.jcp.org/jsf/html"
-	xmlns:f="http://xmlns.jcp.org/jsf/core"
-	xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
-	xmlns:o="http://omnifaces.org/ui" xmlns:p="http://primefaces.org/ui"
-	xmlns:xi="http://www.xima.de/taglib/xfc">
-
-	<xi:validateBean value="#{model}" showMessageFor="@violating"
-		method="validateActual" />
-
-	<p:importConstants type="de.xima.fc.mdl.enums.ETextbausteinKategorie"
-		var="ETextbausteinKategorie" />
-
-	<xi:namingContainer id="WorkflowElementTrigger">
-
-		<xi:newWorkflowActionBase id="base" value="#{model}"
-			legend="#{msg['WorkflowElementTrigger.name']}" />
-			
-	</xi:namingContainer>
-</ui:composition>
diff --git a/src/test/java/de/ozgcloud/formcycle/ExecutionResultTest.java b/src/test/java/de/ozgcloud/formcycle/ExecutionResultTest.java
deleted file mode 100644
index c64aa66e5b4824976b2920fa95c3cc3f408d53d1..0000000000000000000000000000000000000000
--- a/src/test/java/de/ozgcloud/formcycle/ExecutionResultTest.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package de.ozgcloud.formcycle;
-
-import static de.ozgcloud.formcycle.ExecutionResult.*;
-
-import org.apache.commons.lang3.StringUtils;
-import org.assertj.core.api.Assertions;
-import org.assertj.core.data.MapEntry;
-import org.junit.jupiter.api.Test;
-
-class ExecutionResultTest {
-
-	@Test
-	void shouldCreateResultMap() {
-		var vorgangnummer = "1";
-		var executionResult = ExecutionResult.builder().vorgangnummer(vorgangnummer).build();
-
-		var resultPropertyMap = executionResult.get();
-
-		Assertions.assertThat(resultPropertyMap).containsOnly(MapEntry.entry(VORGANGNUMMER_PROPERTY_KEY, vorgangnummer));
-	}
-
-	@Test
-	void shouldAddEmptyValues() {
-		var executionResult = ExecutionResult.builder().build();
-
-		var resultPropertyMap = executionResult.get();
-
-		Assertions.assertThat(resultPropertyMap).containsOnly(MapEntry.entry(VORGANGNUMMER_PROPERTY_KEY, StringUtils.EMPTY));
-	}
-}
\ No newline at end of file
diff --git a/src/test/java/de/ozgcloud/formcycle/ExecutionResultTestFactory.java b/src/test/java/de/ozgcloud/formcycle/ExecutionResultTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..4c73c7208f97a338be1fd022f18477dc98eecfba
--- /dev/null
+++ b/src/test/java/de/ozgcloud/formcycle/ExecutionResultTestFactory.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch das
+ * Ministerium für Energiewende, Klimaschutz, Umwelt und Natur
+ * Zentrales IT-Management
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+
+package de.ozgcloud.formcycle;
+
+import java.util.List;
+
+import de.ozgcloud.formcycle.ExecutionResult.ExecutionResultBuilder;
+import de.ozgcloud.formcycle.errorhandling.Warning;
+
+public class ExecutionResultTestFactory {
+
+	public static final String VORGANGNUMMER = "123456789";
+	public static final Warning WARNING = WarningTestFactory.create();
+
+	public static ExecutionResult create() {
+		return createBuilder().build();
+	}
+
+	public static ExecutionResultBuilder createBuilder() {
+		return ExecutionResult.builder()
+				.vorgangnummer(VORGANGNUMMER)
+				.warnings(List.of(WARNING));
+	}
+}
diff --git a/src/test/java/de/ozgcloud/formcycle/OzgCloudFormDataHttpClientITCase.java b/src/test/java/de/ozgcloud/formcycle/OzgCloudFormDataHttpClientITCase.java
index e2257a3df39c111d59eaec0e835f1ac0b09c0bf7..cb05ea0fb1d1bb4407054c94dfa238447deda5b0 100644
--- a/src/test/java/de/ozgcloud/formcycle/OzgCloudFormDataHttpClientITCase.java
+++ b/src/test/java/de/ozgcloud/formcycle/OzgCloudFormDataHttpClientITCase.java
@@ -24,7 +24,6 @@ package de.ozgcloud.formcycle;
 
 import static org.assertj.core.api.Assertions.*;
 
-import java.io.IOException;
 import java.time.ZonedDateTime;
 import java.util.Collections;
 
@@ -33,25 +32,33 @@ import org.junit.jupiter.api.Test;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.fasterxml.jackson.databind.ObjectMapper;
+
 import de.ozgcloud.eingang.formcycle.FormCycleAttachmentGroup;
 import de.ozgcloud.eingang.formcycle.FormCycleConfirmationResponse;
 import de.ozgcloud.eingang.formcycle.FormCycleFormData;
 import de.ozgcloud.eingang.formcycle.FormCycleFormHeader;
 import de.ozgcloud.eingang.formcycle.FormCycleServiceKonto;
+import de.ozgcloud.formcycle.client.OzgCloudResponseHandler;
 import de.ozgcloud.vorgang.vorgang.GrpcFormData;
+import lombok.SneakyThrows;
 
 class OzgCloudFormDataHttpClientITCase {
 
 	private static final Logger LOG = LoggerFactory.getLogger(OzgCloudFormDataHttpClientITCase.class);
 
+	private final OzgCloudResponseHandler responseHandler = new OzgCloudResponseHandler(new ObjectMapper());
+	private final PortParser portParser = new PortParser();
+
 	@Test
 	@Disabled("Test sollte nur manuell ausgeführt werden, da die Daten in ein Dev System eingeliefert werden")
-	void testReal() throws IOException {
+	@SneakyThrows
+	void testReal() {
 
 		String url = "https://fuerth-formcycle.dev.by.ozg-cloud.de/formData";
 
-		OzgCloudFormDataHttpClient client = new OzgCloudFormDataHttpClient(OzgCloudConfig.builder()
-				.eingangsAdapterUrl(url).build(), new SystemPropertiesProvider());
+		OzgCloudFormDataHttpClient client = new OzgCloudFormDataHttpClient(OzgCloudConfig.builder().eingangsAdapterUrl(url).build(),
+				new SystemPropertiesProvider(), portParser, responseHandler);
 
 		FormCycleConfirmationResponse response = client.send(createTestFormData(), Collections.emptyList(), Collections.emptyList());
 
diff --git a/src/test/java/de/ozgcloud/formcycle/OzgCloudFormDataHttpClientTest.java b/src/test/java/de/ozgcloud/formcycle/OzgCloudFormDataHttpClientTest.java
index 55aa9a9d98d755b9091fea21288e29752b0faa0b..c6c77805cb86e26c9250a20c9f9c2dfded9a278f 100644
--- a/src/test/java/de/ozgcloud/formcycle/OzgCloudFormDataHttpClientTest.java
+++ b/src/test/java/de/ozgcloud/formcycle/OzgCloudFormDataHttpClientTest.java
@@ -24,19 +24,17 @@ package de.ozgcloud.formcycle;
 
 import static de.ozgcloud.formcycle.OzgCloudFormDataHttpClient.*;
 import static org.assertj.core.api.Assertions.*;
+import static org.junit.jupiter.api.Assertions.*;
 import static org.mockito.ArgumentMatchers.*;
 import static org.mockito.Mockito.*;
 
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
+import java.io.IOException;
 import java.lang.reflect.Field;
 import java.util.List;
 import java.util.Optional;
 
-import org.apache.http.HttpEntity;
-import org.apache.http.HttpStatus;
-import org.apache.http.StatusLine;
-import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.HttpHost;
+import org.apache.http.client.methods.HttpPost;
 import org.apache.http.client.protocol.HttpClientContext;
 import org.apache.http.conn.routing.HttpRoutePlanner;
 import org.apache.http.impl.client.BasicCredentialsProvider;
@@ -51,18 +49,24 @@ import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.NullSource;
 import org.junit.jupiter.params.provider.ValueSource;
+import org.mockito.InjectMocks;
 import org.mockito.Mock;
+import org.mockito.Spy;
 import org.springframework.util.ReflectionUtils;
 
-import de.ozgcloud.eingang.formcycle.FormCycleConfirmationResponse;
 import de.ozgcloud.eingang.formcycle.FormCycleFormData;
-import de.ozgcloud.formcycle.OzgCloudFormDataHttpClient.UnexpectedStatusCodeException;
 import de.ozgcloud.formcycle.attachment.FormcycleAttachment;
 import de.ozgcloud.formcycle.attachment.FormcycleAttachmentTestFactory;
+import de.ozgcloud.formcycle.client.OzgCloudResponseHandler;
+import de.ozgcloud.formcycle.errorhandling.OzgCloudRequestException;
 import lombok.SneakyThrows;
 
 class OzgCloudFormDataHttpClientTest {
 
+	@Spy
+	@InjectMocks
+	private OzgCloudFormDataHttpClient client;
+
 	@Mock
 	private SystemPropertiesProvider systemPropertiesProvider;
 	@Mock
@@ -70,37 +74,31 @@ class OzgCloudFormDataHttpClientTest {
 	@Mock
 	private CloseableHttpClient httpClient;
 	@Mock
-	private CloseableHttpResponse closeableHttpResponse;
+	private OzgCloudConfig ozgCloudConfig;
+	@Mock
+	private PortParser portParser;
+	@Mock
+	private OzgCloudResponseHandler responseHandler;
 
 	private List<FormcycleAttachment> attachments;
 	private List<FormcycleAttachment> representations;
 
-	private OzgCloudFormDataHttpClient client;
-
-	@BeforeEach
-	void setup() {
-		client = createOzgClient(OzgCloudConfigTestFactory.proxyConfig);
-	}
-
-	private OzgCloudFormDataHttpClient createOzgClient(ProxyConfig proxyConfig) {
-		return spy(new OzgCloudFormDataHttpClient(OzgCloudConfigTestFactory.createBuilder()
-				.proxyConfig(proxyConfig).build(), systemPropertiesProvider));
-	}
-
 	@Nested
 	class TestSendData {
 
+		@Mock
+		private HttpPost postRequest;
+		@Mock
+		private HttpContext httpContext;
+
 		@SneakyThrows
 		@BeforeEach
 		void setup() {
-			when(formData.toByteArray()).thenReturn(new byte[] {});
 			attachments = List.of(FormcycleAttachmentTestFactory.create());
 			representations = List.of(FormcycleAttachmentTestFactory.create());
-
-			when(httpClient.execute(any(), any(HttpContext.class))).thenReturn(closeableHttpResponse);
-
 			doReturn(httpClient).when(client).createCloseableClient();
-			doReturn(FormCycleConfirmationResponseTestFactory.create()).when(client).parseResponse(any());
+			doReturn(postRequest).when(client).buildPostRequest(any(), any(), any());
+			doReturn(httpContext).when(client).getContext();
 		}
 
 		@SneakyThrows
@@ -114,57 +112,39 @@ class OzgCloudFormDataHttpClientTest {
 		@SneakyThrows
 		@Test
 		void shouldSendData() {
-			client.send(formData, attachments, representations);
-
-			verify(client).createCloseableClient();
-			verify(httpClient).execute(any(), any(HttpContext.class));
-		}
+			when(httpClient.execute(any(), any(OzgCloudResponseHandler.class), any())).thenReturn(FormCycleConfirmationResponseTestFactory.create());
 
-		@SneakyThrows
-		@Test
-		void shouldCallParseRequest() {
-			client.send(formData, attachments, representations);
+			var response = client.send(formData, attachments, representations);
 
-			verify(client).parseResponse(any());
+			assertThat(response).isEqualTo(FormCycleConfirmationResponseTestFactory.create());
 		}
-	}
 
-	@Nested
-	class TestParseREsponse {
-		@Mock
-		private CloseableHttpResponse httpResponse;
-		@Mock
-		private HttpEntity entityMock;
-		@Mock
-		private StatusLine statusLineMock;
+		@Nested
+		class TestThrowException {
 
-		private FormCycleConfirmationResponse response = FormCycleConfirmationResponseTestFactory.create();
+			@Mock
+			private IOException ioException;
 
-		@BeforeEach
-		@SneakyThrows
-		void init() {
-			when(httpResponse.getEntity()).thenReturn(entityMock);
-			when(httpResponse.getStatusLine()).thenReturn(statusLineMock);
-			when(statusLineMock.getStatusCode()).thenReturn(HttpStatus.SC_OK);
-
-			ByteArrayOutputStream out = new ByteArrayOutputStream();
-			response.writeTo(out);
-			when(entityMock.getContent()).thenReturn(new ByteArrayInputStream(out.toByteArray()));
-		}
+			private OzgCloudRequestException exception = spy(new OzgCloudRequestException("message"));
 
-		@Test
-		@SneakyThrows
-		void shouldParseResponse() {
-			var result = client.parseResponse(httpResponse);
+			@SneakyThrows
+			@Test
+			void shouldAddOzgCloudConfig() {
+				when(httpClient.execute(any(), any(OzgCloudResponseHandler.class), any(HttpContext.class))).thenThrow(exception);
 
-			assertThat(result).isEqualTo(response);
-		}
+				assertThrows(OzgCloudRequestException.class, () -> client.send(formData, attachments, representations));
+				verify(exception).setOzgCloudConfig(ozgCloudConfig);
+			}
 
-		@Test
-		void shouldThrowException() {
-			when(statusLineMock.getStatusCode()).thenReturn(HttpStatus.SC_SERVICE_UNAVAILABLE);
+			@SneakyThrows
+			@Test
+			void shouldWrapIOException() {
+				when(httpClient.execute(any(), any(OzgCloudResponseHandler.class), any(HttpContext.class))).thenThrow(ioException);
 
-			assertThatThrownBy(()->client.parseResponse(httpResponse)).isInstanceOf(UnexpectedStatusCodeException.class);
+				var thrownException = assertThrows(OzgCloudRequestException.class, () -> client.send(formData, attachments, representations));
+				assertThat(thrownException.getCause()).isEqualTo(ioException);
+				assertThat(thrownException).extracting("ozgCloudConfig").isEqualTo(ozgCloudConfig);
+			}
 		}
 	}
 
@@ -227,9 +207,14 @@ class OzgCloudFormDataHttpClientTest {
 
 		@Mock
 		private HttpClientBuilder httpClientBuilder;
+		@Mock
+		private ProxyConfig proxyConfig;
 
 		@Test
 		void shouldCallConfigureProxy() {
+			doReturn(proxyConfig).when(client).getProxyConfig();
+			doNothing().when(client).configureProxy(any());
+
 			client.addProxy(httpClientBuilder);
 
 			verify(client).configureProxy(httpClientBuilder);
@@ -241,11 +226,21 @@ class OzgCloudFormDataHttpClientTest {
 
 		@Mock
 		private HttpClientBuilder httpClientBuilder;
+		@Mock
+		private HttpHost proxyHost;
+		@Mock
+		private BasicCredentialsProvider credentialsProvider;
+		@Mock
+		private HttpRoutePlanner routePlanner;
+
+		@BeforeEach
+		void setup() {
+			doReturn(routePlanner).when(client).createHttpRouterPlanner();
+			doReturn(credentialsProvider).when(client).getCredentialsProvider();
+		}
 
 		@Test
 		void shouldCallSetRouterPlanner() {
-			var routePlanner = mock(HttpRoutePlanner.class);
-			doReturn(routePlanner).when(client).createHttpRouterPlanner();
 
 			client.configureProxy(httpClientBuilder);
 
@@ -254,8 +249,6 @@ class OzgCloudFormDataHttpClientTest {
 
 		@Test
 		void shouldCallSetCredentialProvider() {
-			var credentialsProvider = mock(BasicCredentialsProvider.class);
-			doReturn(credentialsProvider).when(client).getCredentialsProvider();
 
 			client.configureProxy(httpClientBuilder);
 
@@ -268,6 +261,8 @@ class OzgCloudFormDataHttpClientTest {
 
 		@Test
 		void shouldReturnFromOzgCloudConfig() {
+			when(client.getProxyConfig()).thenReturn(OzgCloudConfigTestFactory.proxyConfig);
+
 			var proxyConfig = client.getProxyConfig();
 
 			assertThat(proxyConfig).isEqualTo(OzgCloudConfigTestFactory.proxyConfig);
@@ -275,8 +270,6 @@ class OzgCloudFormDataHttpClientTest {
 
 		@Test
 		void shouldCallLoadSystemProxyConfig() {
-			client = createOzgClient(ProxyConfigTestFactory.createEmpty());
-
 			client.getProxyConfig();
 
 			verify(client).loadSystemProxyConfig();
@@ -284,7 +277,7 @@ class OzgCloudFormDataHttpClientTest {
 
 		@Test
 		void shouldReturnNullWhenNoProxy() {
-			client = createOzgClient(ProxyConfigTestFactory.createEmpty());
+			//			client = createOzgClient(ProxyConfigTestFactory.createEmpty());
 			doReturn(Optional.empty()).when(client).loadSystemProxyConfig();
 
 			var proxyConfig = client.getProxyConfig();
@@ -356,7 +349,7 @@ class OzgCloudFormDataHttpClientTest {
 
 		@Test
 		void shouldSetProxyPort() {
-			when(systemPropertiesProvider.getHttpsProxyPort()).thenReturn(PropertiesTestFactory.PROXY_PORT);
+			when(portParser.parse(any())).thenReturn(ProxyConfigTestFactory.PROXY_PORT);
 
 			var proxyConfig = client.loadHttpsProxyConfig();
 
@@ -411,7 +404,7 @@ class OzgCloudFormDataHttpClientTest {
 
 		@Test
 		void shouldSetProxyPort() {
-			when(systemPropertiesProvider.getHttpProxyPort()).thenReturn(PropertiesTestFactory.PROXY_PORT);
+			when(portParser.parse(any())).thenReturn(ProxyConfigTestFactory.PROXY_PORT);
 
 			var proxyConfig = client.loadHttpProxyConfig();
 
@@ -464,9 +457,14 @@ class OzgCloudFormDataHttpClientTest {
 		@Nested
 		class TestCreateContext {
 
+			@Mock
+			private BasicCredentialsProvider credentialsProvider;
+			@Mock
+			private ProxyConfig proxyConfig;
+
 			@Test
 			void shouldSetCredentialsProvider() {
-				var credentialsProvider = mock(BasicCredentialsProvider.class);
+				doReturn(proxyConfig).when(client).getProxyConfig();
 				doReturn(credentialsProvider).when(client).getCredentialsProvider();
 
 				var result = client.createContext();
diff --git a/src/test/java/de/ozgcloud/formcycle/OzgCloudPluginTest.java b/src/test/java/de/ozgcloud/formcycle/OzgCloudPluginTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..c3c732df72ba55fec01ef18424249e53444f33c3
--- /dev/null
+++ b/src/test/java/de/ozgcloud/formcycle/OzgCloudPluginTest.java
@@ -0,0 +1,274 @@
+package de.ozgcloud.formcycle;
+
+import static de.ozgcloud.formcycle.ExecutionResultTestFactory.*;
+import static de.ozgcloud.formcycle.OzgCloudPlugin.*;
+import static org.assertj.core.api.Assertions.*;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import java.util.Map;
+import java.util.Optional;
+import java.util.Properties;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+
+import de.ozgcloud.formcycle.errorhandling.NodeThrewExceptionFactory;
+import de.ozgcloud.formcycle.errorhandling.OzgCloudRequestException;
+import de.ozgcloud.formcycle.errorhandling.TechnicalException;
+import de.xima.fc.exceptions.NodeThrewException;
+import de.xima.fc.interfaces.workflow.execution.INormalCompletionResult;
+import de.xima.fc.interfaces.workflow.params.INodeExecutionParams;
+import de.xima.fc.interfaces.workflow.params.INormalCompletionResultBuilder;
+import de.xima.fc.interfaces.workflow.value.IRecordValueDescriptorBuilder;
+import de.xima.fc.interfaces.workflow.value.IStringValueDescriptor;
+import de.xima.fc.interfaces.workflow.value.IValueDescriptor;
+import de.xima.fc.interfaces.workflow.value.IValueDescriptorFactory;
+import lombok.SneakyThrows;
+
+class OzgCloudPluginTest {
+
+	@Spy
+	@InjectMocks
+	private OzgCloudPlugin plugin;
+
+	@Mock
+	private OzgPluginPropertiesSupplier propertiesMapper;
+	@Mock
+	private INodeExecutionParams<OzgPluginWorkflowNodeProperties> invocationParameters;
+
+	@Nested
+	@DisplayName("Success value descriptor")
+	class TestSuccessValueDescriptor {
+
+		@Mock
+		private IValueDescriptorFactory descriptorFactory;
+		@Mock
+		private IRecordValueDescriptorBuilder descriptorBuilder;
+		@Mock
+		private IStringValueDescriptor stringValueDescriptor;
+
+		@BeforeEach
+		void setup() {
+			when(descriptorFactory.recordBuilder()).thenReturn(descriptorBuilder);
+			when(descriptorBuilder.requiredProperty(any(), any(IValueDescriptor.class))).thenReturn(descriptorBuilder);
+		}
+
+		@Test
+		@DisplayName("should has vorgangnummer property")
+		void shouldAddVorgangnummerProperty() {
+			when(descriptorFactory.string()).thenReturn(stringValueDescriptor);
+
+			plugin.getSuccessValueDescriptor(descriptorFactory);
+
+			verify(descriptorBuilder).requiredProperty(VORGANGNUMMER_PROPERTY_KEY, stringValueDescriptor);
+		}
+	}
+
+	@Nested
+	class TestCreateOzgClient {
+
+		@Mock
+		private OzgPluginWorkflowNodeProperties formProperties;
+		@Mock
+		private NodeThrewExceptionFactory exceptionFactory;
+
+		@Test
+		@SneakyThrows
+		void shouldCallBuildOzgCloudConfig() {
+			when(invocationParameters.getData()).thenReturn(formProperties);
+			doReturn("").when(plugin).getEingangsAdapterUrl(any());
+
+			plugin.createOzgCloudClient(invocationParameters);
+
+			verify(plugin).buildOzgCloudConfig(formProperties);
+		}
+
+		@Nested
+		class TestEingangsAdapterUrl {
+
+			private static final String CUSTOM_URL = "http://custom.url";
+
+			@Test
+			void shouldReturnCustomUrl() {
+				when(formProperties.getEingangsAdapterUrl()).thenReturn(CUSTOM_URL);
+				when(formProperties.isUseCustomEingangsAdapterUrl()).thenReturn(true);
+
+				var eingangsAdapterUrl = plugin.getEingangsAdapterUrl(formProperties);
+
+				assertThat(eingangsAdapterUrl).isEqualTo(CUSTOM_URL);
+			}
+
+			@Test
+			void shouldReturnDefaultUrl() {
+				when(formProperties.isUseCustomEingangsAdapterUrl()).thenReturn(false);
+				when(propertiesMapper.getEingangsAdapterUrl()).thenReturn(PropertiesTestFactory.EINGANGSADAPTER_URL);
+
+				var eingangsAdapterUrl = plugin.getEingangsAdapterUrl(formProperties);
+
+				assertThat(eingangsAdapterUrl).isEqualTo(PropertiesTestFactory.EINGANGSADAPTER_URL);
+			}
+
+			@Test
+			void shouldThrowExceptionIfNoUrl() {
+				when(formProperties.isUseCustomEingangsAdapterUrl()).thenReturn(false);
+
+				assertThrows(TechnicalException.class, () -> plugin.getEingangsAdapterUrl(formProperties));
+			}
+		}
+
+		@Nested
+		class TestBuildOzgClientConfig {
+
+			@Mock
+			private Properties properties;
+
+			@Test
+			void shouldSetEingangsAdapterUrl() {
+				doReturn(PropertiesTestFactory.EINGANGSADAPTER_URL).when(plugin).getEingangsAdapterUrl(any());
+
+				var ozgClientConfig = buildOzgCloudConfig();
+
+				assertThat(ozgClientConfig.getEingangsAdapterUrl()).isEqualTo(PropertiesTestFactory.EINGANGSADAPTER_URL);
+			}
+
+			@SneakyThrows
+			@Test
+			void shouldSetProxyConfig() {
+				var expectedProxyConfig = ProxyConfigTestFactory.create();
+				when(propertiesMapper.getEingangsAdapterUrl()).thenReturn(PropertiesTestFactory.EINGANGSADAPTER_URL);
+				when(propertiesMapper.getProxyProperties()).thenReturn(Optional.of(expectedProxyConfig));
+
+				var ozgClientConfig = buildOzgCloudConfig();
+
+				assertThat(ozgClientConfig.getProxyConfig()).isEqualTo(expectedProxyConfig);
+			}
+
+			@SneakyThrows
+			private OzgCloudConfig buildOzgCloudConfig() {
+				return plugin.buildOzgCloudConfig(formProperties);
+			}
+		}
+	}
+
+	@Nested
+	class TestBuildSuccessResult {
+
+		@Mock
+		private INormalCompletionResultBuilder resultBuilder;
+		@Captor
+		private ArgumentCaptor<Map<String, String>> resultCaptor;
+
+		@BeforeEach
+		public void setup() {
+			when(invocationParameters.normalResult()).thenReturn(resultBuilder);
+			when(resultBuilder.success(resultCaptor.capture())).thenReturn(resultBuilder);
+			when(resultBuilder.softError(any(), any(), any(), any())).thenReturn(resultBuilder);
+		}
+
+		@Test
+		void shouldSetVorgangnummer() {
+			plugin.buildSuccessResult(create(), invocationParameters);
+
+			assertThat(resultCaptor.getValue()).containsExactly(Map.entry(VORGANGNUMMER_PROPERTY_KEY, VORGANGNUMMER));
+		}
+
+		@Test
+		void shouldSetWarnings() {
+			plugin.buildSuccessResult(create(), invocationParameters);
+
+			verify(resultBuilder).softError(WARNING.getErrorCode(), WARNING.getMessage(), WARNING.getValue(), WARNING.getCause());
+		}
+	}
+
+	@Nested
+	class TestExecute {
+
+		@Mock
+		private OzgPluginExecutor pluginExecutor;
+		@Mock
+		private ExecutionResult executionResult;
+		@Mock
+		private INormalCompletionResult pluginCompletionResult;
+
+		@BeforeEach
+		void setup() {
+			doReturn(pluginExecutor).when(plugin).createPluginExecutor(any());
+		}
+
+		@Test
+		void shouldCallBuildSuccessResult() {
+			when(pluginExecutor.execute()).thenReturn(executionResult);
+			doReturn(pluginCompletionResult).when(plugin).buildSuccessResult(any(), any());
+
+			executePlugin();
+
+			verify(plugin).buildSuccessResult(executionResult, invocationParameters);
+		}
+
+		@Nested
+		class TestCreateNodeThrewException {
+
+			@Mock
+			private NodeThrewExceptionFactory exceptionFactory;
+			@Mock
+			private OzgCloudRequestException ozgCloudRequestException;
+			@Mock
+			private TechnicalException technicalException;
+			@Mock
+			private RuntimeException unexpectedException;
+			@Mock
+			private NodeThrewException nodeThrewException;
+
+			@BeforeEach
+			void setup() {
+				doReturn(exceptionFactory).when(plugin).createExceptionFactory(any());
+			}
+
+			@Test
+			void shouldCallCreateOzgCloudRequestException() {
+				when(pluginExecutor.execute()).thenThrow(ozgCloudRequestException);
+				when(exceptionFactory.createOzgCloudRequestException(any())).thenReturn(nodeThrewException);
+
+				executePlugin();
+
+				verify(exceptionFactory).createOzgCloudRequestException(ozgCloudRequestException);
+			}
+
+			@Test
+			void shouldCallCreateInternalException() {
+				when(pluginExecutor.execute()).thenThrow(technicalException);
+				when(exceptionFactory.createInternalException(any())).thenReturn(nodeThrewException);
+
+				executePlugin();
+
+				verify(exceptionFactory).createInternalException(technicalException);
+			}
+
+			@Test
+			void shouldCallCreateInternalExceptionWhenUnexpectedThrown() {
+				when(pluginExecutor.execute()).thenThrow(unexpectedException);
+				when(exceptionFactory.createInternalException(any(), any())).thenReturn(nodeThrewException);
+
+				executePlugin();
+
+				verify(exceptionFactory).createInternalException(any(), eq(unexpectedException));
+			}
+		}
+
+		void executePlugin() {
+			try {
+				plugin.execute(invocationParameters);
+			} catch (Exception e) {
+			}
+		}
+
+	}
+}
\ No newline at end of file
diff --git a/src/test/java/de/ozgcloud/formcycle/WorkflowElementNodeExecutorTest.java b/src/test/java/de/ozgcloud/formcycle/OzgPluginExecutorTest.java
similarity index 91%
rename from src/test/java/de/ozgcloud/formcycle/WorkflowElementNodeExecutorTest.java
rename to src/test/java/de/ozgcloud/formcycle/OzgPluginExecutorTest.java
index ef68358ce6aa9bb6d4e8c06ea831ad57f22997bb..74b6ec69fc7a0f818a94ca3937729f33d7bc9832 100644
--- a/src/test/java/de/ozgcloud/formcycle/WorkflowElementNodeExecutorTest.java
+++ b/src/test/java/de/ozgcloud/formcycle/OzgPluginExecutorTest.java
@@ -26,7 +26,6 @@ import static org.assertj.core.api.Assertions.*;
 import static org.mockito.ArgumentMatchers.*;
 import static org.mockito.Mockito.*;
 
-import java.io.IOException;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
@@ -50,17 +49,18 @@ import de.ozgcloud.formcycle.attachment.AttachmentType;
 import de.ozgcloud.formcycle.attachment.FormcycleAttachment;
 import de.ozgcloud.formcycle.attachment.FormcycleAttachmentTestFactory;
 import de.ozgcloud.formcycle.errorhandling.NodeThrewExceptionFactory;
+import de.ozgcloud.formcycle.errorhandling.OzgPluginSoftError;
 import de.ozgcloud.formcycle.formdata.FormData;
 import de.ozgcloud.formcycle.formdata.OzgCloudFormDataMapper;
 import de.ozgcloud.formcycle.formdata.PluginFormDataAdapter;
 import de.xima.fc.entities.Attachment;
 import lombok.SneakyThrows;
 
-class WorkflowElementNodeExecutorTest {
+class OzgPluginExecutorTest {
 
 	@Spy
 	@InjectMocks
-	private WorkflowElementNodeExecutor executor;
+	private OzgPluginExecutor executor;
 
 	@Mock
 	private OzgCloudFormDataMapper ozgCloudFormDataMapper;
@@ -119,7 +119,7 @@ class WorkflowElementNodeExecutorTest {
 			void shouldCreateSuccessResult() {
 				var executionResult = executor.execute();
 
-				assertThat(executionResult.get()).containsEntry(ExecutionResult.VORGANGNUMMER_PROPERTY_KEY, VORGANGS_NUMMER);
+				assertThat(executionResult.getVorgangnummer()).isEqualTo(VORGANGS_NUMMER);
 			}
 		}
 
@@ -180,29 +180,6 @@ class WorkflowElementNodeExecutorTest {
 		}
 	}
 
-	@Nested
-	@DisplayName("By error")
-	class TestException {
-
-		@Mock
-		private FormData formData;
-
-		@SneakyThrows
-		@Test
-		void shouldThrowException() {
-			when(attachmentSupplier.get()).thenReturn(List.of());
-			when(pluginFormDataAdapter.readFormData()).thenReturn(formData);
-			when(ozgHttpClient.send(any(), any(), any())).thenThrow(IOException.class);
-
-			try {
-				executor.execute();
-			} catch (NullPointerException e) {
-			}
-
-			verify(exceptionFactory).createCannotSendException(any(), any());
-		}
-	}
-
 	@Nested
 	@DisplayName("Attachment's handling")
 	class TestGetAttachments {
@@ -267,4 +244,23 @@ class WorkflowElementNodeExecutorTest {
 		}
 	}
 
+	@Nested
+	class TestBuildWarning {
+
+		private final String MESSAGE = "message";
+
+		@Test
+		void shouldSetErrorCode() {
+			var warning = executor.buildWarning(MESSAGE);
+
+			assertThat(warning.getErrorCode()).isEqualTo(OzgPluginSoftError.MISSING_REPRESENTATION.toString());
+		}
+
+		@Test
+		void shouldSetMessage() {
+			var warning = executor.buildWarning(MESSAGE);
+
+			assertThat(warning.getMessage()).isEqualTo(MESSAGE);
+		}
+	}
 }
\ No newline at end of file
diff --git a/src/test/java/de/ozgcloud/formcycle/OzgPluginPropertiesSupplierTest.java b/src/test/java/de/ozgcloud/formcycle/OzgPluginPropertiesSupplierTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..a30ce39e069ca5170290fa3dd8b4888c725b9921
--- /dev/null
+++ b/src/test/java/de/ozgcloud/formcycle/OzgPluginPropertiesSupplierTest.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2023 Das Land Schleswig-Holstein vertreten durch das
+ * Ministerium für Energiewende, Klimaschutz, Umwelt und Natur
+ * Zentrales IT-Management
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+
+package de.ozgcloud.formcycle;
+
+import static de.ozgcloud.formcycle.PropertiesTestFactory.*;
+import static java.util.Objects.*;
+import static org.assertj.core.api.Assertions.*;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import java.util.Properties;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+
+import de.ozgcloud.formcycle.errorhandling.TechnicalException;
+import de.xima.fc.interfaces.plugin.lifecycle.IPluginInitializeData;
+
+class OzgPluginPropertiesSupplierTest {
+	@Spy
+	@InjectMocks
+	private OzgPluginPropertiesSupplier mapper;
+
+	@Mock
+	private IPluginInitializeData pluginData;
+	@Mock
+	private PortParser portParser;
+
+	private Properties properties = PropertiesTestFactory.create();
+
+	@Nested
+	class TestMapProxyProperty {
+
+		@Mock
+		private ProxyConfig proxyConfig;
+
+		@Test
+		void shouldCallValidateProxyConfig() {
+			doReturn(properties).when(pluginData).getProperties();
+
+			mapper.getProxyProperties();
+
+			verify(mapper).validateProxyConfig(properties);
+		}
+
+		@Test
+		void shouldReturnProxyConfig() {
+			doReturn(properties).when(pluginData).getProperties();
+			doNothing().when(mapper).validateProxyConfig(any());
+			doReturn(proxyConfig).when(mapper).buildProxyConfig(any());
+
+			var result = mapper.getProxyProperties();
+
+			assertThat(result).isPresent().get().isEqualTo(proxyConfig);
+		}
+
+		@Test
+		void shouldReturnEmptyWhenNoProxy() {
+			doReturn(PropertiesTestFactory.createEmpty()).when(pluginData).getProperties();
+
+			var result = mapper.getProxyProperties();
+
+			assertThat(result).isEmpty();
+		}
+	}
+
+	@Nested
+	class TestBuildProxyConfig {
+
+		@Test
+		void shouldSetHost() {
+			var result = mapper.buildProxyConfig(properties);
+
+			assertThat(result.getHost()).isEqualTo(PROXY_HOST);
+		}
+
+		@Test
+		void shouldSetPort() {
+			doReturn(1).when(mapper).getPort(any());
+
+			var result = mapper.buildProxyConfig(properties);
+
+			assertThat(result.getPort()).isEqualTo(1);
+		}
+
+		@Test
+		void shouldSetUser() {
+			var result = mapper.buildProxyConfig(properties);
+
+			assertThat(result.getUser()).isEqualTo(PROXY_USER);
+		}
+
+		@Test
+		void shouldSetPassword() {
+			var result = mapper.buildProxyConfig(properties);
+
+			assertThat(result.getPassword()).isEqualTo(PROXY_PASSWORD);
+		}
+
+	}
+
+	@Nested
+	class TestValidateProxyConfig {
+
+		@DisplayName(value = "should fail when")
+		@ParameterizedTest(name = "host: \"{0}\" and port: \"{1}\"")
+		@CsvSource({ "host, ", ", 1", "'',1", "host,' '" })
+		void shouldFailWhen(String host, String port) {
+			var properties = createProperties(host, port);
+
+			assertThrows(TechnicalException.class, () -> mapper.validateProxyConfig(properties));
+		}
+
+		@Test
+		void shouldValidate() {
+			assertDoesNotThrow(() -> mapper.validateProxyConfig(properties));
+		}
+
+		private Properties createProperties(String host, String port) {
+			var properties = new Properties();
+			if (nonNull(host)) {
+				properties.setProperty(ProxyConfig.KEY_PROXY_HOST, host);
+			}
+			if (nonNull(port)) {
+				properties.setProperty(ProxyConfig.KEY_PROXY_PORT, port);
+			}
+			return properties;
+		}
+	}
+
+	@Nested
+	class TestGetPort {
+
+		@Test
+		void shouldReturnNull() {
+			var result = mapper.getPort(PropertiesTestFactory.createEmpty());
+
+			assertThat(result).isNull();
+		}
+
+		@Test
+		void shouldCallParsePort() {
+			mapper.getPort(properties);
+
+			verify(portParser).parse(PROXY_PORT);
+		}
+	}
+}
\ No newline at end of file
diff --git a/src/test/java/de/ozgcloud/formcycle/PluginPropertiesMapperTest.java b/src/test/java/de/ozgcloud/formcycle/PluginPropertiesMapperTest.java
deleted file mode 100644
index 1955324effb56cfd9e987eb9eb8d90bceed0dd05..0000000000000000000000000000000000000000
--- a/src/test/java/de/ozgcloud/formcycle/PluginPropertiesMapperTest.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (C) 2023 Das Land Schleswig-Holstein vertreten durch das
- * Ministerium für Energiewende, Klimaschutz, Umwelt und Natur
- * Zentrales IT-Management
- *
- * Lizenziert unter der EUPL, Version 1.2 oder - sobald
- * diese von der Europäischen Kommission genehmigt wurden -
- * Folgeversionen der EUPL ("Lizenz");
- * Sie dürfen dieses Werk ausschließlich gemäß
- * dieser Lizenz nutzen.
- * Eine Kopie der Lizenz finden Sie hier:
- *
- * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
- *
- * Sofern nicht durch anwendbare Rechtsvorschriften
- * gefordert oder in schriftlicher Form vereinbart, wird
- * die unter der Lizenz verbreitete Software "so wie sie
- * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
- * ausdrücklich oder stillschweigend - verbreitet.
- * Die sprachspezifischen Genehmigungen und Beschränkungen
- * unter der Lizenz sind dem Lizenztext zu entnehmen.
- */
-
-package de.ozgcloud.formcycle;
-
-import static de.ozgcloud.formcycle.PropertiesTestFactory.*;
-import static org.assertj.core.api.Assertions.*;
-import static org.mockito.Mockito.*;
-
-import java.util.Properties;
-
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.DisplayName;
-import org.junit.jupiter.api.Nested;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.params.ParameterizedTest;
-import org.junit.jupiter.params.provider.ValueSource;
-import org.mockito.Mock;
-
-import de.ozgcloud.formcycle.errorhandling.UserErrorDispatcher;
-
-class PluginPropertiesMapperTest {
-
-	private PluginPropertiesMapper mapper;
-
-	@Mock
-	private UserErrorDispatcher userErrorDispatcher;
-
-	private Properties properties = PropertiesTestFactory.create();
-
-	@BeforeEach
-	void setUp() {
-		mapper = spy(new PluginPropertiesMapper(userErrorDispatcher));
-	}
-
-	@Nested
-	class TestMapProxyProperty {
-
-		@Test
-		void shouldSetHost() {
-			var result = mapper.mapProxyProperties(properties);
-
-			assertThat(result.getHost()).isEqualTo(PROXY_HOST);
-		}
-
-		@Test
-		void shouldSetPort(){
-			doReturn(1).when(mapper).getPort(any());
-
-			var result = mapper.mapProxyProperties(properties);
-
-			assertThat(result.getPort()).isEqualTo(1);
-		}
-
-		@Test
-		void shouldSetUser() {
-			var result = mapper.mapProxyProperties(properties);
-
-			assertThat(result.getUser()).isEqualTo(PROXY_USER);
-		}
-
-		@Test
-		void shouldSetPassword() {
-			var result = mapper.mapProxyProperties(properties);
-
-			assertThat(result.getPassword()).isEqualTo(PROXY_PASSWORD);
-		}
-
-	}
-
-	@Nested
-	class TestGetPort {
-
-		@DisplayName("should send user error message when")
-		@ParameterizedTest(name = "port property is {0}")
-		@ValueSource(strings = {"0", "65536", "abs"})
-		void shouldSendUserErrorMessage(String portProperty) {
-			mapper.getPort(createPortProperty(portProperty));
-
-			verify(userErrorDispatcher).sendWrongProxyPortFormatMessage();
-		}
-
-		@Test
-		void shouldReturnPort() {
-			var result = mapper.getPort(create());
-
-			assertThat(result).isEqualTo(1);
-		}
-	}
-}
\ No newline at end of file
diff --git a/src/test/java/de/ozgcloud/formcycle/UtilsTest.java b/src/test/java/de/ozgcloud/formcycle/PortParserTest.java
similarity index 66%
rename from src/test/java/de/ozgcloud/formcycle/UtilsTest.java
rename to src/test/java/de/ozgcloud/formcycle/PortParserTest.java
index 96b8741289c6f23532e25337d4c0cd7cae36963e..58f006b371b2d06f8811de2aa073610c7f115fdc 100644
--- a/src/test/java/de/ozgcloud/formcycle/UtilsTest.java
+++ b/src/test/java/de/ozgcloud/formcycle/PortParserTest.java
@@ -24,27 +24,39 @@ package de.ozgcloud.formcycle;
 
 import static org.assertj.core.api.Assertions.*;
 
+import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
-import org.junit.jupiter.params.provider.NullSource;
+import org.junit.jupiter.params.provider.NullAndEmptySource;
 import org.junit.jupiter.params.provider.ValueSource;
+import org.mockito.InjectMocks;
 
-class UtilsTest {
+class PortParserTest {
 
-	@DisplayName("should return null when")
+	@InjectMocks
+	private PortParser portParser;
+
+	@DisplayName("should throw exception when")
 	@ParameterizedTest(name = "port property is \"{0}\"")
-	@NullSource
-	@ValueSource(strings = {"", "0", "65536", "abs"})
+	@ValueSource(strings = { "-1", "0", "65536", "abs" })
 	void shouldReturnNull(String portProperty) {
-		var result = Utils.parsePort(portProperty);
+		Assertions.assertThrows(IllegalArgumentException.class, () -> portParser.parse(portProperty));
+	}
+
+	@DisplayName("should return null when")
+	@ParameterizedTest(name = "port property is \"{0}\"")
+	@NullAndEmptySource
+	@ValueSource(strings = { " " })
+	void shouldReturnNullWhenPortPropertyIsBlank(String portProperty) {
+		var port = portParser.parse(portProperty);
 
-		assertThat(result).isNull();
+		assertThat(port).isNull();
 	}
 
 	@Test
 	void shouldReturnPort() {
-		var result = Utils.parsePort("1");
+		var result = portParser.parse("1");
 
 		assertThat(result).isEqualTo(1);
 	}
diff --git a/src/test/java/de/ozgcloud/formcycle/PropertiesTestFactory.java b/src/test/java/de/ozgcloud/formcycle/PropertiesTestFactory.java
index aa6caf0cfa281471f03ae788aca0dddae5ea1fe5..8d634384f73584631cc423392835df23c334f3cd 100644
--- a/src/test/java/de/ozgcloud/formcycle/PropertiesTestFactory.java
+++ b/src/test/java/de/ozgcloud/formcycle/PropertiesTestFactory.java
@@ -36,6 +36,7 @@ public class PropertiesTestFactory {
 	public static Properties createEmpty() {
 		return new Properties();
 	}
+
 	public static Properties create() {
 		var properties = createEmpty();
 		properties.setProperty(OzgCloudConfig.KEY_EINGANGSADAPTER_URL, EINGANGSADAPTER_URL);
diff --git a/src/test/java/de/ozgcloud/formcycle/WarningTestFactory.java b/src/test/java/de/ozgcloud/formcycle/WarningTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..410198d7d7c002f6e7044551a436051e41ffcdda
--- /dev/null
+++ b/src/test/java/de/ozgcloud/formcycle/WarningTestFactory.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch das
+ * Ministerium für Energiewende, Klimaschutz, Umwelt und Natur
+ * Zentrales IT-Management
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+
+package de.ozgcloud.formcycle;
+
+import de.ozgcloud.formcycle.errorhandling.OzgPluginSoftError;
+import de.ozgcloud.formcycle.errorhandling.Warning;
+import de.ozgcloud.formcycle.errorhandling.Warning.WarningBuilder;
+
+public class WarningTestFactory {
+
+	public static final OzgPluginSoftError ERROR_CODE = OzgPluginSoftError.MISSING_REPRESENTATION;
+	public static final String MESSAGE = "Missing representation";
+	public static final String VALUE = "value";
+	public static final Exception CAUSE = new Exception("cause");
+
+	public static Warning create() {
+		return createBuilder().build();
+	}
+
+	public static WarningBuilder createBuilder() {
+		return Warning.builder()
+				.errorCode(ERROR_CODE)
+				.message(MESSAGE)
+				.value(VALUE)
+				.cause(CAUSE);
+	}
+}
diff --git a/src/test/java/de/ozgcloud/formcycle/WorkflowElementNodePluginTest.java b/src/test/java/de/ozgcloud/formcycle/WorkflowElementNodePluginTest.java
deleted file mode 100644
index d851698ee47a3867d9c03a9e51bc1a0fc4ec1ba0..0000000000000000000000000000000000000000
--- a/src/test/java/de/ozgcloud/formcycle/WorkflowElementNodePluginTest.java
+++ /dev/null
@@ -1,195 +0,0 @@
-package de.ozgcloud.formcycle;
-
-import static org.assertj.core.api.Assertions.*;
-import static org.mockito.Mockito.*;
-
-import java.util.Properties;
-
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.DisplayName;
-import org.junit.jupiter.api.Nested;
-import org.junit.jupiter.api.Test;
-import org.mockito.InjectMocks;
-import org.mockito.Mock;
-import org.mockito.Spy;
-import org.springframework.util.ReflectionUtils;
-
-import de.ozgcloud.formcycle.errorhandling.NodeThrewExceptionFactory;
-import de.xima.fc.interfaces.plugin.lifecycle.IPluginInitializeData;
-import de.xima.fc.interfaces.workflow.value.IRecordValueDescriptorBuilder;
-import de.xima.fc.interfaces.workflow.value.IStringValueDescriptor;
-import de.xima.fc.interfaces.workflow.value.IValueDescriptor;
-import de.xima.fc.interfaces.workflow.value.IValueDescriptorFactory;
-import lombok.SneakyThrows;
-
-class WorkflowElementNodePluginTest {
-
-	@Spy
-	@InjectMocks
-	private WorkflowElementNodePlugin plugin;
-
-	@Mock
-	private IPluginInitializeData initializeData;
-	@Mock
-	private PluginPropertiesMapper propertiesMapper;
-
-	@Nested
-	class TestInitialization {
-
-		@Test
-		void shouldCallCreatePropertiesMapper() {
-			plugin.initialize(initializeData);
-
-			verify(plugin).createPropertiesMapper();
-		}
-
-		@Test
-		void shouldSetInitializeData() {
-			plugin.initialize(initializeData);
-
-			assertThat(getInitializedData()).isEqualTo(initializeData);
-		}
-
-		private IPluginInitializeData getInitializedData() {
-			var initializeDataField = ReflectionUtils.findField(WorkflowElementNodePlugin.class, "initializeData");
-			initializeDataField.setAccessible(true);
-			return (IPluginInitializeData) ReflectionUtils.getField(initializeDataField, plugin);
-		}
-
-	}
-
-	@Nested
-	@DisplayName("Success value descriptor")
-	class TestSuccessValueDescriptor {
-
-		@Mock
-		private IValueDescriptorFactory descriptorFactory;
-		@Mock
-		private IRecordValueDescriptorBuilder descriptorBuilder;
-		@Mock
-		private IStringValueDescriptor stringValueDescriptor;
-
-		@BeforeEach
-		void setup() {
-			when(descriptorFactory.recordBuilder()).thenReturn(descriptorBuilder);
-			when(descriptorBuilder.requiredProperty(any(), any(IValueDescriptor.class))).thenReturn(descriptorBuilder);
-		}
-
-		@Test
-		@DisplayName("should has vorgangnummer property")
-		void shouldAddVorgangnummerProperty() {
-			when(descriptorFactory.string()).thenReturn(stringValueDescriptor);
-
-			plugin.getSuccessValueDescriptor(descriptorFactory);
-
-			verify(descriptorBuilder).requiredProperty(ExecutionResult.VORGANGNUMMER_PROPERTY_KEY, stringValueDescriptor);
-		}
-	}
-
-	@Nested
-	class TestCreateOzgClient {
-
-		@Mock
-		private WorkflowElementNodeProps formProperties;
-		@Mock
-		private NodeThrewExceptionFactory exceptionFactory;
-
-		private OzgCloudConfig ozgCloudConfig = OzgCloudConfigTestFactory.create();
-
-		@Test
-		@SneakyThrows
-		void shouldSetOzgCloudConfig() {
-			doReturn(ozgCloudConfig).when(plugin).buildOzgCloudConfig(any(), any());
-
-			var ozgClient = plugin.createOzgClient(formProperties, exceptionFactory);
-
-			assertThat(getOzgCloudConfig(ozgClient)).isEqualTo(ozgCloudConfig);
-		}
-
-		@Nested
-		class TestEingangsAdapterUrl {
-
-			private static final String CUSTOM_URL = "http://custom.url";
-
-			@Test
-			void shouldReturnCustomUrl() {
-				when(formProperties.getEingangsAdapterUrl()).thenReturn(CUSTOM_URL);
-				when(formProperties.isUseCustomEingangsAdapterUrl()).thenReturn(true);
-
-				var eingangsAdapterUrl = plugin.getEingangsAdapterUrl(formProperties);
-
-				assertThat(eingangsAdapterUrl).isEqualTo(CUSTOM_URL);
-			}
-
-			@Test
-			void shouldReturnDefaultUrl() {
-				when(formProperties.isUseCustomEingangsAdapterUrl()).thenReturn(false);
-				when(propertiesMapper.getEingangsAdapterUrl(any())).thenReturn(PropertiesTestFactory.EINGANGSADAPTER_URL);
-
-				var eingangsAdapterUrl = plugin.getEingangsAdapterUrl(formProperties);
-
-				assertThat(eingangsAdapterUrl).isEqualTo(PropertiesTestFactory.EINGANGSADAPTER_URL);
-			}
-
-		}
-
-		@Nested
-		class TestBuildOzgClientConfig {
-
-			@Mock
-			private Properties properties;
-
-			@Test
-			void shouldSetEingangsAdapterUrl() {
-				doReturn(PropertiesTestFactory.EINGANGSADAPTER_URL).when(plugin).getEingangsAdapterUrl(any());
-
-				var ozgClientConfig = buildOzgCloudConfig();
-
-				assertThat(ozgClientConfig.getEingangsAdapterUrl()).isEqualTo(PropertiesTestFactory.EINGANGSADAPTER_URL);
-			}
-
-			@Test
-			void shouldSetProxyConfig() {
-				var expectedProxyConfig = ProxyConfigTestFactory.create();
-				when(propertiesMapper.getEingangsAdapterUrl(any())).thenReturn(PropertiesTestFactory.EINGANGSADAPTER_URL);
-				when(propertiesMapper.mapProxyProperties(any())).thenReturn(expectedProxyConfig);
-
-				var ozgClientConfig = buildOzgCloudConfig();
-
-				assertThat(ozgClientConfig.getProxyConfig()).isEqualTo(expectedProxyConfig);
-			}
-
-			@Test
-			void shouldCallMapProxyProperties() {
-				doReturn(PropertiesTestFactory.EINGANGSADAPTER_URL).when(plugin).getEingangsAdapterUrl(any());
-				when(initializeData.getProperties()).thenReturn(properties);
-
-				buildOzgCloudConfig();
-
-				verify(propertiesMapper).mapProxyProperties(properties);
-			}
-
-			@Test
-			void shouldCallCreateException() {
-				try {
-					buildOzgCloudConfig();
-				} catch (NullPointerException e) {
-					// ignore exception
-				}
-
-				verify(exceptionFactory).createConfigurationException(anyString());
-			}
-
-			@SneakyThrows
-			private OzgCloudConfig buildOzgCloudConfig() {
-				return plugin.buildOzgCloudConfig(formProperties, exceptionFactory);
-			}
-		}
-
-		private OzgCloudConfig getOzgCloudConfig(OzgCloudFormDataHttpClient ozgClient) {
-			var ozgCloudConfigField = ReflectionUtils.findField(OzgCloudFormDataHttpClient.class, "config");
-			ozgCloudConfigField.setAccessible(true);
-			return (OzgCloudConfig) ReflectionUtils.getField(ozgCloudConfigField, ozgClient);
-		}
-	}
-}
\ No newline at end of file
diff --git a/src/test/java/de/ozgcloud/formcycle/attachment/AttachmentMapperTest.java b/src/test/java/de/ozgcloud/formcycle/attachment/AttachmentMapperTest.java
index e13d9a5c510615b091e47e48a15eca0816031b62..a0601fb17eb151986ee61626d36d6055936e9389 100644
--- a/src/test/java/de/ozgcloud/formcycle/attachment/AttachmentMapperTest.java
+++ b/src/test/java/de/ozgcloud/formcycle/attachment/AttachmentMapperTest.java
@@ -24,8 +24,10 @@ package de.ozgcloud.formcycle.attachment;
 
 import static de.ozgcloud.formcycle.attachment.FormcycleAttachmentTestFactory.*;
 import static org.assertj.core.api.Assertions.*;
+import static org.junit.jupiter.api.Assertions.*;
 import static org.mockito.Mockito.*;
 
+import org.apache.http.entity.ContentType;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.DisplayName;
@@ -33,8 +35,11 @@ import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
+import org.mockito.Spy;
+import org.mockito.stubbing.Stubber;
 
 import de.ozgcloud.formcycle.errorhandling.NodeThrewExceptionFactory;
+import de.ozgcloud.formcycle.errorhandling.TechnicalException;
 import de.xima.fc.entities.Attachment;
 import de.xima.fc.entities.AttachmentDatei;
 import de.xima.fc.exceptions.NodeThrewException;
@@ -42,20 +47,48 @@ import lombok.SneakyThrows;
 
 class AttachmentMapperTest {
 
+	@Spy
 	@InjectMocks
 	private AttachmentMapper mapper;
 
-	@Mock
-	private NodeThrewExceptionFactory exceptionFactory;
 	@Mock
 	private Attachment attachment;
 
 	@Nested
-	@DisplayName("Set fields")
-	class TestSetFields {
+	class TestMap {
+
+		@Mock
+		private AttachmentDatei attachmentDatei;
+
+		@BeforeEach
+		void setup() {
+			when(attachment.getFileEntity()).thenReturn(attachmentDatei);
+		}
+
+		@Test
+		void shouldThrowException() {
+			assertThrows(TechnicalException.class, () -> mapper.map(attachment));
+		}
+
+		@Test
+		void shouldReturnResult() {
+			var expectedFormcycleAttachment = FormcycleAttachmentTestFactory.create();
+			doReturn(expectedFormcycleAttachment).when(mapper).buildAttachment(any());
+			when(attachmentDatei.getDaten()).thenReturn(CONTENT);
+
+			var result = mapper.map(attachment);
+
+			assertThat(result).isEqualTo(expectedFormcycleAttachment);
+		}
+	}
+
+	@Nested
+	class TestBuildAttachment {
 
 		@Mock
 		private AttachmentDatei attachmentDatei;
+		@Mock
+		private ContentType contentType;
 
 		@BeforeEach
 		void setup() {
@@ -89,9 +122,11 @@ class AttachmentMapperTest {
 
 		@Test
 		void shouldCreateAttachmentWithContentType() {
+			doReturn(contentType).when(mapper).getContentType(any());
+
 			var ozgAttachment = buildAttachments();
 
-			assertThat(ozgAttachment.getContentType().getMimeType()).isEqualTo(FormcycleAttachmentTestFactory.CONTENT_TYPE.getMimeType());
+			assertThat(ozgAttachment.getContentType()).isEqualTo(contentType);
 		}
 
 		@SneakyThrows
@@ -101,33 +136,23 @@ class AttachmentMapperTest {
 	}
 
 	@Nested
-	class TestExceptions {
-
-		@Mock
-		private AttachmentDatei attachmentDatei;
+	class TestGetContentType {
 
 		@BeforeEach
 		void setup() {
-			when(attachment.getFileEntity()).thenReturn(attachmentDatei);
-			when(exceptionFactory.createCannotGetAttachmentException(any())).thenReturn(mock(NodeThrewException.class));
+//			when(exceptionFactory.createMissingRepresentationException(any())).thenReturn(mock(NodeThrewException.class));
 		}
 
 		@Test
-		@DisplayName("should throw when can not read attached data")
 		void shouldThrowException() {
-			Assertions.assertThrows(NodeThrewException.class, () -> mapper.map(attachment));
-
-			verify(exceptionFactory).createCannotGetAttachmentException(AttachmentMapper.CANNOT_READ_ATTACHED_FILE);
+			assertThrows(TechnicalException.class, () -> mapper.getContentType(null));
 		}
 
 		@Test
-		@DisplayName("should throw exception when file name is null")
-		void shouldThrowExceptionName() {
-			when(attachmentDatei.getDaten()).thenReturn(new byte[]{});
-
-			Assertions.assertThrows(NodeThrewException.class, () -> mapper.map(attachment));
+		void shouldReturnResult() {
+			var result = mapper.getContentType(FILE_NAME);
 
-			verify(exceptionFactory).createCannotGetAttachmentException(AttachmentMapper.FILENAME_IS_NULL);
+			assertThat(result.getMimeType()).isEqualTo(CONTENT_TYPE.getMimeType());
 		}
 	}
 }
\ No newline at end of file
diff --git a/src/test/java/de/ozgcloud/formcycle/attachment/FormcycleAttachmentTestFactory.java b/src/test/java/de/ozgcloud/formcycle/attachment/FormcycleAttachmentTestFactory.java
index 5b16895bb7c629b3e87870f5c3141c1854cb0532..7dbb6461e0503ac7598d1079bc118a214ac2bfdc 100644
--- a/src/test/java/de/ozgcloud/formcycle/attachment/FormcycleAttachmentTestFactory.java
+++ b/src/test/java/de/ozgcloud/formcycle/attachment/FormcycleAttachmentTestFactory.java
@@ -19,7 +19,7 @@ public class FormcycleAttachmentTestFactory {
 		return createBuilder().build();
 	}
 
-	private static FormcycleAttachmentBuilder createBuilder() {
+	static FormcycleAttachmentBuilder createBuilder() {
 		return FormcycleAttachment.builder()
 				.uuid(UUID)
 				.fileName(FILE_NAME)
diff --git a/src/test/java/de/ozgcloud/formcycle/client/OzgCloudResponseHandlerTest.java b/src/test/java/de/ozgcloud/formcycle/client/OzgCloudResponseHandlerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..3d8dc0e3bcc61660b7f69bbba6cd4171b2b1ff62
--- /dev/null
+++ b/src/test/java/de/ozgcloud/formcycle/client/OzgCloudResponseHandlerTest.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch das
+ * Ministerium für Energiewende, Klimaschutz, Umwelt und Natur
+ * Zentrales IT-Management
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+
+package de.ozgcloud.formcycle.client;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.StatusLine;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import de.ozgcloud.eingang.formcycle.FormCycleConfirmationResponse;
+import de.ozgcloud.formcycle.errorhandling.OzgCloudRequestException;
+import lombok.SneakyThrows;
+
+class OzgCloudResponseHandlerTest {
+
+	@Spy
+	@InjectMocks
+	private OzgCloudResponseHandler responseHandler;
+
+	@Mock
+	private ObjectMapper objectMapper;
+
+	@Mock
+	private HttpResponse httpResponse;
+
+	@Nested
+	class TestHandleResponse {
+
+		@Mock
+		private HttpEntity httpEntity;
+
+		@SneakyThrows
+		@BeforeEach
+		void setUp() {
+			doNothing().when(responseHandler).checkResponseStatus(any());
+		}
+
+		@SneakyThrows
+		@Test
+		void shouldCheckResponseStatus() {
+			when(httpResponse.getEntity()).thenReturn(httpEntity);
+			when(httpEntity.getContent()).thenReturn(InputStream.nullInputStream());
+
+			responseHandler.handleResponse(httpResponse);
+
+			verify(responseHandler).checkResponseStatus(httpResponse);
+		}
+
+		@SneakyThrows
+		@Test
+		void shouldReturnVorgangNummer() {
+			var expectedConformationResponse = FormCycleConfirmationResponse.newBuilder().setVorgangNummer("123").build();
+			doReturn(expectedConformationResponse).when(responseHandler).buildResponse(httpResponse);
+
+			var response = responseHandler.handleResponse(httpResponse);
+
+			assertThat(response).isEqualTo(expectedConformationResponse);
+		}
+
+	}
+
+	@Nested
+	class TestCheckResponseStatus {
+
+		@Mock
+		private StatusLine statusLine;
+
+		private final OzgCloudErrorDto errorDto = new OzgCloudErrorDto();
+		private final String message = "message";
+		private final String exceptionId = "exception-id";
+
+		@BeforeEach
+		void setUp() {
+			errorDto.setMessage(message);
+			errorDto.setExceptionId(exceptionId);
+		}
+
+		@Test
+		void shouldPassWhenOk() {
+			when(statusLine.getStatusCode()).thenReturn(HttpStatus.SC_OK);
+			when(httpResponse.getStatusLine()).thenReturn(statusLine);
+
+			assertDoesNotThrow(() -> responseHandler.checkResponseStatus(httpResponse));
+		}
+
+		@Test
+		void shouldThrowExceptionWhenError() {
+			when(statusLine.getStatusCode()).thenReturn(HttpStatus.SC_BAD_REQUEST);
+			when(httpResponse.getStatusLine()).thenReturn(statusLine);
+			doReturn(errorDto).when(responseHandler).getOzgCloudError(any());
+
+			var ozgCloudRequestException = assertThrows(OzgCloudRequestException.class, () -> responseHandler.checkResponseStatus(httpResponse));
+
+			assertThat(ozgCloudRequestException.getMessage()).startsWith(message);
+			assertThat(ozgCloudRequestException.getExceptionId()).isEqualTo(exceptionId);
+		}
+
+		@Test
+		void shouldThrowExceptionWhenNotOk() {
+			when(statusLine.getStatusCode()).thenReturn(HttpStatus.SC_NO_CONTENT);
+			when(httpResponse.getStatusLine()).thenReturn(statusLine);
+			doReturn(message).when(responseHandler).buildFullMessage(any());
+
+			var ozgCloudRequestException = assertThrows(OzgCloudRequestException.class, () -> responseHandler.checkResponseStatus(httpResponse));
+
+			assertThat(ozgCloudRequestException.getMessage()).startsWith(message);
+		}
+	}
+
+	@Nested
+	class TestBuildFullMessage {
+
+		@Mock
+		private HttpEntity httpEntity;
+
+		@Mock
+		private StatusLine statusLine;
+
+		private String messageBody = "message-body";
+
+		@SneakyThrows
+		@BeforeEach
+		void setUp() {
+			when(statusLine.toString()).thenReturn(String.valueOf(HttpStatus.SC_BAD_REQUEST));
+			when(httpResponse.getStatusLine()).thenReturn(statusLine);
+			var inputStream = IOUtils.toInputStream(messageBody, StandardCharsets.UTF_8);
+			when(httpEntity.getContent()).thenReturn(inputStream);
+			when(httpResponse.getEntity()).thenReturn(httpEntity);
+		}
+
+		@Test
+		void shouldBuildMessage() {
+			var expectedMessage = String.format(OzgCloudResponseHandler.MESSAGE, HttpStatus.SC_BAD_REQUEST) + "\n" + messageBody;
+
+			var message = responseHandler.buildFullMessage(httpResponse);
+
+			assertThat(message).isEqualTo(expectedMessage);
+		}
+
+	}
+}
\ No newline at end of file
diff --git a/src/test/java/de/ozgcloud/formcycle/errorhandling/NodeThrewExceptionFactoryTest.java b/src/test/java/de/ozgcloud/formcycle/errorhandling/NodeThrewExceptionFactoryTest.java
index fc9ed294f733899cb4353c9db23a573d37193429..2235fd882268800d9730c51e993af1dd17fb9f7e 100644
--- a/src/test/java/de/ozgcloud/formcycle/errorhandling/NodeThrewExceptionFactoryTest.java
+++ b/src/test/java/de/ozgcloud/formcycle/errorhandling/NodeThrewExceptionFactoryTest.java
@@ -3,7 +3,7 @@ package de.ozgcloud.formcycle.errorhandling;
 import static org.assertj.core.api.Assertions.*;
 import static org.mockito.Mockito.*;
 
-import java.io.IOException;
+import java.util.Map;
 
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.DisplayName;
@@ -15,13 +15,13 @@ import org.mockito.InjectMocks;
 import org.mockito.Mock;
 import org.mockito.Spy;
 
-import com.alibaba.fastjson.JSONObject;
-
 import de.ozgcloud.formcycle.attachment.FormcycleAttachment;
 import de.xima.fc.interfaces.workflow.params.INodeThrewExceptionBuilder;
 
 class NodeThrewExceptionFactoryTest {
 
+	private static final String EXCEPTION_ID = "exception-id";
+
 	@Spy
 	@InjectMocks
 	private NodeThrewExceptionFactory exceptionFactory;
@@ -30,74 +30,187 @@ class NodeThrewExceptionFactoryTest {
 	private INodeThrewExceptionBuilder exceptionBuilder;
 
 	@Captor
-	private ArgumentCaptor<String> errorKeyCaptor;
+	private ArgumentCaptor<String> stringCaptor;
 	@Captor
-	private ArgumentCaptor<JSONObject> errorDataCaptor;
+	private ArgumentCaptor<Map<String, String>> errorDataCaptor;
 	@Captor
-	private ArgumentCaptor<IOException> errorCauseCaptor;
+	private ArgumentCaptor<Exception> errorCauseCaptor;
 	@Mock
 	private FormcycleAttachment expectedAttachments;
 
 	@BeforeEach
 	void setup() {
-		when(exceptionBuilder.error(errorKeyCaptor.capture(), errorDataCaptor.capture())).thenReturn(exceptionBuilder);
-		when(exceptionBuilder.message(anyString())).thenReturn(exceptionBuilder);
+		when(exceptionBuilder.error(any(), any())).thenReturn(exceptionBuilder);
+		when(exceptionBuilder.cause(any())).thenReturn(exceptionBuilder);
 	}
 
+	@DisplayName("Create internal exception")
 	@Nested
-	@DisplayName("When Can Not Send exception will throw")
-	class TestCanNotSend {
+	class TestCreateInternalException {
 
-		@BeforeEach
-		void setup() {
-			when(exceptionBuilder.cause(errorCauseCaptor.capture())).thenReturn(exceptionBuilder);
-		}
+		@DisplayName("form TechnicalException")
+		@Nested
+		class TestFromTechnicalException {
+			@Mock
+			private TechnicalException exception;
 
-		@Test
-		void shouldAddErrorCode() {
-			exceptionFactory.createCannotSendException("", mock(IOException.class));
+			@BeforeEach
+			void setup() {
+				when(exception.getExceptionId()).thenReturn(EXCEPTION_ID);
+			}
 
-			assertThat(errorKeyCaptor.getValue()).isEqualTo(EWorkflowElementNodeError.OZGCLOUD_CANNOT_SEND.name());
-		}
+			@Test
+			void shouldAddErrorCode() {
+				when(exceptionBuilder.message(any())).thenReturn(exceptionBuilder);
+				when(exceptionBuilder.error(stringCaptor.capture(), any())).thenReturn(exceptionBuilder);
 
-		@Test
-		void shouldAddExceptionMessage() {
-			var message = "error";
+				exceptionFactory.createInternalException(exception);
+
+				assertThat(stringCaptor.getValue()).isEqualTo(OzgPluginError.INTERNAL_ERROR.name());
+			}
+
+			@Test
+			void shouldAddExceptionId() {
+				when(exceptionBuilder.message(any())).thenReturn(exceptionBuilder);
+				when(exceptionBuilder.error(any(), errorDataCaptor.capture())).thenReturn(exceptionBuilder);
 
-			exceptionFactory.createCannotSendException(message, mock(IOException.class));
+				exceptionFactory.createInternalException(exception);
 
-			assertThat(errorDataCaptor.getValue()).containsEntry(NodeThrewExceptionFactory.ERROR_MESSAGE_KEY, message);
+				var errorData = errorDataCaptor.getValue();
+				assertThat(errorData).containsEntry(NodeThrewExceptionFactory.EXCEPTION_ID_KEY, EXCEPTION_ID);
+			}
+
+			@Test
+			void shouldAddExceptionMessage() {
+				var errorMessage = "error message";
+				when(exception.getMessage()).thenReturn(errorMessage);
+				when(exceptionBuilder.message(stringCaptor.capture())).thenReturn(exceptionBuilder);
+
+				exceptionFactory.createInternalException(exception);
+
+				assertThat(stringCaptor.getValue()).isEqualTo(errorMessage);
+			}
+
+			@Test
+			void shouldAddCause() {
+				when(exceptionBuilder.message(any())).thenReturn(exceptionBuilder);
+				when(exceptionBuilder.cause(errorCauseCaptor.capture())).thenReturn(exceptionBuilder);
+
+				exceptionFactory.createInternalException(exception);
+
+				assertThat(errorCauseCaptor.getValue()).isEqualTo(exception);
+			}
 		}
 
-		@Test
-		void shouldAddCause() {
-			var causeException = mock(IOException.class);
+		@DisplayName("from message and exception")
+		@Nested
+		class TestFromException {
+
+			private static final String EXCEPTION_MESSAGE = "exception message";
+			@Mock
+			private Exception exception;
+
+			@Test
+			void shouldAddErrorCode() {
+				when(exceptionBuilder.message(any())).thenReturn(exceptionBuilder);
+				when(exceptionBuilder.error(stringCaptor.capture(), any())).thenReturn(exceptionBuilder);
+
+				createException();
+
+				assertThat(stringCaptor.getValue()).isEqualTo(OzgPluginError.INTERNAL_ERROR.name());
+			}
+
+			@Test
+			void shouldAddExceptionId() {
+				doReturn(Map.of(NodeThrewExceptionFactory.EXCEPTION_ID_KEY, EXCEPTION_ID)).when(exceptionFactory).createDataMap(any());
+				when(exceptionBuilder.message(any())).thenReturn(exceptionBuilder);
+				when(exceptionBuilder.error(any(), errorDataCaptor.capture())).thenReturn(exceptionBuilder);
 
-			exceptionFactory.createCannotSendException("", causeException);
+				createException();
 
-			assertThat(errorCauseCaptor.getValue()).isEqualTo(causeException);
+				var errorData = errorDataCaptor.getValue();
+				assertThat(errorData).containsEntry(NodeThrewExceptionFactory.EXCEPTION_ID_KEY, EXCEPTION_ID);
+			}
+
+			@Test
+			void shouldAddExceptionMessage() {
+				when(exceptionBuilder.message(stringCaptor.capture())).thenReturn(exceptionBuilder);
+
+				createException();
+
+				assertThat(stringCaptor.getValue()).contains(EXCEPTION_MESSAGE, "(ExceptionId:");
+			}
+
+			@Test
+			void shouldAddCause() {
+				when(exceptionBuilder.message(any())).thenReturn(exceptionBuilder);
+				when(exceptionBuilder.cause(errorCauseCaptor.capture())).thenReturn(exceptionBuilder);
+
+				createException();
+
+				assertThat(errorCauseCaptor.getValue()).isEqualTo(exception);
+			}
+
+			private void createException() {
+				exceptionFactory.createInternalException(EXCEPTION_MESSAGE, exception);
+			}
 		}
 	}
 
 	@Nested
-	@DisplayName("When error by reading of attachments")
-	class TestCanNotGetAttachments {
+	class TestFromOzgCloudRequestException {
+		@Mock
+		private OzgCloudRequestException exception;
+
+		@BeforeEach
+		void setup() {
+			when(exception.getExceptionId()).thenReturn(EXCEPTION_ID);
+		}
 
 		@Test
 		void shouldAddErrorCode() {
-			exceptionFactory.createCannotGetAttachmentException("");
+			when(exceptionBuilder.message(any())).thenReturn(exceptionBuilder);
+			when(exceptionBuilder.error(stringCaptor.capture(), any())).thenReturn(exceptionBuilder);
+
+			createException();
 
-			assertThat(errorKeyCaptor.getValue()).isEqualTo(EWorkflowElementNodeError.PLUGIN_CANNOT_GET_ATTACHMENT.name());
+			assertThat(stringCaptor.getValue()).isEqualTo(OzgPluginError.OZGCLOUD_REQUEST_FAILED.name());
+		}
+
+		@Test
+		void shouldAddExceptionId() {
+			when(exceptionBuilder.message(any())).thenReturn(exceptionBuilder);
+			when(exceptionBuilder.error(any(), errorDataCaptor.capture())).thenReturn(exceptionBuilder);
+
+			createException();
+
+			var errorData = errorDataCaptor.getValue();
+			assertThat(errorData).containsEntry(NodeThrewExceptionFactory.EXCEPTION_ID_KEY, EXCEPTION_ID);
 		}
 
 		@Test
 		void shouldAddExceptionMessage() {
-			var message = "error";
+			var errorMessage = "error message";
+			when(exception.getMessageWithConfig()).thenReturn(errorMessage);
+			when(exceptionBuilder.message(stringCaptor.capture())).thenReturn(exceptionBuilder);
+
+			createException();
+
+			assertThat(stringCaptor.getValue()).isEqualTo(errorMessage);
+		}
+
+		@Test
+		void shouldAddCause() {
+			when(exceptionBuilder.message(any())).thenReturn(exceptionBuilder);
+			when(exceptionBuilder.cause(errorCauseCaptor.capture())).thenReturn(exceptionBuilder);
 
-			exceptionFactory.createCannotGetAttachmentException(message);
+			createException();
 
-			assertThat(errorDataCaptor.getValue()).containsEntry(NodeThrewExceptionFactory.ERROR_MESSAGE_KEY, message);
+			assertThat(errorCauseCaptor.getValue()).isEqualTo(exception);
 		}
 
+		private void createException() {
+			exceptionFactory.createOzgCloudRequestException(exception);
+		}
 	}
 }
\ No newline at end of file