This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new 50376680fdd7 CAMEL-26165: camel-clickup - deliver webhook events of 
other kinds as GenericEvent instead of failing with 500 (#28943)
50376680fdd7 is described below

commit 50376680fdd73e25d6b9f907a2922ea864315454
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Nov 2 21:48:15 2026 +0100

    CAMEL-26165: camel-clickup - deliver webhook events of other kinds as 
GenericEvent instead of failing with 500 (#28943)
    
    * CAMEL-26165: camel-clickup - deliver webhook events of other kinds as…
    
    … GenericEvent instead of failing with 500
    
    Event declared only TaskTimeTrackedUpdatedEvent as subtype, so a signed 
webhook
    event of any other kind (such as taskCreated) failed in Jackson with
    InvalidTypeIdException, the webhook answered 500, and ClickUp kept retrying 
it.
    Event now has GenericEvent as defaultImpl, which keeps the event kind, the
    webhook id and the other JSON fields, and Event exposes the event kind.
    
    * Merge remote-tracking branch 'origin/main' into CAMEL-26165-clickup-w…
    
    …ebhook-events
    
    
    # Conflicts:
    #       docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../apache/camel/catalog/components/clickup.json   |  2 +-
 .../camel/catalog/docs/clickup-component.adoc      |  6 +++
 .../apache/camel/component/clickup/clickup.json    |  2 +-
 .../src/main/docs/clickup-component.adoc           |  6 +++
 .../camel/component/clickup/ClickUpEndpoint.java   |  9 ++--
 .../camel/component/clickup/model/Event.java       | 15 +++++-
 .../component/clickup/model/GenericEvent.java      | 60 ++++++++++++++++++++++
 .../component/clickup/ClickUpWebhookCallTest.java  | 41 +++++++++++++++
 .../resources/messages/events/task-created.json    | 38 ++++++++++++++
 .../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc    |  7 +++
 10 files changed, 178 insertions(+), 8 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/clickup.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/clickup.json
index 976407830b2e..51a5aca76a03 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/clickup.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/clickup.json
@@ -28,7 +28,7 @@
     "autowiredEnabled": { "index": 1, "kind": "property", "displayName": 
"Autowired Enabled", "group": "advanced", "label": "advanced", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": true, "description": 
"Whether autowiring is enabled. This is used for automatic autowiring options 
(the option must be marked as autowired) by looking up in the registry to find 
if there is a single instance of matching t [...]
   },
   "bodyTypes": {
-    "consumerOut": { "carriers": [ "pojo" ], "javaType": 
"org.apache.camel.component.clickup.model.TaskTimeTrackedUpdatedEvent", 
"description": "The event that ClickUp posts to the webhook, the only kind 
supported: an event of another kind, or one without a valid x-signature, fails 
with 500 and does not reach the route" },
+    "consumerOut": { "carriers": [ "pojo" ], "javaType": 
"org.apache.camel.component.clickup.model.Event", "description": "The event 
that ClickUp posts to the webhook: a TaskTimeTrackedUpdatedEvent, or a 
GenericEvent with the raw fields for an event of another kind. An event without 
a valid x-signature fails with 500 and does not reach the route" },
     "consumerReply": { "carriers": [ "any" ], "description": "The body at the 
end of the route is not sent: ClickUp gets 204 without a body, or 500 when the 
route fails" }
   },
   "properties": {
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/clickup-component.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/clickup-component.adoc
index aa6860c20a4e..2bf7d0a115fb 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/clickup-component.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/clickup-component.adoc
@@ -98,6 +98,12 @@ In YAML, the `is` operator of the simple language checks the 
type of the event:
                     message: "Unknown Event: ${body}"
 ----
 
+The `events` option accepts only `taskTimeTrackedUpdated`, the one kind with a 
class of its own,
+`TaskTimeTrackedUpdatedEvent`. A webhook set up in ClickUp itself 
(`webhookAutoRegister=false`) can send other kinds
+too: such an event reaches the route as a `GenericEvent`, where `getEvent()` 
gives the kind (such as `taskCreated`) and
+`getProperties()` the other fields of the JSON as they are. ClickUp gets 204 
for it, as for any event the route
+processes without failing.
+
 You can follow the
 https://clickup.com/api/developer-portal/webhooks[set-up webhooks guide]
 to enable and configure the webhook.
diff --git 
a/components/camel-clickup/src/generated/resources/META-INF/org/apache/camel/component/clickup/clickup.json
 
b/components/camel-clickup/src/generated/resources/META-INF/org/apache/camel/component/clickup/clickup.json
index 976407830b2e..51a5aca76a03 100644
--- 
a/components/camel-clickup/src/generated/resources/META-INF/org/apache/camel/component/clickup/clickup.json
+++ 
b/components/camel-clickup/src/generated/resources/META-INF/org/apache/camel/component/clickup/clickup.json
@@ -28,7 +28,7 @@
     "autowiredEnabled": { "index": 1, "kind": "property", "displayName": 
"Autowired Enabled", "group": "advanced", "label": "advanced", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": true, "description": 
"Whether autowiring is enabled. This is used for automatic autowiring options 
(the option must be marked as autowired) by looking up in the registry to find 
if there is a single instance of matching t [...]
   },
   "bodyTypes": {
-    "consumerOut": { "carriers": [ "pojo" ], "javaType": 
"org.apache.camel.component.clickup.model.TaskTimeTrackedUpdatedEvent", 
"description": "The event that ClickUp posts to the webhook, the only kind 
supported: an event of another kind, or one without a valid x-signature, fails 
with 500 and does not reach the route" },
+    "consumerOut": { "carriers": [ "pojo" ], "javaType": 
"org.apache.camel.component.clickup.model.Event", "description": "The event 
that ClickUp posts to the webhook: a TaskTimeTrackedUpdatedEvent, or a 
GenericEvent with the raw fields for an event of another kind. An event without 
a valid x-signature fails with 500 and does not reach the route" },
     "consumerReply": { "carriers": [ "any" ], "description": "The body at the 
end of the route is not sent: ClickUp gets 204 without a body, or 500 when the 
route fails" }
   },
   "properties": {
diff --git a/components/camel-clickup/src/main/docs/clickup-component.adoc 
b/components/camel-clickup/src/main/docs/clickup-component.adoc
index aa6860c20a4e..2bf7d0a115fb 100644
--- a/components/camel-clickup/src/main/docs/clickup-component.adoc
+++ b/components/camel-clickup/src/main/docs/clickup-component.adoc
@@ -98,6 +98,12 @@ In YAML, the `is` operator of the simple language checks the 
type of the event:
                     message: "Unknown Event: ${body}"
 ----
 
+The `events` option accepts only `taskTimeTrackedUpdated`, the one kind with a 
class of its own,
+`TaskTimeTrackedUpdatedEvent`. A webhook set up in ClickUp itself 
(`webhookAutoRegister=false`) can send other kinds
+too: such an event reaches the route as a `GenericEvent`, where `getEvent()` 
gives the kind (such as `taskCreated`) and
+`getProperties()` the other fields of the JSON as they are. ClickUp gets 204 
for it, as for any event the route
+processes without failing.
+
 You can follow the
 https://clickup.com/api/developer-portal/webhooks[set-up webhooks guide]
 to enable and configure the webhook.
diff --git 
a/components/camel-clickup/src/main/java/org/apache/camel/component/clickup/ClickUpEndpoint.java
 
b/components/camel-clickup/src/main/java/org/apache/camel/component/clickup/ClickUpEndpoint.java
index bbb46365671d..423a7488b0ae 100644
--- 
a/components/camel-clickup/src/main/java/org/apache/camel/component/clickup/ClickUpEndpoint.java
+++ 
b/components/camel-clickup/src/main/java/org/apache/camel/component/clickup/ClickUpEndpoint.java
@@ -48,10 +48,11 @@ import org.slf4j.LoggerFactory;
              category = { Category.CLOUD },
              consumerOnly = true)
 @BodyTypes(consumerOut = @BodyType(value = Carrier.POJO,
-                                   javaType = 
"org.apache.camel.component.clickup.model.TaskTimeTrackedUpdatedEvent",
-                                   description = "The event that ClickUp posts 
to the webhook, the only kind supported: an event"
-                                                 + " of another kind, or one 
without a valid x-signature, fails with 500 and"
-                                                 + " does not reach the 
route"),
+                                   javaType = 
"org.apache.camel.component.clickup.model.Event",
+                                   description = "The event that ClickUp posts 
to the webhook: a TaskTimeTrackedUpdatedEvent,"
+                                                 + " or a GenericEvent with 
the raw fields for an event of another kind. An"
+                                                 + " event without a valid 
x-signature fails with 500 and does not reach the"
+                                                 + " route"),
            consumerReply = @BodyType(value = Carrier.ANY,
                                      description = "The body at the end of the 
route is not sent: ClickUp gets 204 without a"
                                                    + " body, or 500 when the 
route fails"))
diff --git 
a/components/camel-clickup/src/main/java/org/apache/camel/component/clickup/model/Event.java
 
b/components/camel-clickup/src/main/java/org/apache/camel/component/clickup/model/Event.java
index 75d3cde391cc..850e08714e2d 100644
--- 
a/components/camel-clickup/src/main/java/org/apache/camel/component/clickup/model/Event.java
+++ 
b/components/camel-clickup/src/main/java/org/apache/camel/component/clickup/model/Event.java
@@ -21,16 +21,26 @@ import com.fasterxml.jackson.annotation.JsonProperty;
 import com.fasterxml.jackson.annotation.JsonSubTypes;
 import com.fasterxml.jackson.annotation.JsonTypeInfo;
 
-@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "event")
+@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "event", visible = true, 
defaultImpl = GenericEvent.class)
 @JsonSubTypes({
         @JsonSubTypes.Type(value = TaskTimeTrackedUpdatedEvent.class, name = 
Events.TASK_TIME_TRACKED_UPDATED)
 })
 @JsonIgnoreProperties(ignoreUnknown = true)
 public abstract class Event {
 
+    @JsonProperty("event")
+    protected String event;
+
     @JsonProperty("webhook_id")
     protected String webhookId;
 
+    /**
+     * The kind of the event, such as taskTimeTrackedUpdated.
+     */
+    public String getEvent() {
+        return event;
+    }
+
     public String getWebhookId() {
         return webhookId;
     }
@@ -38,7 +48,8 @@ public abstract class Event {
     @Override
     public String toString() {
         return "Event{" +
-               "webhookId='" + webhookId + '\'' +
+               "event='" + event + '\'' +
+               ", webhookId='" + webhookId + '\'' +
                '}';
     }
 
diff --git 
a/components/camel-clickup/src/main/java/org/apache/camel/component/clickup/model/GenericEvent.java
 
b/components/camel-clickup/src/main/java/org/apache/camel/component/clickup/model/GenericEvent.java
new file mode 100644
index 000000000000..df08580a190b
--- /dev/null
+++ 
b/components/camel-clickup/src/main/java/org/apache/camel/component/clickup/model/GenericEvent.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.camel.component.clickup.model;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonAnyGetter;
+import com.fasterxml.jackson.annotation.JsonAnySetter;
+
+/**
+ * An event of a kind that has no class of its own, such as taskCreated: the 
fields other than event and webhook_id are
+ * kept as they are in the JSON.
+ */
+public class GenericEvent extends Event implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 0L;
+
+    private final Map<String, Object> properties = new LinkedHashMap<>();
+
+    /**
+     * The fields of the event other than event and webhook_id, such as 
task_id and history_items.
+     */
+    @JsonAnyGetter
+    public Map<String, Object> getProperties() {
+        return properties;
+    }
+
+    @JsonAnySetter
+    public void setProperty(String name, Object value) {
+        properties.put(name, value);
+    }
+
+    @Override
+    public String toString() {
+        return "GenericEvent{" +
+               "event='" + event + '\'' +
+               ", webhookId='" + webhookId + '\'' +
+               ", properties=" + properties +
+               '}';
+    }
+
+}
diff --git 
a/components/camel-clickup/src/test/java/org/apache/camel/component/clickup/ClickUpWebhookCallTest.java
 
b/components/camel-clickup/src/test/java/org/apache/camel/component/clickup/ClickUpWebhookCallTest.java
index e59c935a047f..c324063ff161 100644
--- 
a/components/camel-clickup/src/test/java/org/apache/camel/component/clickup/ClickUpWebhookCallTest.java
+++ 
b/components/camel-clickup/src/test/java/org/apache/camel/component/clickup/ClickUpWebhookCallTest.java
@@ -17,6 +17,7 @@
 package org.apache.camel.component.clickup;
 
 import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
@@ -25,14 +26,17 @@ import java.util.Set;
 import org.apache.camel.Exchange;
 import org.apache.camel.RoutesBuilder;
 import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.clickup.model.GenericEvent;
 import org.apache.camel.component.clickup.model.TaskTimeTrackedUpdatedEvent;
 import org.apache.camel.component.clickup.util.ClickUpTestSupport;
+import org.apache.camel.component.clickup.util.Utils;
 import org.apache.camel.component.mock.MockEndpoint;
 import org.apache.camel.component.webhook.WebhookConfiguration;
 import org.apache.camel.component.webhook.WebhookEndpoint;
 import org.junit.jupiter.api.Test;
 
 import static java.util.List.of;
+import static org.assertj.core.api.Assertions.assertThat;
 
 class ClickUpWebhookCallTest extends ClickUpTestSupport {
 
@@ -40,6 +44,7 @@ class ClickUpWebhookCallTest extends ClickUpTestSupport {
     private static final String TIME_TRACKING_CREATED_RESOURCE = 
"messages/events/time-tracking-created.json";
     private static final String TIME_TRACKING_CREATED_SIGNATURE
             = 
"ac99f10017e28db6839941c184964890ec3262b1d6b1756d33ff53d972d5a361";
+    private static final String TASK_CREATED_RESOURCE = 
"messages/events/task-created.json";
 
     @Test
     void testWebhookCall() throws Exception {
@@ -62,6 +67,42 @@ class ClickUpWebhookCallTest extends ClickUpTestSupport {
         mock.assertIsSatisfied();
     }
 
+    @Test
+    void testWebhookCallWithEventOfAnotherKind() throws Exception {
+        WebhookConfiguration config
+                = ((WebhookEndpoint) 
context().getRoute("webhook").getConsumer().getEndpoint()).getConfiguration();
+        String url = config.computeFullExternalUrl();
+
+        MockEndpoint mock = getMockEndpoint("mock:endpoint");
+        mock.expectedMessageCount(1);
+
+        String body;
+        try (InputStream content = 
getClass().getClassLoader().getResourceAsStream(TASK_CREATED_RESOURCE)) {
+            body = new String(content.readAllBytes(), StandardCharsets.UTF_8);
+        }
+
+        Map<String, Object> headers = new HashMap<>();
+        headers.put(Exchange.HTTP_METHOD, "POST");
+        headers.put(Exchange.CONTENT_TYPE, "application/json");
+        headers.put("x-signature", Utils.computeMessageHMAC(body, 
WEBHOOK_SECRET));
+        Exchange reply = template().request("netty-http:" + url + 
"?throwExceptionOnFailure=false", exchange -> {
+            exchange.getIn().setBody(body);
+            exchange.getIn().setHeaders(headers);
+        });
+
+        mock.assertIsSatisfied();
+
+        assertThat(reply.getMessage().getHeader(Exchange.HTTP_RESPONSE_CODE, 
Integer.class)).isEqualTo(204);
+
+        Object received = mock.getReceivedExchanges().get(0).getIn().getBody();
+        assertThat(received).isInstanceOf(GenericEvent.class);
+        GenericEvent event = (GenericEvent) received;
+        assertThat(event.getEvent()).isEqualTo("taskCreated");
+        
assertThat(event.getWebhookId()).isEqualTo("7fa3ec74-69a8-4530-a251-8a13730bd204");
+        assertThat(event.getProperties()).containsEntry("task_id", 
"1vj37mc").containsKey("history_items")
+                .doesNotContainKeys("event", "webhook_id");
+    }
+
     @Override
     protected RoutesBuilder createRouteBuilder() {
         return new RouteBuilder() {
diff --git 
a/components/camel-clickup/src/test/resources/messages/events/task-created.json 
b/components/camel-clickup/src/test/resources/messages/events/task-created.json
new file mode 100644
index 000000000000..36086e2e3a2e
--- /dev/null
+++ 
b/components/camel-clickup/src/test/resources/messages/events/task-created.json
@@ -0,0 +1,38 @@
+{
+    "event": "taskCreated",
+    "history_items": [
+        {
+            "id": "2800763136717140857",
+            "type": 1,
+            "date": "1642734631523",
+            "field": "status",
+            "parent_id": "162641062",
+            "data": {
+                "status_type": "open"
+            },
+            "source": null,
+            "user": {
+                "id": 7654321,
+                "username": "John Doe",
+                "email": "[email protected]",
+                "color": "#2ea52c",
+                "initials": "JD",
+                "profilePicture": null
+            },
+            "before": {
+                "status": null,
+                "color": "#000000",
+                "type": "removed",
+                "orderindex": -1
+            },
+            "after": {
+                "status": "to do",
+                "color": "#f9d900",
+                "orderindex": 0,
+                "type": "open"
+            }
+        }
+    ],
+    "task_id": "1vj37mc",
+    "webhook_id": "7fa3ec74-69a8-4530-a251-8a13730bd204"
+}
diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
index a7befadbf7d3..0f459ca6474f 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
@@ -670,6 +670,13 @@ Inserting a `List` of `Map` now fails with a 
`ClickHouseException` that says why
 class is registered on the `Client` for the table. Before, both failed with a 
`NullPointerException` from the ClickHouse
 client.
 
+=== camel-clickup - an event of another kind than taskTimeTrackedUpdated 
reaches the route
+
+A webhook event of another kind than `taskTimeTrackedUpdated`, such as 
`taskCreated`, now reaches the route as a
+`GenericEvent` (`getEvent()` gives the kind, `getProperties()` the other 
fields of the JSON). Before, it failed before
+the route with 500, and ClickUp kept sending it again. A route that expects 
only `TaskTimeTrackedUpdatedEvent` can check
+the type of the body, as the example in the ClickUp component page does.
+
 === camel-cm-sms - the from of an SMSMessage is the sender of the SMS
 
 An `SMSMessage` with a `from` is now sent with that sender. Before, the sender 
was set only when `from` was empty (to

Reply via email to