diff --git a/orkes-client/src/main/java/io/orkes/conductor/client/OrkesClients.java b/orkes-client/src/main/java/io/orkes/conductor/client/OrkesClients.java
index 5016bf87..1c26d042 100644
--- a/orkes-client/src/main/java/io/orkes/conductor/client/OrkesClients.java
+++ b/orkes-client/src/main/java/io/orkes/conductor/client/OrkesClients.java
@@ -61,6 +61,14 @@ public OrkesTokenClient getTokenClient() {
return new OrkesTokenClient(client);
}
+ public OrkesSharedResourceClient getSharedResourceClient() {
+ return new OrkesSharedResourceClient(client);
+ }
+
+ public OrkesWebhookClient getWebhookClient() {
+ return new OrkesWebhookClient(client);
+ }
+
public IntegrationClient getIntegrationClient() {
return new OrkesIntegrationClient(client);
}
diff --git a/orkes-client/src/main/java/io/orkes/conductor/client/http/OrkesSharedResourceClient.java b/orkes-client/src/main/java/io/orkes/conductor/client/http/OrkesSharedResourceClient.java
new file mode 100644
index 00000000..f1e2cdf8
--- /dev/null
+++ b/orkes-client/src/main/java/io/orkes/conductor/client/http/OrkesSharedResourceClient.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2022 Conductor Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package io.orkes.conductor.client.http;
+
+import java.util.List;
+
+import com.netflix.conductor.client.http.ConductorClient;
+
+import io.orkes.conductor.client.model.SharedResourceModel;
+
+/**
+ * Client for managing shared resources in Orkes Conductor.
+ * Provides functionality to share various types of resources (workflows, tasks, secrets, etc.)
+ * with other users, groups, or applications within the Conductor ecosystem.
+ */
+public class OrkesSharedResourceClient {
+
+ private final SharedResource sharedResource;
+
+ /**
+ * Constructs a new OrkesSharedResourceClient with the specified ConductorClient.
+ *
+ * @param client the ConductorClient to use for making HTTP requests to the server
+ */
+ public OrkesSharedResourceClient(ConductorClient client) {
+ this.sharedResource = new SharedResource(client);
+ }
+
+ /**
+ * Shares a resource with another user, group, or application.
+ *
+ * @param resourceType the type of resource to share (e.g., "WORKFLOW", "TASK", "SECRET")
+ * @param resourceName the name/identifier of the specific resource to share
+ * @param sharedWith the identifier of the entity to share with (user email, group name, or application ID)
+ */
+ public void shareResource(String resourceType, String resourceName, String sharedWith) {
+ sharedResource.shareResource(resourceType, resourceName, sharedWith);
+ }
+
+ /**
+ * Removes sharing access for a resource from a specific entity.
+ *
+ * @param resourceType the type of resource to stop sharing (e.g., "WORKFLOW", "TASK", "SECRET")
+ * @param resourceName the name/identifier of the specific resource to stop sharing
+ * @param sharedWith the identifier of the entity to remove sharing from (user email, group name, or application ID)
+ */
+ public void removeSharingResource(String resourceType, String resourceName, String sharedWith) {
+ sharedResource.removeSharingResource(resourceType, resourceName, sharedWith);
+ }
+
+ /**
+ * Retrieves a list of all resources that are currently shared by or with the current user/application.
+ *
+ * @return a list of SharedResourceModel objects representing all shared resources
+ */
+ public List getSharedResources() {
+ return sharedResource.getSharedResources();
+ }
+}
\ No newline at end of file
diff --git a/orkes-client/src/main/java/io/orkes/conductor/client/http/OrkesWebhookClient.java b/orkes-client/src/main/java/io/orkes/conductor/client/http/OrkesWebhookClient.java
new file mode 100644
index 00000000..be555f3b
--- /dev/null
+++ b/orkes-client/src/main/java/io/orkes/conductor/client/http/OrkesWebhookClient.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright 2022 Conductor Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package io.orkes.conductor.client.http;
+
+import java.util.List;
+
+import com.netflix.conductor.client.http.ConductorClient;
+import com.netflix.conductor.client.http.ConductorClientResponse;
+
+import io.orkes.conductor.client.model.Tag;
+import io.orkes.conductor.client.model.WebhookConfig;
+
+/**
+ * Client for managing webhook configurations in Orkes Conductor.
+ * Provides functionality to create, update, delete, and manage webhook configurations
+ * that can trigger workflows based on external events.
+ */
+public class OrkesWebhookClient {
+
+ private final WebhookResource webhookResource;
+
+ /**
+ * Constructs a new OrkesWebhookClient with the specified ConductorClient.
+ *
+ * @param client the ConductorClient to use for making HTTP requests to the server
+ */
+ public OrkesWebhookClient(ConductorClient client) {
+ this.webhookResource = new WebhookResource(client);
+ }
+
+ /**
+ * Create a new webhook configuration.
+ *
+ * @param webhookConfig the webhook configuration to create
+ * @return ConductorClientResponse containing the created webhook configuration
+ */
+ public ConductorClientResponse createWebhook(WebhookConfig webhookConfig) {
+ return webhookResource.createWebhook(webhookConfig);
+ }
+
+ /**
+ * Update an existing webhook configuration.
+ *
+ * @param id the webhook ID to update
+ * @param webhookConfig the updated webhook configuration
+ * @return ConductorClientResponse containing the updated webhook configuration
+ */
+ public ConductorClientResponse updateWebhook(String id, WebhookConfig webhookConfig) {
+ return webhookResource.updateWebhook(id, webhookConfig);
+ }
+
+ /**
+ * Delete a webhook configuration by ID.
+ *
+ * @param id the webhook ID to delete
+ * @return ConductorClientResponse for the delete operation
+ */
+ public ConductorClientResponse deleteWebhook(String id) {
+ return webhookResource.deleteWebhook(id);
+ }
+
+ /**
+ * Get a webhook configuration by ID.
+ *
+ * @param id the webhook ID to retrieve
+ * @return ConductorClientResponse containing the webhook configuration
+ */
+ public ConductorClientResponse getWebhook(String id) {
+ return webhookResource.getWebhook(id);
+ }
+
+ /**
+ * Get all webhook configurations.
+ *
+ * @return ConductorClientResponse containing a list of all webhook configurations
+ */
+ public ConductorClientResponse> getAllWebhooks() {
+ return webhookResource.getAllWebhooks();
+ }
+
+ /**
+ * Get tags for a webhook by ID.
+ *
+ * @param id the webhook ID
+ * @return ConductorClientResponse containing a list of tags
+ */
+ public ConductorClientResponse> getTagsForWebhook(String id) {
+ return webhookResource.getTagsForWebhook(id);
+ }
+
+ /**
+ * Put tags for a webhook by ID.
+ *
+ * @param id the webhook ID
+ * @param tags the tags to set
+ * @return ConductorClientResponse for the put operation
+ */
+ public ConductorClientResponse putTagsForWebhook(String id, List tags) {
+ return webhookResource.putTagsForWebhook(id, tags);
+ }
+
+ /**
+ * Delete tags for a webhook by ID.
+ *
+ * @param id the webhook ID
+ * @param tags the tags to remove
+ * @return ConductorClientResponse for the delete operation
+ */
+ public ConductorClientResponse deleteTagsForWebhook(String id, List tags) {
+ return webhookResource.deleteTagsForWebhook(id, tags);
+ }
+
+ /**
+ * Convenience method to get a webhook configuration directly without wrapper.
+ *
+ * @param id the webhook ID to retrieve
+ * @return WebhookConfig object or null if failed
+ */
+ public WebhookConfig getWebhookConfig(String id) {
+ try {
+ ConductorClientResponse response = getWebhook(id);
+ return response != null ? response.getData() : null;
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ /**
+ * Convenience method to get all webhook configurations directly without wrapper.
+ *
+ * @return List of WebhookConfig objects or empty list if failed
+ */
+ public List getWebhookConfigs() {
+ try {
+ ConductorClientResponse> response = getAllWebhooks();
+ return response != null && response.getData() != null ? response.getData() : List.of();
+ } catch (Exception e) {
+ return List.of();
+ }
+ }
+
+ /**
+ * Check if a webhook exists by ID.
+ *
+ * @param id the webhook ID to check
+ * @return true if webhook exists, false otherwise
+ */
+ public boolean webhookExists(String id) {
+ try {
+ ConductorClientResponse response = getWebhook(id);
+ return response != null && response.getData() != null;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+}
diff --git a/orkes-client/src/main/java/io/orkes/conductor/client/http/SharedResource.java b/orkes-client/src/main/java/io/orkes/conductor/client/http/SharedResource.java
new file mode 100644
index 00000000..a592afaf
--- /dev/null
+++ b/orkes-client/src/main/java/io/orkes/conductor/client/http/SharedResource.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2022 Conductor Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package io.orkes.conductor.client.http;
+
+import java.util.List;
+
+import com.netflix.conductor.client.http.ConductorClient;
+import com.netflix.conductor.client.http.ConductorClientRequest;
+import com.netflix.conductor.client.http.ConductorClientResponse;
+
+import io.orkes.conductor.client.model.SharedResourceModel;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+
+public class SharedResource {
+ private final ConductorClient client;
+
+ public SharedResource(ConductorClient client) {
+ this.client = client;
+ }
+
+ public void shareResource(String resourceType, String resourceName, String sharedWith) {
+ ConductorClientRequest request = ConductorClientRequest.builder()
+ .method(ConductorClientRequest.Method.POST)
+ .path("/share/shareResource")
+ .addQueryParam("resourceType", resourceType)
+ .addQueryParam("resourceName", resourceName)
+ .addQueryParam("sharedWith", sharedWith)
+ .build();
+
+ client.execute(request);
+ }
+
+ public void removeSharingResource(String resourceType, String resourceName, String sharedWith) {
+ ConductorClientRequest request = ConductorClientRequest.builder()
+ .method(ConductorClientRequest.Method.DELETE)
+ .path("/share/removeSharingResource")
+ .addQueryParam("resourceType", resourceType)
+ .addQueryParam("resourceName", resourceName)
+ .addQueryParam("sharedWith", sharedWith)
+ .build();
+
+ client.execute(request);
+ }
+
+ public List getSharedResources() {
+ ConductorClientRequest request = ConductorClientRequest.builder()
+ .method(ConductorClientRequest.Method.GET)
+ .path("/share/getSharedResources")
+ .build();
+
+ ConductorClientResponse> resp = client.execute(request, new TypeReference<>() {
+ });
+
+ return resp.getData();
+ }
+}
diff --git a/orkes-client/src/main/java/io/orkes/conductor/client/http/WebhookResource.java b/orkes-client/src/main/java/io/orkes/conductor/client/http/WebhookResource.java
new file mode 100644
index 00000000..27cb5ab7
--- /dev/null
+++ b/orkes-client/src/main/java/io/orkes/conductor/client/http/WebhookResource.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright 2022 Conductor Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package io.orkes.conductor.client.http;
+
+import java.util.List;
+
+import com.netflix.conductor.client.http.ConductorClient;
+import com.netflix.conductor.client.http.ConductorClientRequest;
+import com.netflix.conductor.client.http.ConductorClientRequest.Method;
+import com.netflix.conductor.client.http.ConductorClientResponse;
+
+import io.orkes.conductor.client.model.Tag;
+import io.orkes.conductor.client.model.WebhookConfig;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+
+/**
+ * Resource class for webhook configuration operations.
+ * Provides HTTP client methods to interact with webhook endpoints.
+ */
+public class WebhookResource {
+
+ private final ConductorClient client;
+
+ public WebhookResource(ConductorClient client) {
+ this.client = client;
+ }
+
+ /**
+ * Create a new webhook configuration.
+ *
+ * @param webhookConfig the webhook configuration to create
+ * @return ConductorClientResponse containing the created webhook configuration
+ */
+ public ConductorClientResponse createWebhook(WebhookConfig webhookConfig) {
+ ConductorClientRequest request = ConductorClientRequest.builder()
+ .method(Method.POST)
+ .path("/metadata/webhook")
+ .body(webhookConfig)
+ .build();
+
+ return client.execute(request, new TypeReference<>() {
+ });
+ }
+
+ /**
+ * Update an existing webhook configuration.
+ *
+ * @param id the webhook ID to update
+ * @param webhookConfig the updated webhook configuration
+ * @return ConductorClientResponse containing the updated webhook configuration
+ */
+ public ConductorClientResponse updateWebhook(String id, WebhookConfig webhookConfig) {
+ ConductorClientRequest request = ConductorClientRequest.builder()
+ .method(Method.PUT)
+ .path("/metadata/webhook/" + id)
+ .body(webhookConfig)
+ .build();
+
+ return client.execute(request, new TypeReference<>() {
+ });
+ }
+
+ /**
+ * Delete a webhook configuration by ID.
+ *
+ * @param id the webhook ID to delete
+ * @return ConductorClientResponse for the delete operation
+ */
+ public ConductorClientResponse deleteWebhook(String id) {
+ ConductorClientRequest request = ConductorClientRequest.builder()
+ .method(Method.DELETE)
+ .path("/metadata/webhook/" + id)
+ .build();
+
+ return client.execute(request, new TypeReference<>() {
+ });
+ }
+
+ /**
+ * Get a webhook configuration by ID.
+ *
+ * @param id the webhook ID to retrieve
+ * @return ConductorClientResponse containing the webhook configuration
+ */
+ public ConductorClientResponse getWebhook(String id) {
+ ConductorClientRequest request = ConductorClientRequest.builder()
+ .method(Method.GET)
+ .path("/metadata/webhook/" + id)
+ .build();
+
+ return client.execute(request, new TypeReference<>() {
+ });
+ }
+
+ /**
+ * Get all webhook configurations.
+ *
+ * @return ConductorClientResponse containing a list of all webhook configurations
+ */
+ public ConductorClientResponse> getAllWebhooks() {
+ ConductorClientRequest request = ConductorClientRequest.builder()
+ .method(Method.GET)
+ .path("/metadata/webhook")
+ .build();
+
+ return client.execute(request, new TypeReference<>() {
+ });
+ }
+
+ /**
+ * Get tags for a webhook by ID.
+ *
+ * @param id the webhook ID
+ * @return ConductorClientResponse containing a list of tags
+ */
+ public ConductorClientResponse> getTagsForWebhook(String id) {
+ ConductorClientRequest request = ConductorClientRequest.builder()
+ .method(Method.GET)
+ .path("/metadata/webhook/" + id + "/tags")
+ .build();
+
+ return client.execute(request, new TypeReference<>() {
+ });
+ }
+
+ /**
+ * Put tags for a webhook by ID.
+ *
+ * @param id the webhook ID
+ * @param tags the tags to set
+ * @return ConductorClientResponse for the put operation
+ */
+ public ConductorClientResponse putTagsForWebhook(String id, List tags) {
+ ConductorClientRequest request = ConductorClientRequest.builder()
+ .method(Method.PUT)
+ .path("/metadata/webhook/" + id + "/tags")
+ .body(tags)
+ .build();
+
+ return client.execute(request, new TypeReference<>() {
+ });
+ }
+
+ /**
+ * Delete tags for a webhook by ID.
+ *
+ * @param id the webhook ID
+ * @param tags the tags to remove
+ * @return ConductorClientResponse for the delete operation
+ */
+ public ConductorClientResponse deleteTagsForWebhook(String id, List tags) {
+ ConductorClientRequest request = ConductorClientRequest.builder()
+ .method(Method.DELETE)
+ .path("/metadata/webhook/" + id + "/tags")
+ .body(tags)
+ .build();
+
+ return client.execute(request, new TypeReference<>() {
+ });
+ }
+}
\ No newline at end of file
diff --git a/orkes-client/src/main/java/io/orkes/conductor/client/model/SharedResourceModel.java b/orkes-client/src/main/java/io/orkes/conductor/client/model/SharedResourceModel.java
new file mode 100644
index 00000000..12b5a191
--- /dev/null
+++ b/orkes-client/src/main/java/io/orkes/conductor/client/model/SharedResourceModel.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2025 Conductor Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package io.orkes.conductor.client.model;
+
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.ToString;
+
+@Getter
+@Setter
+@NoArgsConstructor
+@ToString(callSuper = true)
+public class SharedResourceModel {
+
+ String resourceType;
+ String resourceName;
+ String sharedBy;
+ String sharedWith;
+}
diff --git a/orkes-client/src/main/java/io/orkes/conductor/client/model/Tag.java b/orkes-client/src/main/java/io/orkes/conductor/client/model/Tag.java
index e681f9d3..80876a3d 100644
--- a/orkes-client/src/main/java/io/orkes/conductor/client/model/Tag.java
+++ b/orkes-client/src/main/java/io/orkes/conductor/client/model/Tag.java
@@ -17,9 +17,9 @@
@Data
@NoArgsConstructor
@Builder
-@AllArgsConstructor(access = AccessLevel.PRIVATE)
+@AllArgsConstructor(access = AccessLevel.PUBLIC)
public class Tag {
- private String key;
- private String value;
+ public String key;
+ public String value;
}
diff --git a/orkes-client/src/main/java/io/orkes/conductor/client/model/WebhookConfig.java b/orkes-client/src/main/java/io/orkes/conductor/client/model/WebhookConfig.java
new file mode 100644
index 00000000..806b7c6a
--- /dev/null
+++ b/orkes-client/src/main/java/io/orkes/conductor/client/model/WebhookConfig.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2025 Conductor Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package io.orkes.conductor.client.model;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.RequiredArgsConstructor;
+
+@Data
+@Builder
+@RequiredArgsConstructor
+@AllArgsConstructor
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class WebhookConfig {
+
+ private String name;
+
+ private String id;
+
+ private Map receiverWorkflowNamesToVersions;
+
+ private Map workflowsToStart;
+
+ private boolean urlVerified;
+
+ private String sourcePlatform;
+
+ private Verifier verifier;
+
+ private Map headers;
+
+ private String headerKey; // Required for signature_based verifier.
+
+ private String secretKey;
+
+ private String secretValue;
+
+ private String createdBy;
+ private List tags;
+
+ private List webhookExecutionHistory;//TODO Remove this
+
+ private String expression;
+ private String evaluatorType;
+
+ public enum Verifier {
+ SLACK_BASED,
+ SIGNATURE_BASED,
+ HEADER_BASED,
+ STRIPE,
+ TWITTER,
+ HMAC_BASED,
+ SENDGRID
+ }
+
+ @JsonIgnore
+ public List getWorkflowNames() {
+ return receiverWorkflowNamesToVersions == null ? List.of() : new ArrayList<>(receiverWorkflowNamesToVersions.keySet());
+ }
+
+ public void accept(WebhookConfigVisitor visitor) {
+ visitor.visit(this);
+ }
+
+ public interface WebhookConfigVisitor {
+ default void visit(WebhookConfig webhookConfig) {
+ }
+ }
+
+}
diff --git a/orkes-client/src/main/java/io/orkes/conductor/client/model/WebhookExecutionHistory.java b/orkes-client/src/main/java/io/orkes/conductor/client/model/WebhookExecutionHistory.java
new file mode 100644
index 00000000..3722c330
--- /dev/null
+++ b/orkes-client/src/main/java/io/orkes/conductor/client/model/WebhookExecutionHistory.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2025 Conductor Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package io.orkes.conductor.client.model;
+
+import java.util.Set;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.RequiredArgsConstructor;
+
+@Builder
+@Data
+@AllArgsConstructor
+@RequiredArgsConstructor
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class WebhookExecutionHistory {
+
+ private String eventId;
+ private boolean matched;
+ private Set workflowIds;
+ private String payload;
+ private long timeStamp;
+}
\ No newline at end of file
diff --git a/tests/src/test/java/io/orkes/conductor/client/http/WebhookClientE2ETest.java b/tests/src/test/java/io/orkes/conductor/client/http/WebhookClientE2ETest.java
new file mode 100644
index 00000000..63271844
--- /dev/null
+++ b/tests/src/test/java/io/orkes/conductor/client/http/WebhookClientE2ETest.java
@@ -0,0 +1,587 @@
+/*
+ * Copyright 2025 Conductor Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package io.orkes.conductor.client.http;
+
+import com.google.common.util.concurrent.Uninterruptibles;
+import com.netflix.conductor.client.http.ConductorClientResponse;
+import com.netflix.conductor.common.metadata.tasks.TaskDef;
+import com.netflix.conductor.common.metadata.workflow.WorkflowDef;
+import com.netflix.conductor.common.metadata.workflow.WorkflowTask;
+import io.orkes.conductor.client.OrkesClients;
+import io.orkes.conductor.client.model.Tag;
+import io.orkes.conductor.client.model.WebhookConfig;
+import io.orkes.conductor.client.util.ClientTestUtil;
+import io.orkes.conductor.client.util.TestUtil;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * E2E tests for OrkesWebhookClient demonstrating webhook configuration management
+ */
+public class WebhookClientE2ETest {
+
+ private static OrkesWebhookClient webhookClient;
+ private static OrkesClients orkesClients;
+ private String testWebhookId;
+
+ @BeforeAll
+ public static void setup() {
+ orkesClients = ClientTestUtil.getOrkesClients();
+ webhookClient = orkesClients.getWebhookClient();
+ }
+
+ @AfterEach
+ public void cleanup() {
+ // Clean up any created webhooks
+ if (testWebhookId != null) {
+ try {
+ webhookClient.deleteWebhook(testWebhookId);
+ } catch (Exception e) {
+ // Ignore cleanup errors
+ }
+ testWebhookId = null;
+ }
+ }
+
+ @Test
+ public void testCreateWebhook() {
+ // Arrange
+ String webhookName = "e2e_test_webhook_" + System.currentTimeMillis();
+
+ // Create a simple workflow to trigger from webhook
+ String workflowName = "e2e_webhook_target_" + System.currentTimeMillis();
+ createTestWorkflow(workflowName);
+
+ WebhookConfig webhookConfig = WebhookConfig.builder()
+ .name(webhookName)
+ .sourcePlatform("SLACK")
+ .workflowsToStart(Map.of(workflowName, 1))
+ .verifier(WebhookConfig.Verifier.SLACK_BASED)
+ .receiverWorkflowNamesToVersions(Map.of(workflowName, 1))
+ .secretValue("slack_secret_value")
+ .build();
+
+ // Act
+ ConductorClientResponse response = webhookClient.createWebhook(webhookConfig);
+
+ // Assert
+ assertNotNull(response, "Response should not be null");
+ assertNotNull(response.getData(), "Response data should not be null");
+
+ WebhookConfig createdWebhook = response.getData();
+ assertNotNull(createdWebhook.getId(), "Webhook ID should be generated");
+ assertEquals(webhookName, createdWebhook.getName(), "Webhook name should match");
+ assertEquals("SLACK", createdWebhook.getSourcePlatform(), "Source platform should match");
+ assertEquals(WebhookConfig.Verifier.SLACK_BASED, createdWebhook.getVerifier(), "Verifier should match");
+ assertNotNull(createdWebhook.getReceiverWorkflowNamesToVersions(), "Receiver workflows should not be null");
+ assertTrue(createdWebhook.getReceiverWorkflowNamesToVersions().containsKey(workflowName),
+ "Should contain target workflow");
+
+ // Store for cleanup
+ testWebhookId = createdWebhook.getId();
+
+ System.out.println("✅ Successfully created webhook with ID: " + testWebhookId);
+ }
+
+ @Test
+ public void testCreateWebhookWithSignatureVerifier() {
+ // Arrange
+ String webhookName = "e2e_signature_webhook_" + System.currentTimeMillis();
+ String workflowName = "e2e_webhook_target_" + System.currentTimeMillis();
+ createTestWorkflow(workflowName);
+
+ WebhookConfig webhookConfig = WebhookConfig.builder()
+ .name(webhookName)
+ .sourcePlatform("GITHUB")
+ .verifier(WebhookConfig.Verifier.SIGNATURE_BASED)
+ .headerKey("X-Hub-Signature-256")
+ .secretKey("webhook_secret")
+ .secretValue("my_secret_value")
+ .receiverWorkflowNamesToVersions(Map.of(workflowName, 1))
+ .build();
+
+ // Act
+ ConductorClientResponse response = webhookClient.createWebhook(webhookConfig);
+
+ // Assert
+ assertNotNull(response);
+ assertNotNull(response.getData());
+
+ WebhookConfig createdWebhook = response.getData();
+ assertNotNull(createdWebhook.getId());
+ assertEquals(webhookName, createdWebhook.getName());
+ assertEquals("GITHUB", createdWebhook.getSourcePlatform());
+ assertEquals(WebhookConfig.Verifier.SIGNATURE_BASED, createdWebhook.getVerifier());
+ assertEquals("X-Hub-Signature-256", createdWebhook.getHeaderKey());
+ assertEquals("webhook_secret", createdWebhook.getSecretKey());
+
+ // Store for cleanup
+ testWebhookId = createdWebhook.getId();
+
+ System.out.println("✅ Successfully created signature-based webhook with ID: " + testWebhookId);
+ }
+
+ @Test
+ public void testCreateWebhookWithHeaderBasedVerifier() {
+ // Arrange
+ String webhookName = "e2e_header_webhook_" + System.currentTimeMillis();
+ String workflowName = "e2e_webhook_target_" + System.currentTimeMillis();
+ createTestWorkflow(workflowName);
+
+ WebhookConfig webhookConfig = WebhookConfig.builder()
+ .name(webhookName)
+ .sourcePlatform("CUSTOM")
+ .verifier(WebhookConfig.Verifier.HEADER_BASED)
+ .headerKey("X-Custom-Token")
+ .secretValue("custom_token_123")
+ .receiverWorkflowNamesToVersions(Map.of(workflowName, 1))
+ .headers(Map.of(
+ "Content-Type", "application/json",
+ "X-Custom-Header", "webhook-integration"
+ ))
+ .build();
+
+ // Act
+ ConductorClientResponse response = webhookClient.createWebhook(webhookConfig);
+
+ // Assert
+ assertNotNull(response);
+ assertNotNull(response.getData());
+
+ WebhookConfig createdWebhook = response.getData();
+ assertNotNull(createdWebhook.getId());
+ assertEquals(webhookName, createdWebhook.getName());
+ assertEquals("CUSTOM", createdWebhook.getSourcePlatform());
+ assertEquals(WebhookConfig.Verifier.HEADER_BASED, createdWebhook.getVerifier());
+ assertEquals("X-Custom-Token", createdWebhook.getHeaderKey());
+ assertEquals("custom_token_123", createdWebhook.getSecretValue());
+
+ // Store for cleanup
+ testWebhookId = createdWebhook.getId();
+
+ System.out.println("✅ Successfully created header-based webhook with ID: " + testWebhookId);
+ }
+
+ @Test
+ public void testCreateWebhookWithMultipleWorkflows() {
+ // Arrange
+ String webhookName = "e2e_multi_webhook_" + System.currentTimeMillis();
+ String workflow1Name = "e2e_webhook_target1_" + System.currentTimeMillis();
+ String workflow2Name = "e2e_webhook_target2_" + System.currentTimeMillis();
+
+ createTestWorkflow(workflow1Name);
+ createTestWorkflow(workflow2Name);
+
+ Map workflowsToVersions = new HashMap<>();
+ workflowsToVersions.put(workflow1Name, 1);
+ workflowsToVersions.put(workflow2Name, 1);
+
+ WebhookConfig webhookConfig = WebhookConfig.builder()
+ .name(webhookName)
+ .sourcePlatform("STRIPE")
+ .verifier(WebhookConfig.Verifier.STRIPE)
+ .secretValue("whsec_test_secret")
+ .receiverWorkflowNamesToVersions(workflowsToVersions)
+ .build();
+
+ // Act
+ ConductorClientResponse response = webhookClient.createWebhook(webhookConfig);
+
+ // Assert
+ assertNotNull(response);
+ assertNotNull(response.getData());
+
+ WebhookConfig createdWebhook = response.getData();
+ assertNotNull(createdWebhook.getId());
+ assertEquals(webhookName, createdWebhook.getName());
+ assertEquals("STRIPE", createdWebhook.getSourcePlatform());
+ assertEquals(WebhookConfig.Verifier.STRIPE, createdWebhook.getVerifier());
+
+ Map receiverWorkflows = createdWebhook.getReceiverWorkflowNamesToVersions();
+ assertNotNull(receiverWorkflows);
+ assertEquals(2, receiverWorkflows.size());
+ assertTrue(receiverWorkflows.containsKey(workflow1Name));
+ assertTrue(receiverWorkflows.containsKey(workflow2Name));
+
+ // Store for cleanup
+ testWebhookId = createdWebhook.getId();
+
+ System.out.println("✅ Successfully created multi-workflow webhook with ID: " + testWebhookId);
+ }
+
+ @Test
+ public void testCreateWebhookValidation() {
+ // Test creating webhook with missing required fields
+ WebhookConfig invalidConfig = WebhookConfig.builder()
+ .name("") // Empty name should fail
+ .build();
+
+ try {
+ ConductorClientResponse response = webhookClient.createWebhook(invalidConfig);
+ // If this doesn't throw an exception, check the response for errors
+ if (response != null && response.getData() != null) {
+ fail("Expected webhook creation to fail with invalid config");
+ }
+ } catch (Exception e) {
+ // Expected - webhook creation should fail with invalid config
+ System.out.println("✅ Webhook creation properly rejected invalid config: " + e.getMessage());
+ }
+ }
+
+ @Test
+ public void testGetWebhook() {
+ // Arrange - First create a webhook
+ String webhookName = "e2e_get_webhook_" + System.currentTimeMillis();
+ String workflowName = "e2e_webhook_target_" + System.currentTimeMillis();
+ createTestWorkflow(workflowName);
+
+ WebhookConfig webhookConfig = WebhookConfig.builder()
+ .name(webhookName)
+ .sourcePlatform("SLACK")
+ .verifier(WebhookConfig.Verifier.SLACK_BASED)
+ .receiverWorkflowNamesToVersions(Map.of(workflowName, 1))
+ .build();
+
+ ConductorClientResponse createResponse = webhookClient.createWebhook(webhookConfig);
+ assertNotNull(createResponse.getData());
+ testWebhookId = createResponse.getData().getId();
+
+ // Act
+ ConductorClientResponse response = webhookClient.getWebhook(testWebhookId);
+
+ // Assert
+ assertNotNull(response);
+ assertNotNull(response.getData());
+
+ WebhookConfig retrievedWebhook = response.getData();
+ assertEquals(testWebhookId, retrievedWebhook.getId());
+ assertEquals(webhookName, retrievedWebhook.getName());
+ assertEquals("SLACK", retrievedWebhook.getSourcePlatform());
+ assertEquals(WebhookConfig.Verifier.SLACK_BASED, retrievedWebhook.getVerifier());
+
+ System.out.println("✅ Successfully retrieved webhook: " + testWebhookId);
+ }
+
+ @Test
+ public void testUpdateWebhook() {
+ // Arrange - First create a webhook
+ String webhookName = "e2e_update_webhook_" + System.currentTimeMillis();
+ String workflowName = "e2e_webhook_target_" + System.currentTimeMillis();
+ createTestWorkflow(workflowName);
+
+ WebhookConfig webhookConfig = WebhookConfig.builder()
+ .name(webhookName)
+ .sourcePlatform("SLACK")
+ .verifier(WebhookConfig.Verifier.SLACK_BASED)
+ .receiverWorkflowNamesToVersions(Map.of(workflowName, 1))
+ .build();
+
+ ConductorClientResponse createResponse = webhookClient.createWebhook(webhookConfig);
+ assertNotNull(createResponse.getData());
+ testWebhookId = createResponse.getData().getId();
+
+ // Act - Update the webhook
+ WebhookConfig updatedConfig = WebhookConfig.builder()
+ .name(webhookName + "_updated")
+ .sourcePlatform("GITHUB")
+ .verifier(WebhookConfig.Verifier.SIGNATURE_BASED)
+ .headerKey("X-Hub-Signature-256")
+ .secretKey("updated_secret")
+ .secretValue("updated_secret_value")
+ .receiverWorkflowNamesToVersions(Map.of(workflowName, 1))
+ .build();
+
+ ConductorClientResponse updateResponse = webhookClient.updateWebhook(testWebhookId, updatedConfig);
+
+ // Assert
+ assertNotNull(updateResponse);
+ assertNotNull(updateResponse.getData());
+
+ WebhookConfig updated = updateResponse.getData();
+ assertEquals(testWebhookId, updated.getId());
+ assertEquals(webhookName + "_updated", updated.getName());
+ assertEquals("GITHUB", updated.getSourcePlatform());
+ assertEquals(WebhookConfig.Verifier.SIGNATURE_BASED, updated.getVerifier());
+ assertEquals("X-Hub-Signature-256", updated.getHeaderKey());
+ assertEquals("updated_secret", updated.getSecretKey());
+
+ System.out.println("✅ Successfully updated webhook: " + testWebhookId);
+ }
+
+ @Test
+ public void testGetAllWebhooks() {
+ // Arrange - Create a test webhook
+ String webhookName = "e2e_list_webhook_" + System.currentTimeMillis();
+ String workflowName = "e2e_webhook_target_" + System.currentTimeMillis();
+ createTestWorkflow(workflowName);
+
+ WebhookConfig webhookConfig = WebhookConfig.builder()
+ .name(webhookName)
+ .sourcePlatform("SLACK")
+ .verifier(WebhookConfig.Verifier.SLACK_BASED)
+ .receiverWorkflowNamesToVersions(Map.of(workflowName, 1))
+ .build();
+
+ ConductorClientResponse createResponse = webhookClient.createWebhook(webhookConfig);
+ assertNotNull(createResponse.getData());
+ testWebhookId = createResponse.getData().getId();
+
+ // Act
+ ConductorClientResponse> response = webhookClient.getAllWebhooks();
+
+ // Assert
+ assertNotNull(response);
+ assertNotNull(response.getData());
+
+ List webhooks = response.getData();
+ assertTrue(webhooks.size() > 0, "Should have at least one webhook");
+
+ // Verify our test webhook is in the list
+ boolean found = webhooks.stream()
+ .anyMatch(wh -> testWebhookId.equals(wh.getId()));
+ assertTrue(found, "Should find our test webhook in the list");
+
+ System.out.println("✅ Successfully retrieved " + webhooks.size() + " webhooks");
+ }
+
+ @Test
+ public void testWebhookTagOperations() {
+ // Arrange - Create a webhook
+ String webhookName = "e2e_tag_webhook_" + System.currentTimeMillis();
+ String workflowName = "e2e_webhook_target_" + System.currentTimeMillis();
+ createTestWorkflow(workflowName);
+
+ WebhookConfig webhookConfig = WebhookConfig.builder()
+ .name(webhookName)
+ .sourcePlatform("SLACK")
+ .verifier(WebhookConfig.Verifier.SLACK_BASED)
+ .receiverWorkflowNamesToVersions(Map.of(workflowName, 1))
+ .build();
+
+ ConductorClientResponse createResponse = webhookClient.createWebhook(webhookConfig);
+ assertNotNull(createResponse.getData());
+ testWebhookId = createResponse.getData().getId();
+
+ // Create tags
+ Tag tag1 = new Tag();
+ tag1.setKey("environment");
+ tag1.setValue("testing");
+
+ Tag tag2 = new Tag();
+ tag2.setKey("team");
+ tag2.setValue("e2e");
+
+ List tags = List.of(tag1, tag2);
+
+ // Act - Put tags
+ ConductorClientResponse putResponse = webhookClient.putTagsForWebhook(testWebhookId, tags);
+ assertNotNull(putResponse);
+
+ // Act - Get tags
+ ConductorClientResponse> getResponse = webhookClient.getTagsForWebhook(testWebhookId);
+ assertNotNull(getResponse);
+ assertNotNull(getResponse.getData());
+
+ List retrievedTags = getResponse.getData();
+ assertEquals(2, retrievedTags.size());
+
+ // Verify tags
+ boolean hasEnvTag = retrievedTags.stream()
+ .anyMatch(tag -> "environment".equals(tag.getKey()) && "testing".equals(tag.getValue()));
+ boolean hasTeamTag = retrievedTags.stream()
+ .anyMatch(tag -> "team".equals(tag.getKey()) && "e2e".equals(tag.getValue()));
+
+ assertTrue(hasEnvTag, "Should have environment tag");
+ assertTrue(hasTeamTag, "Should have team tag");
+
+ System.out.println("✅ Successfully tested webhook tag operations");
+ }
+
+ @Test
+ public void testDeleteWebhook() {
+ // Arrange - Create a webhook
+ String webhookName = "e2e_delete_webhook_" + System.currentTimeMillis();
+ String workflowName = "e2e_webhook_target_" + System.currentTimeMillis();
+ createTestWorkflow(workflowName);
+
+ WebhookConfig webhookConfig = WebhookConfig.builder()
+ .name(webhookName)
+ .sourcePlatform("SLACK")
+ .verifier(WebhookConfig.Verifier.SLACK_BASED)
+ .receiverWorkflowNamesToVersions(Map.of(workflowName, 1))
+ .build();
+
+ ConductorClientResponse createResponse = webhookClient.createWebhook(webhookConfig);
+ assertNotNull(createResponse.getData());
+ String webhookId = createResponse.getData().getId();
+
+ // Verify webhook exists
+ ConductorClientResponse getResponse = webhookClient.getWebhook(webhookId);
+ assertNotNull(getResponse.getData());
+
+ // Act - Delete webhook
+ ConductorClientResponse deleteResponse = webhookClient.deleteWebhook(webhookId);
+ assertNotNull(deleteResponse);
+
+ // Assert - Verify webhook is deleted
+ try {
+ ConductorClientResponse getAfterDeleteResponse = webhookClient.getWebhook(webhookId);
+ // If we get here, the webhook might still exist (depending on API behavior)
+ // or the API might return an empty response
+ if (getAfterDeleteResponse != null && getAfterDeleteResponse.getData() != null) {
+ fail("Webhook should have been deleted");
+ }
+ } catch (Exception e) {
+ // Expected - webhook should not be found after deletion
+ System.out.println("✅ Webhook properly deleted, get request failed as expected: " + e.getMessage());
+ }
+
+ // Clear testWebhookId since it's already deleted
+ testWebhookId = null;
+
+ System.out.println("✅ Successfully deleted webhook: " + webhookId);
+ }
+
+ @Test
+ public void testWebhookConvenienceMethods() {
+ // Arrange - Create a webhook
+ String webhookName = "e2e_convenience_webhook_" + System.currentTimeMillis();
+ String workflowName = "e2e_webhook_target_" + System.currentTimeMillis();
+ createTestWorkflow(workflowName);
+
+ WebhookConfig webhookConfig = WebhookConfig.builder()
+ .name(webhookName)
+ .sourcePlatform("SLACK")
+ .verifier(WebhookConfig.Verifier.SLACK_BASED)
+ .receiverWorkflowNamesToVersions(Map.of(workflowName, 1))
+ .build();
+
+ ConductorClientResponse createResponse = webhookClient.createWebhook(webhookConfig);
+ assertNotNull(createResponse.getData());
+ testWebhookId = createResponse.getData().getId();
+
+ // Test convenience methods
+
+ // Test getWebhookConfig
+ WebhookConfig retrievedConfig = webhookClient.getWebhookConfig(testWebhookId);
+ assertNotNull(retrievedConfig);
+ assertEquals(testWebhookId, retrievedConfig.getId());
+ assertEquals(webhookName, retrievedConfig.getName());
+
+ // Test getWebhookConfigs
+ List allConfigs = webhookClient.getWebhookConfigs();
+ assertNotNull(allConfigs);
+ assertTrue(allConfigs.size() > 0);
+
+ boolean found = allConfigs.stream()
+ .anyMatch(wh -> testWebhookId.equals(wh.getId()));
+ assertTrue(found, "Should find our test webhook in the list");
+
+ // Test webhookExists
+ boolean exists = webhookClient.webhookExists(testWebhookId);
+ assertTrue(exists, "Webhook should exist");
+
+ boolean nonExistentExists = webhookClient.webhookExists("non-existent-id");
+ assertFalse(nonExistentExists, "Non-existent webhook should not exist");
+
+ System.out.println("✅ Successfully tested convenience methods");
+ }
+
+ @Test
+ public void testWebhookErrorHandling() {
+ // Test error scenarios
+
+ // Test getting non-existent webhook
+ try {
+ ConductorClientResponse response = webhookClient.getWebhook("non-existent-webhook-id");
+ if (response != null && response.getData() != null) {
+ fail("Should not be able to get non-existent webhook");
+ }
+ } catch (Exception e) {
+ System.out.println("✅ Properly handled getting non-existent webhook: " + e.getMessage());
+ }
+
+ // Test updating non-existent webhook
+ try {
+ WebhookConfig updateConfig = WebhookConfig.builder()
+ .name("update_test")
+ .sourcePlatform("SLACK")
+ .verifier(WebhookConfig.Verifier.SLACK_BASED)
+ .build();
+
+ ConductorClientResponse response = webhookClient.updateWebhook("non-existent-webhook-id", updateConfig);
+ if (response != null && response.getData() != null) {
+ fail("Should not be able to update non-existent webhook");
+ }
+ } catch (Exception e) {
+ System.out.println("✅ Properly handled updating non-existent webhook: " + e.getMessage());
+ }
+
+ // Test deleting non-existent webhook
+ try {
+ ConductorClientResponse response = webhookClient.deleteWebhook("non-existent-webhook-id");
+ // Some APIs might return success even for non-existent resources
+ System.out.println("✅ Delete non-existent webhook handled gracefully");
+ } catch (Exception e) {
+ System.out.println("✅ Properly handled deleting non-existent webhook: " + e.getMessage());
+ }
+ }
+
+ /**
+ * Helper method to create a simple test workflow for webhook targets
+ */
+ private void createTestWorkflow(String workflowName) {
+ try {
+ // Create a simple task definition
+ String taskName = workflowName + "_task";
+ TaskDef taskDef = new TaskDef(taskName);
+ taskDef.setRetryCount(1);
+ taskDef.setOwnerEmail("test@orkes.io");
+
+ TestUtil.retryMethodCall(() ->
+ orkesClients.getMetadataClient().registerTaskDefs(List.of(taskDef)));
+
+ // Create workflow definition
+ WorkflowDef workflowDef = new WorkflowDef();
+ workflowDef.setName(workflowName);
+ workflowDef.setVersion(1);
+ workflowDef.setOwnerEmail("test@orkes.io");
+
+ // Add a simple task to the workflow
+ WorkflowTask workflowTask = new WorkflowTask();
+ workflowTask.setName(taskName);
+ workflowTask.setTaskReferenceName(taskName + "_ref");
+ workflowTask.setType("SIMPLE");
+
+ workflowDef.setTasks(List.of(workflowTask));
+
+ TestUtil.retryMethodCall(() ->
+ orkesClients.getMetadataClient().registerWorkflowDef(workflowDef));
+
+ // Wait a bit for registration to complete
+ Uninterruptibles.sleepUninterruptibly(1, TimeUnit.SECONDS);
+
+ } catch (Exception e) {
+ System.err.println("Failed to create test workflow: " + workflowName + " - " + e.getMessage());
+ // Don't fail the test if workflow creation fails, as this is just setup
+ }
+ }
+}