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

gnodet pushed a commit to branch backport/27716-to-camel-4.22.x
in repository https://gitbox.apache.org/repos/asf/camel.git

commit b2d33c5a105de609354844fa199cb3ec065860f9
Author: Guillaume Nodet <[email protected]>
AuthorDate: Tue Oct 13 11:41:20 2026 +0000

    [backport camel-4.22.x] CAMEL-25564: camel-telegram - verify the Bot API 
secret token on webhook updates
---
 .../apache/camel/catalog/components/telegram.json  |   4 +-
 .../camel/catalog/docs/telegram-component.adoc     |  13 ++
 .../telegram/TelegramEndpointConfigurer.java       |  12 ++
 .../telegram/TelegramEndpointUriFactory.java       |   7 +-
 .../apache/camel/component/telegram/telegram.json  |   4 +-
 .../src/main/docs/telegram-component.adoc          |  13 ++
 .../component/telegram/TelegramConfiguration.java  |  30 +++++
 .../camel/component/telegram/TelegramEndpoint.java |  52 +++++++-
 .../camel/component/telegram/TelegramService.java  |  10 ++
 .../telegram/TelegramWebhookProcessor.java         |  34 ++++-
 .../telegram/service/TelegramAsyncHandler.java     |  16 ++-
 .../service/TelegramServiceRestBotAPIAdapter.java  |  32 +++--
 .../telegram/TelegramWebhookCallTest.java          |  82 ++++++++++--
 .../telegram/TelegramWebhookSecretTokenTest.java   | 132 ++++++++++++++++++++
 .../TelegramServiceRestBotAPIAdapterErrorTest.java | 138 +++++++++++++++++++++
 .../java/org/apache/camel/util/SecurityUtils.java  |   1 +
 .../ROOT/pages/camel-4x-upgrade-guide-4_22.adoc    |  25 ++++
 .../dsl/TelegramEndpointBuilderFactory.java        |  59 +++++++++
 18 files changed, 637 insertions(+), 27 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/telegram.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/telegram.json
index 830113732dce..0fa748818ba5 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/telegram.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/telegram.json
@@ -72,6 +72,8 @@
     "startScheduler": { "index": 27, "kind": "parameter", "displayName": 
"Start Scheduler", "group": "scheduler", "label": "consumer,scheduler", 
"required": false, "type": "boolean", "javaType": "boolean", "deprecated": 
false, "autowired": false, "secret": false, "defaultValue": true, 
"description": "Whether the scheduler should be auto started." },
     "timeUnit": { "index": 28, "kind": "parameter", "displayName": "Time 
Unit", "group": "scheduler", "label": "consumer,scheduler", "required": false, 
"type": "enum", "javaType": "java.util.concurrent.TimeUnit", "enum": [ 
"NANOSECONDS", "MICROSECONDS", "MILLISECONDS", "SECONDS", "MINUTES", "HOURS", 
"DAYS" ], "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": "MILLISECONDS", "description": "Time unit for initialDelay and 
delay options." },
     "useFixedDelay": { "index": 29, "kind": "parameter", "displayName": "Use 
Fixed Delay", "group": "scheduler", "label": "consumer,scheduler", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": true, "description": 
"Controls if fixed delay or fixed rate is used. See ScheduledExecutorService in 
JDK for details." },
-    "authorizationToken": { "index": 30, "kind": "parameter", "displayName": 
"Authorization Token", "group": "security", "label": "security", "required": 
true, "type": "string", "javaType": "java.lang.String", "deprecated": false, 
"deprecationNote": "", "autowired": false, "secret": true, "security": 
"secret", "configurationClass": 
"org.apache.camel.component.telegram.TelegramConfiguration", 
"configurationField": "configuration", "description": "The authorization token 
for using the bot  [...]
+    "authorizationToken": { "index": 30, "kind": "parameter", "displayName": 
"Authorization Token", "group": "security", "label": "security", "required": 
true, "type": "string", "javaType": "java.lang.String", "deprecated": false, 
"deprecationNote": "", "autowired": false, "secret": true, "security": 
"secret", "configurationClass": 
"org.apache.camel.component.telegram.TelegramConfiguration", 
"configurationField": "configuration", "description": "The authorization token 
for using the bot  [...]
+    "webhookSecretToken": { "index": 31, "kind": "parameter", "displayName": 
"Webhook Secret Token", "group": "security", "label": "consumer,security", 
"required": false, "type": "string", "javaType": "java.lang.String", 
"deprecated": false, "autowired": false, "secret": true, "security": "secret", 
"configurationClass": 
"org.apache.camel.component.telegram.TelegramConfiguration", 
"configurationField": "configuration", "description": "The secret token that 
Telegram sends in the X-Telegram [...]
+    "webhookSecretTokenEnabled": { "index": 32, "kind": "parameter", 
"displayName": "Webhook Secret Token Enabled", "group": "security", "label": 
"consumer,security", "required": false, "type": "boolean", "javaType": 
"boolean", "deprecated": false, "autowired": false, "secret": false, 
"security": "insecure:dev", "insecureValue": "false", "defaultValue": true, 
"configurationClass": 
"org.apache.camel.component.telegram.TelegramConfiguration", 
"configurationField": "configuration", "descrip [...]
   }
 }
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/telegram-component.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/telegram-component.adoc
index c43282d76059..c96b9c371404 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/telegram-component.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/telegram-component.adoc
@@ -497,3 +497,16 @@ You need to ensure that your server is exposed to the 
internet and to pass the r
 *camel.component.webhook.configuration.webhook-external-url* property.
 
 Refer to the *camel-webhook* component documentation for instructions on how 
to set it.
+
+Camel registers the webhook with a secret token, which Telegram sends back in 
the `X-Telegram-Bot-Api-Secret-Token`
+header of every webhook request. A webhook request that does not carry the 
token is rejected with HTTP 403, and does
+not reach the route. By default, the token is derived from the authorization 
token, so it stays the same across
+restarts and replicas. If the webhook is registered outside Camel, set the 
`webhookSecretToken` option, and pass the
+same value as the `secret_token` parameter of the `setWebhook` method of the 
Telegram Bot API.
+
+The default webhook path is a hash of the endpoint URI, so adding an option to 
the URI, such as `webhookSecretToken`,
+changes the path. When the webhook is registered outside Camel, set the path 
with the `webhookPath` option of the
+*camel-webhook* component.
+
+Setting `webhookSecretTokenEnabled=false` registers the webhook without a 
secret token, and accepts webhook requests
+without verifying them, as earlier versions did.
diff --git 
a/components/camel-telegram/src/generated/java/org/apache/camel/component/telegram/TelegramEndpointConfigurer.java
 
b/components/camel-telegram/src/generated/java/org/apache/camel/component/telegram/TelegramEndpointConfigurer.java
index adb41c6ee40e..3a83db6e02f8 100644
--- 
a/components/camel-telegram/src/generated/java/org/apache/camel/component/telegram/TelegramEndpointConfigurer.java
+++ 
b/components/camel-telegram/src/generated/java/org/apache/camel/component/telegram/TelegramEndpointConfigurer.java
@@ -77,6 +77,10 @@ public class TelegramEndpointConfigurer extends 
PropertyConfigurerSupport implem
         case "timeout": 
target.getConfiguration().setTimeout(property(camelContext, 
java.lang.Integer.class, value)); return true;
         case "usefixeddelay":
         case "useFixedDelay": target.setUseFixedDelay(property(camelContext, 
boolean.class, value)); return true;
+        case "webhooksecrettoken":
+        case "webhookSecretToken": 
target.getConfiguration().setWebhookSecretToken(property(camelContext, 
java.lang.String.class, value)); return true;
+        case "webhooksecrettokenenabled":
+        case "webhookSecretTokenEnabled": 
target.getConfiguration().setWebhookSecretTokenEnabled(property(camelContext, 
boolean.class, value)); return true;
         default: return false;
         }
     }
@@ -138,6 +142,10 @@ public class TelegramEndpointConfigurer extends 
PropertyConfigurerSupport implem
         case "timeout": return java.lang.Integer.class;
         case "usefixeddelay":
         case "useFixedDelay": return boolean.class;
+        case "webhooksecrettoken":
+        case "webhookSecretToken": return java.lang.String.class;
+        case "webhooksecrettokenenabled":
+        case "webhookSecretTokenEnabled": return boolean.class;
         default: return null;
         }
     }
@@ -200,6 +208,10 @@ public class TelegramEndpointConfigurer extends 
PropertyConfigurerSupport implem
         case "timeout": return target.getConfiguration().getTimeout();
         case "usefixeddelay":
         case "useFixedDelay": return target.isUseFixedDelay();
+        case "webhooksecrettoken":
+        case "webhookSecretToken": return 
target.getConfiguration().getWebhookSecretToken();
+        case "webhooksecrettokenenabled":
+        case "webhookSecretTokenEnabled": return 
target.getConfiguration().isWebhookSecretTokenEnabled();
         default: return null;
         }
     }
diff --git 
a/components/camel-telegram/src/generated/java/org/apache/camel/component/telegram/TelegramEndpointUriFactory.java
 
b/components/camel-telegram/src/generated/java/org/apache/camel/component/telegram/TelegramEndpointUriFactory.java
index 1b0bc2a36ccf..0b6e51109dda 100644
--- 
a/components/camel-telegram/src/generated/java/org/apache/camel/component/telegram/TelegramEndpointUriFactory.java
+++ 
b/components/camel-telegram/src/generated/java/org/apache/camel/component/telegram/TelegramEndpointUriFactory.java
@@ -24,7 +24,7 @@ public class TelegramEndpointUriFactory extends 
org.apache.camel.support.compone
     private static final Set<String> ENDPOINT_IDENTITY_PROPERTY_NAMES;
     private static final Map<String, String> MULTI_VALUE_PREFIXES;
     static {
-        Set<String> props = new HashSet<>(31);
+        Set<String> props = new HashSet<>(33);
         props.add("authorizationToken");
         props.add("backoffErrorThreshold");
         props.add("backoffIdleThreshold");
@@ -56,9 +56,12 @@ public class TelegramEndpointUriFactory extends 
org.apache.camel.support.compone
         props.add("timeout");
         props.add("type");
         props.add("useFixedDelay");
+        props.add("webhookSecretToken");
+        props.add("webhookSecretTokenEnabled");
         PROPERTY_NAMES = Collections.unmodifiableSet(props);
-        Set<String> secretProps = new HashSet<>(1);
+        Set<String> secretProps = new HashSet<>(2);
         secretProps.add("authorizationToken");
+        secretProps.add("webhookSecretToken");
         SECRET_PROPERTY_NAMES = Collections.unmodifiableSet(secretProps);
         ENDPOINT_IDENTITY_PROPERTY_NAMES = Collections.emptySet();
         Map<String, String> prefixes = new HashMap<>(1);
diff --git 
a/components/camel-telegram/src/generated/resources/META-INF/org/apache/camel/component/telegram/telegram.json
 
b/components/camel-telegram/src/generated/resources/META-INF/org/apache/camel/component/telegram/telegram.json
index 830113732dce..0fa748818ba5 100644
--- 
a/components/camel-telegram/src/generated/resources/META-INF/org/apache/camel/component/telegram/telegram.json
+++ 
b/components/camel-telegram/src/generated/resources/META-INF/org/apache/camel/component/telegram/telegram.json
@@ -72,6 +72,8 @@
     "startScheduler": { "index": 27, "kind": "parameter", "displayName": 
"Start Scheduler", "group": "scheduler", "label": "consumer,scheduler", 
"required": false, "type": "boolean", "javaType": "boolean", "deprecated": 
false, "autowired": false, "secret": false, "defaultValue": true, 
"description": "Whether the scheduler should be auto started." },
     "timeUnit": { "index": 28, "kind": "parameter", "displayName": "Time 
Unit", "group": "scheduler", "label": "consumer,scheduler", "required": false, 
"type": "enum", "javaType": "java.util.concurrent.TimeUnit", "enum": [ 
"NANOSECONDS", "MICROSECONDS", "MILLISECONDS", "SECONDS", "MINUTES", "HOURS", 
"DAYS" ], "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": "MILLISECONDS", "description": "Time unit for initialDelay and 
delay options." },
     "useFixedDelay": { "index": 29, "kind": "parameter", "displayName": "Use 
Fixed Delay", "group": "scheduler", "label": "consumer,scheduler", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": true, "description": 
"Controls if fixed delay or fixed rate is used. See ScheduledExecutorService in 
JDK for details." },
-    "authorizationToken": { "index": 30, "kind": "parameter", "displayName": 
"Authorization Token", "group": "security", "label": "security", "required": 
true, "type": "string", "javaType": "java.lang.String", "deprecated": false, 
"deprecationNote": "", "autowired": false, "secret": true, "security": 
"secret", "configurationClass": 
"org.apache.camel.component.telegram.TelegramConfiguration", 
"configurationField": "configuration", "description": "The authorization token 
for using the bot  [...]
+    "authorizationToken": { "index": 30, "kind": "parameter", "displayName": 
"Authorization Token", "group": "security", "label": "security", "required": 
true, "type": "string", "javaType": "java.lang.String", "deprecated": false, 
"deprecationNote": "", "autowired": false, "secret": true, "security": 
"secret", "configurationClass": 
"org.apache.camel.component.telegram.TelegramConfiguration", 
"configurationField": "configuration", "description": "The authorization token 
for using the bot  [...]
+    "webhookSecretToken": { "index": 31, "kind": "parameter", "displayName": 
"Webhook Secret Token", "group": "security", "label": "consumer,security", 
"required": false, "type": "string", "javaType": "java.lang.String", 
"deprecated": false, "autowired": false, "secret": true, "security": "secret", 
"configurationClass": 
"org.apache.camel.component.telegram.TelegramConfiguration", 
"configurationField": "configuration", "description": "The secret token that 
Telegram sends in the X-Telegram [...]
+    "webhookSecretTokenEnabled": { "index": 32, "kind": "parameter", 
"displayName": "Webhook Secret Token Enabled", "group": "security", "label": 
"consumer,security", "required": false, "type": "boolean", "javaType": 
"boolean", "deprecated": false, "autowired": false, "secret": false, 
"security": "insecure:dev", "insecureValue": "false", "defaultValue": true, 
"configurationClass": 
"org.apache.camel.component.telegram.TelegramConfiguration", 
"configurationField": "configuration", "descrip [...]
   }
 }
diff --git a/components/camel-telegram/src/main/docs/telegram-component.adoc 
b/components/camel-telegram/src/main/docs/telegram-component.adoc
index c43282d76059..c96b9c371404 100644
--- a/components/camel-telegram/src/main/docs/telegram-component.adoc
+++ b/components/camel-telegram/src/main/docs/telegram-component.adoc
@@ -497,3 +497,16 @@ You need to ensure that your server is exposed to the 
internet and to pass the r
 *camel.component.webhook.configuration.webhook-external-url* property.
 
 Refer to the *camel-webhook* component documentation for instructions on how 
to set it.
+
+Camel registers the webhook with a secret token, which Telegram sends back in 
the `X-Telegram-Bot-Api-Secret-Token`
+header of every webhook request. A webhook request that does not carry the 
token is rejected with HTTP 403, and does
+not reach the route. By default, the token is derived from the authorization 
token, so it stays the same across
+restarts and replicas. If the webhook is registered outside Camel, set the 
`webhookSecretToken` option, and pass the
+same value as the `secret_token` parameter of the `setWebhook` method of the 
Telegram Bot API.
+
+The default webhook path is a hash of the endpoint URI, so adding an option to 
the URI, such as `webhookSecretToken`,
+changes the path. When the webhook is registered outside Camel, set the path 
with the `webhookPath` option of the
+*camel-webhook* component.
+
+Setting `webhookSecretTokenEnabled=false` registers the webhook without a 
secret token, and accepts webhook requests
+without verifying them, as earlier versions did.
diff --git 
a/components/camel-telegram/src/main/java/org/apache/camel/component/telegram/TelegramConfiguration.java
 
b/components/camel-telegram/src/main/java/org/apache/camel/component/telegram/TelegramConfiguration.java
index 3ccd3bf25fbb..9b9543058c48 100644
--- 
a/components/camel-telegram/src/main/java/org/apache/camel/component/telegram/TelegramConfiguration.java
+++ 
b/components/camel-telegram/src/main/java/org/apache/camel/component/telegram/TelegramConfiguration.java
@@ -62,6 +62,20 @@ public class TelegramConfiguration {
               optionalPrefix = "consumer.", defaultValue = "100", label = 
"consumer")
     private Integer limit = 100;
 
+    @UriParam(description = "The secret token that Telegram sends in the 
X-Telegram-Bot-Api-Secret-Token header of every webhook request."
+                            + " In webhook mode, Camel registers the webhook 
with this token, and rejects a webhook request that does not"
+                            + " carry it with HTTP 403. Only the characters 
A-Z, a-z, 0-9, _ and - are allowed, up to 256 characters."
+                            + " When not set, a token derived from the 
authorization token is used. Set it when the webhook is"
+                            + " registered outside Camel, and register the 
webhook with the same secret_token.",
+              label = "consumer,security", security = "secret")
+    private String webhookSecretToken;
+
+    @UriParam(description = "Whether, in webhook mode, Camel registers the 
webhook with a secret token and rejects a webhook request"
+                            + " that does not carry it. Set it to false to 
restore the behaviour of earlier versions, where the webhook"
+                            + " was registered without a secret token and 
webhook requests were not verified.",
+              label = "consumer,security", defaultValue = "true", security = 
"insecure:dev", insecureValue = "false")
+    private boolean webhookSecretTokenEnabled = true;
+
     @UriParam(label = "advanced",
               description = "Can be used to set an alternative base URI, e.g. 
when you want to test the component against a mock Telegram API")
     private String baseUri;
@@ -133,6 +147,22 @@ public class TelegramConfiguration {
         this.limit = limit;
     }
 
+    public String getWebhookSecretToken() {
+        return webhookSecretToken;
+    }
+
+    public void setWebhookSecretToken(String webhookSecretToken) {
+        this.webhookSecretToken = webhookSecretToken;
+    }
+
+    public boolean isWebhookSecretTokenEnabled() {
+        return webhookSecretTokenEnabled;
+    }
+
+    public void setWebhookSecretTokenEnabled(boolean 
webhookSecretTokenEnabled) {
+        this.webhookSecretTokenEnabled = webhookSecretTokenEnabled;
+    }
+
     public String getBaseUri() {
         return baseUri;
     }
diff --git 
a/components/camel-telegram/src/main/java/org/apache/camel/component/telegram/TelegramEndpoint.java
 
b/components/camel-telegram/src/main/java/org/apache/camel/component/telegram/TelegramEndpoint.java
index b3530de0724b..f1f248bb383e 100644
--- 
a/components/camel-telegram/src/main/java/org/apache/camel/component/telegram/TelegramEndpoint.java
+++ 
b/components/camel-telegram/src/main/java/org/apache/camel/component/telegram/TelegramEndpoint.java
@@ -19,8 +19,15 @@ package org.apache.camel.component.telegram;
 import java.net.InetSocketAddress;
 import java.net.ProxySelector;
 import java.net.http.HttpClient;
+import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
+import java.util.Base64;
 import java.util.Collections;
 import java.util.List;
+import java.util.regex.Pattern;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
 
 import org.apache.camel.Category;
 import org.apache.camel.Component;
@@ -46,6 +53,7 @@ import org.slf4j.LoggerFactory;
              category = { Category.CLOUD, Category.API, Category.CHAT }, 
headersClass = TelegramConstants.class)
 public class TelegramEndpoint extends ScheduledPollEndpoint implements 
WebhookCapableEndpoint, EndpointServiceLocation {
     private static final Logger LOG = 
LoggerFactory.getLogger(TelegramEndpoint.class);
+    private static final Pattern WEBHOOK_SECRET_TOKEN_PATTERN = 
Pattern.compile("[A-Za-z0-9_-]{1,256}");
 
     @UriParam
     private TelegramConfiguration configuration;
@@ -122,16 +130,56 @@ public class TelegramEndpoint extends 
ScheduledPollEndpoint implements WebhookCa
 
     @Override
     public Processor createWebhookHandler(Processor next) {
-        return new TelegramWebhookProcessor(next);
+        if (!configuration.isWebhookSecretTokenEnabled()) {
+            LOG.warn("The webhook secret token is disabled 
(webhookSecretTokenEnabled=false): webhook requests to {} are not"
+                     + " verified",
+                    this);
+            return new TelegramWebhookProcessor(next, null);
+        }
+        return new TelegramWebhookProcessor(next, resolveWebhookSecretToken());
     }
 
     @Override
     public void registerWebhook() throws Exception {
-        if 
(!telegramService.setWebhook(webhookConfiguration.computeFullExternalUrl())) {
+        String url = webhookConfiguration.computeFullExternalUrl();
+        boolean registered = configuration.isWebhookSecretTokenEnabled()
+                ? telegramService.setWebhook(url, resolveWebhookSecretToken())
+                : telegramService.setWebhook(url);
+        if (!registered) {
             throw new RuntimeCamelException("The Telegram API refused to 
register a webhook");
         }
     }
 
+    /**
+     * Returns the secret token of the webhook: the configured one, or else 
one derived from the authorization token,
+     * which stays the same across restarts and replicas.
+     */
+    String resolveWebhookSecretToken() {
+        String token = configuration.getWebhookSecretToken();
+        if (token == null) {
+            return 
deriveWebhookSecretToken(configuration.getAuthorizationToken());
+        }
+        if (!WEBHOOK_SECRET_TOKEN_PATTERN.matcher(token).matches()) {
+            throw new IllegalArgumentException(
+                    "The webhookSecretToken must be 1 to 256 characters long, 
using only A-Z, a-z, 0-9, _ and -");
+        }
+        return token;
+    }
+
+    static String deriveWebhookSecretToken(String authorizationToken) {
+        try {
+            Mac mac = Mac.getInstance("HmacSHA256");
+            // the authorization token (the secret) is the key, and a fixed 
label is the message: the derived token
+            // cannot be computed without the authorization token, and changes 
only when the authorization token does
+            mac.init(new 
SecretKeySpec(authorizationToken.getBytes(StandardCharsets.UTF_8), 
"HmacSHA256"));
+            byte[] digest = 
mac.doFinal("camel-telegram-webhook-secret-token".getBytes(StandardCharsets.UTF_8));
+            // base64url without padding only uses the characters that 
Telegram allows in a secret token
+            return 
Base64.getUrlEncoder().withoutPadding().encodeToString(digest);
+        } catch (GeneralSecurityException e) {
+            throw new RuntimeCamelException("Cannot derive the webhook secret 
token", e);
+        }
+    }
+
     @Override
     public void unregisterWebhook() throws Exception {
         if (!telegramService.removeWebhook()) {
diff --git 
a/components/camel-telegram/src/main/java/org/apache/camel/component/telegram/TelegramService.java
 
b/components/camel-telegram/src/main/java/org/apache/camel/component/telegram/TelegramService.java
index 387c4c773156..3610ac29e03e 100644
--- 
a/components/camel-telegram/src/main/java/org/apache/camel/component/telegram/TelegramService.java
+++ 
b/components/camel-telegram/src/main/java/org/apache/camel/component/telegram/TelegramService.java
@@ -38,6 +38,16 @@ public interface TelegramService {
 
     boolean setWebhook(String url);
 
+    /**
+     * Registers the webhook. Telegram sends the given secret token in the 
{@code X-Telegram-Bot-Api-Secret-Token}
+     * header of every webhook request.
+     *
+     * @param  url         the URL of the webhook
+     * @param  secretToken the secret token
+     * @return             {@code true} if Telegram registered the webhook
+     */
+    boolean setWebhook(String url, String secretToken);
+
     boolean removeWebhook();
 
 }
diff --git 
a/components/camel-telegram/src/main/java/org/apache/camel/component/telegram/TelegramWebhookProcessor.java
 
b/components/camel-telegram/src/main/java/org/apache/camel/component/telegram/TelegramWebhookProcessor.java
index 3479555b58b2..2876a708a720 100644
--- 
a/components/camel-telegram/src/main/java/org/apache/camel/component/telegram/TelegramWebhookProcessor.java
+++ 
b/components/camel-telegram/src/main/java/org/apache/camel/component/telegram/TelegramWebhookProcessor.java
@@ -17,6 +17,8 @@
 package org.apache.camel.component.telegram;
 
 import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
 import org.apache.camel.AsyncCallback;
@@ -26,21 +28,51 @@ import org.apache.camel.Processor;
 import org.apache.camel.component.telegram.model.Update;
 import org.apache.camel.support.AsyncProcessorConverterHelper;
 import org.apache.camel.support.AsyncProcessorSupport;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import static 
org.apache.camel.component.telegram.util.TelegramMessageHelper.populateExchange;
 
 public class TelegramWebhookProcessor extends AsyncProcessorSupport {
 
+    /**
+     * The header in which Telegram sends the secret token the webhook was 
registered with.
+     */
+    public static final String SECRET_TOKEN_HEADER = 
"X-Telegram-Bot-Api-Secret-Token";
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(TelegramWebhookProcessor.class);
     private static final ObjectMapper MAPPER = new ObjectMapper();
 
     private AsyncProcessor next;
+    private final byte[] secretToken;
 
-    public TelegramWebhookProcessor(Processor next) {
+    /**
+     * @param next        the processor that receives the updates
+     * @param secretToken the secret token that a webhook request must carry, 
or {@code null} to accept every webhook
+     *                    request without verifying it
+     */
+    public TelegramWebhookProcessor(Processor next, String secretToken) {
         this.next = AsyncProcessorConverterHelper.convert(next);
+        this.secretToken = secretToken != null ? 
secretToken.getBytes(StandardCharsets.UTF_8) : null;
     }
 
     @Override
     public boolean process(Exchange exchange, AsyncCallback callback) {
+        if (secretToken != null) {
+            String token = exchange.getIn().getHeader(SECRET_TOKEN_HEADER, 
String.class);
+            if (token == null || !MessageDigest.isEqual(secretToken, 
token.getBytes(StandardCharsets.UTF_8))) {
+                LOG.warn("Rejecting a Telegram webhook request with a missing 
or invalid {} header."
+                         + " A webhook registered outside Camel must use the 
webhookSecretToken as its secret_token.",
+                        SECRET_TOKEN_HEADER);
+                exchange.getMessage().setHeader(Exchange.HTTP_RESPONSE_CODE, 
403);
+                exchange.getMessage().setBody("");
+                callback.done(true);
+                return true;
+            }
+        }
+        // the token is not passed on to the route
+        exchange.getIn().removeHeader(SECRET_TOKEN_HEADER);
+
         // Let's process the body and transform it into a Telegram incoming 
message
         Update update;
         try (InputStream source = exchange.getIn().getBody(InputStream.class)) 
{
diff --git 
a/components/camel-telegram/src/main/java/org/apache/camel/component/telegram/service/TelegramAsyncHandler.java
 
b/components/camel-telegram/src/main/java/org/apache/camel/component/telegram/service/TelegramAsyncHandler.java
index 3b180356d1a8..60aa29025830 100644
--- 
a/components/camel-telegram/src/main/java/org/apache/camel/component/telegram/service/TelegramAsyncHandler.java
+++ 
b/components/camel-telegram/src/main/java/org/apache/camel/component/telegram/service/TelegramAsyncHandler.java
@@ -22,6 +22,7 @@ import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.Reader;
 import java.io.UnsupportedEncodingException;
+import java.net.URI;
 import java.net.http.HttpResponse;
 import java.nio.charset.StandardCharsets;
 
@@ -38,7 +39,8 @@ import org.slf4j.LoggerFactory;
 public class TelegramAsyncHandler {
     private static final Logger LOG = 
LoggerFactory.getLogger(TelegramAsyncHandler.class);
 
-    private final String uri;
+    // describes the request without its URI, which contains the authorization 
token
+    private final String request;
     private final Class<? extends MessageResult> resultClass;
     private final ObjectMapper mapper;
     private final Exchange exchange;
@@ -46,7 +48,7 @@ public class TelegramAsyncHandler {
 
     TelegramAsyncHandler(String uri, Class<? extends MessageResult> 
resultClass, ObjectMapper mapper, Exchange exchange,
                          AsyncCallback callback) {
-        this.uri = uri;
+        this.request = TelegramServiceRestBotAPIAdapter.describe("POST", 
URI.create(uri));
         this.resultClass = resultClass;
         this.mapper = mapper;
         this.exchange = exchange;
@@ -78,6 +80,10 @@ public class TelegramAsyncHandler {
 
     }
 
+    /**
+     * Sets the result of a successful response as the message body. Throws 
for a response with an error status and for
+     * a body that cannot be read; the caller fails the exchange with that 
exception and completes it.
+     */
     public Object handleCompressedResponse(HttpResponse<InputStream> response) 
{
         Object result;
         final boolean success = response.statusCode() >= 200 && 
response.statusCode() < 300;
@@ -97,7 +103,7 @@ public class TelegramAsyncHandler {
             if (success) {
                 if (LOG.isTraceEnabled()) {
                     final String body = IOHelper.toString(r);
-                    LOG.trace("Received body for {}: {}", uri, body);
+                    LOG.trace("Received body for {}: {}", request, body);
                     result = mapper.readValue(body, resultClass);
                 } else {
                     result = mapper.readValue(r, resultClass);
@@ -106,12 +112,12 @@ public class TelegramAsyncHandler {
                 exchange.getMessage().setBody(result);
             } else {
                 throw new RuntimeCamelException(
-                        uri + " responded: " + response.statusCode() + 
IOHelper.toString(r));
+                        request + " responded: " + response.statusCode() + 
IOHelper.toString(r));
             }
         } catch (UnsupportedEncodingException ex) {
             throw new RuntimeException(ex);
         } catch (IOException ex) {
-            throw new RuntimeCamelException("Could not parse the response from 
" + uri, ex);
+            throw new RuntimeCamelException("Could not parse the response from 
" + request, ex);
         } finally {
             callback.done(false);
         }
diff --git 
a/components/camel-telegram/src/main/java/org/apache/camel/component/telegram/service/TelegramServiceRestBotAPIAdapter.java
 
b/components/camel-telegram/src/main/java/org/apache/camel/component/telegram/service/TelegramServiceRestBotAPIAdapter.java
index e96a3bb184f5..a509dfbb7eb1 100644
--- 
a/components/camel-telegram/src/main/java/org/apache/camel/component/telegram/service/TelegramServiceRestBotAPIAdapter.java
+++ 
b/components/camel-telegram/src/main/java/org/apache/camel/component/telegram/service/TelegramServiceRestBotAPIAdapter.java
@@ -19,9 +19,11 @@ package org.apache.camel.component.telegram.service;
 import java.io.IOException;
 import java.net.URI;
 import java.net.URISyntaxException;
+import java.net.URLEncoder;
 import java.net.http.HttpClient;
 import java.net.http.HttpRequest;
 import java.net.http.HttpResponse;
+import java.nio.charset.StandardCharsets;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -152,6 +154,7 @@ public class TelegramServiceRestBotAPIAdapter implements 
TelegramService {
     }
 
     <T> T sendSyncRequest(final HttpRequest request, Class<T> resultType) {
+        final String description = describe(request.method(), request.uri());
         try {
             final HttpResponse<String> response = client.send(request, 
HttpResponse.BodyHandlers.ofString());
 
@@ -159,29 +162,44 @@ public class TelegramServiceRestBotAPIAdapter implements 
TelegramService {
             if (code >= 200 && code < 300) {
                 final String responseBody = response.body();
                 if (LOG.isTraceEnabled()) {
-                    LOG.trace("Received body for {} {}: {}", request.method(), 
request.uri(), responseBody);
+                    LOG.trace("Received body for {}: {}", description, 
responseBody);
                 }
                 return mapper.readValue(responseBody, resultType);
             } else {
                 throw new TelegramException(
-                        "Could not " + request.method() + " " + request.uri() 
+ ": " + response.statusCode() + " "
-                                            + response.body(),
+                        "Could not " + description + ": " + 
response.statusCode() + " " + response.body(),
                         response.statusCode(), response.body());
             }
         } catch (JsonProcessingException e) {
-            throw new RuntimeCamelException(
-                    "Could not parse the response from " + request.method() + 
" " + request.uri(), e);
+            throw new RuntimeCamelException("Could not parse the response from 
" + description, e);
         } catch (IOException e) {
-            throw new RuntimeCamelException("Could not request " + 
request.method() + " " + request.uri(), e);
+            throw new RuntimeCamelException("Could not request " + 
description, e);
         } catch (InterruptedException e) {
             Thread.currentThread().interrupt();
             throw new RuntimeCamelException(e);
         }
     }
 
+    /**
+     * Describes a request to the Bot API in logs and exception messages by 
its HTTP method and the method of the Bot
+     * API, such as {@code GET getUpdates}. The request URI is not used, as it 
contains the authorization token.
+     */
+    static String describe(String httpMethod, URI uri) {
+        String path = uri.getPath();
+        return httpMethod + " " + path.substring(path.lastIndexOf('/') + 1);
+    }
+
     @Override
     public boolean setWebhook(String url) {
-        final String uri = baseUri + "/setWebhook?url=" + url;
+        return setWebhook(url, null);
+    }
+
+    @Override
+    public boolean setWebhook(String url, String secretToken) {
+        String uri = baseUri + "/setWebhook?url=" + url;
+        if (secretToken != null) {
+            uri += "&secret_token=" + URLEncoder.encode(secretToken, 
StandardCharsets.UTF_8);
+        }
 
         final HttpRequest.Builder request = 
HttpRequest.newBuilder().uri(URI.create(uri)).GET();
 
diff --git 
a/components/camel-telegram/src/test/java/org/apache/camel/component/telegram/TelegramWebhookCallTest.java
 
b/components/camel-telegram/src/test/java/org/apache/camel/component/telegram/TelegramWebhookCallTest.java
index 8ddd85af58a8..d15edb028698 100644
--- 
a/components/camel-telegram/src/test/java/org/apache/camel/component/telegram/TelegramWebhookCallTest.java
+++ 
b/components/camel-telegram/src/test/java/org/apache/camel/component/telegram/TelegramWebhookCallTest.java
@@ -27,24 +27,78 @@ import 
org.apache.camel.component.webhook.WebhookConfiguration;
 import org.apache.camel.component.webhook.WebhookEndpoint;
 import org.junit.jupiter.api.Test;
 
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
 /**
- * Tests a producer that sends media information.
+ * Tests the webhook endpoint that receives the updates from Telegram.
  */
 public class TelegramWebhookCallTest extends TelegramTestSupport {
 
+    private static final String CONFIGURED_SECRET_TOKEN = 
"configured_Secret-token-42";
+
     @Test
     public void testWebhookCall() throws Exception {
+        MockEndpoint mock = getMockEndpoint("mock:endpoint");
+        mock.expectedBodiesReceived("aho");
+        
mock.message(0).header(TelegramWebhookProcessor.SECRET_TOKEN_HEADER).isNull();
+
+        Exchange response = call("webhook", 
TelegramEndpoint.deriveWebhookSecretToken("mock-token"));
+
+        assertEquals(204, 
response.getMessage().getHeader(Exchange.HTTP_RESPONSE_CODE, Integer.class));
+        mock.assertIsSatisfied();
+    }
+
+    @Test
+    void testWebhookCallWithConfiguredSecretToken() throws Exception {
+        MockEndpoint mock = getMockEndpoint("mock:endpoint");
+        mock.expectedBodiesReceived("aho");
+
+        Exchange response = call("webhook-configured-token", 
CONFIGURED_SECRET_TOKEN);
+
+        assertEquals(204, 
response.getMessage().getHeader(Exchange.HTTP_RESPONSE_CODE, Integer.class));
+        mock.assertIsSatisfied();
+    }
+
+    @Test
+    void testWebhookCallWithoutSecretTokenIsRejected() throws Exception {
+        Exchange response = call("webhook", null);
+
+        assertEquals(403, 
response.getMessage().getHeader(Exchange.HTTP_RESPONSE_CODE, Integer.class));
+        assertEquals(0, getMockEndpoint("mock:endpoint").getReceivedCounter());
+    }
+
+    @Test
+    void testWebhookCallWithWrongSecretTokenIsRejected() throws Exception {
+        Exchange response = call("webhook", CONFIGURED_SECRET_TOKEN);
+
+        assertEquals(403, 
response.getMessage().getHeader(Exchange.HTTP_RESPONSE_CODE, Integer.class));
+        assertEquals(0, getMockEndpoint("mock:endpoint").getReceivedCounter());
+    }
+
+    @Test
+    void testWebhookCallWithoutSecretTokenWhenDisabled() throws Exception {
+        MockEndpoint mock = getMockEndpoint("mock:endpoint");
+        mock.expectedBodiesReceived("aho");
+
+        Exchange response = call("webhook-secret-token-disabled", null);
+
+        assertEquals(204, 
response.getMessage().getHeader(Exchange.HTTP_RESPONSE_CODE, Integer.class));
+        mock.assertIsSatisfied();
+    }
+
+    private Exchange call(String routeId, String secretToken) throws Exception 
{
         WebhookConfiguration config
-                = ((WebhookEndpoint) 
context().getRoute("webhook").getConsumer().getEndpoint()).getConfiguration();
+                = ((WebhookEndpoint) 
context().getRoute(routeId).getConsumer().getEndpoint()).getConfiguration();
         String url = config.computeFullExternalUrl();
 
         try (InputStream content = 
getClass().getClassLoader().getResourceAsStream("messages/webhook-call.json")) {
-            MockEndpoint mock = getMockEndpoint("mock:endpoint");
-            mock.expectedBodiesReceived("aho");
-            mock.expectedMinimumMessageCount(1);
-
-            template().sendBodyAndHeader("netty-http:" + url, content, 
Exchange.CONTENT_TYPE, "application/json");
-            mock.assertIsSatisfied();
+            return template().request("netty-http:" + url + 
"?throwExceptionOnFailure=false", exchange -> {
+                exchange.getIn().setBody(content);
+                exchange.getIn().setHeader(Exchange.CONTENT_TYPE, 
"application/json");
+                if (secretToken != null) {
+                    
exchange.getIn().setHeader(TelegramWebhookProcessor.SECRET_TOKEN_HEADER, 
secretToken);
+                }
+            });
         }
     }
 
@@ -61,6 +115,18 @@ public class TelegramWebhookCallTest extends 
TelegramTestSupport {
                         .id("webhook")
                         .convertBodyTo(String.class)
                         .to("mock:endpoint");
+
+                
from("webhook:telegram:bots?authorizationToken=mock-token&webhookAutoRegister=false&webhookSecretToken="
+                     + CONFIGURED_SECRET_TOKEN)
+                        .id("webhook-configured-token")
+                        .convertBodyTo(String.class)
+                        .to("mock:endpoint");
+
+                
from("webhook:telegram:bots?authorizationToken=mock-token&webhookAutoRegister=false"
+                     + "&webhookSecretTokenEnabled=false")
+                        .id("webhook-secret-token-disabled")
+                        .convertBodyTo(String.class)
+                        .to("mock:endpoint");
             }
         };
     }
diff --git 
a/components/camel-telegram/src/test/java/org/apache/camel/component/telegram/TelegramWebhookSecretTokenTest.java
 
b/components/camel-telegram/src/test/java/org/apache/camel/component/telegram/TelegramWebhookSecretTokenTest.java
new file mode 100644
index 000000000000..3043b7ca3b13
--- /dev/null
+++ 
b/components/camel-telegram/src/test/java/org/apache/camel/component/telegram/TelegramWebhookSecretTokenTest.java
@@ -0,0 +1,132 @@
+/*
+ * 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.telegram;
+
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.camel.Exchange;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.telegram.model.WebhookResult;
+import org.apache.camel.component.telegram.util.TelegramTestSupport;
+import org.apache.camel.component.telegram.util.TelegramTestUtil;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.test.AvailablePortFinder;
+import org.junit.jupiter.api.Test;
+
+import static org.awaitility.Awaitility.await;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Tests the secret token that the webhook is registered with.
+ */
+class TelegramWebhookSecretTokenTest extends TelegramTestSupport {
+
+    private final List<String> setWebhookQueries = new 
CopyOnWriteArrayList<>();
+
+    @Test
+    void derivedSecretTokenIsStableAndUsesOnlyTheAllowedCharacters() {
+        String token = TelegramEndpoint.deriveWebhookSecretToken("mock-token");
+
+        assertEquals(token, 
TelegramEndpoint.deriveWebhookSecretToken("mock-token"));
+        assertNotEquals(token, 
TelegramEndpoint.deriveWebhookSecretToken("another-token"));
+        assertTrue(token.matches("[A-Za-z0-9_-]{43}"), token);
+    }
+
+    @Test
+    void invalidConfiguredSecretTokenIsRefused() {
+        TelegramEndpoint endpoint = context().getEndpoint(
+                
"telegram:bots?authorizationToken=mock-token&webhookSecretToken=invalid.token", 
TelegramEndpoint.class);
+
+        assertThrows(IllegalArgumentException.class, 
endpoint::resolveWebhookSecretToken);
+    }
+
+    @Test
+    void registrationSendsTheDerivedSecretToken() throws Exception {
+        String query = 
register("webhook:telegram:bots?webhookPath=/my-test&webhook-external-url=http://my-domain.com";);
+
+        assertEquals("url=http://my-domain.com/my-test&secret_token="; + 
TelegramEndpoint.deriveWebhookSecretToken("mock-token"),
+                query);
+    }
+
+    @Test
+    void registrationSendsTheConfiguredSecretToken() throws Exception {
+        String query = 
register("webhook:telegram:bots?webhookPath=/my-test&webhook-external-url=http://my-domain.com";
+                                + 
"&webhookSecretToken=configured_Secret-token-42");
+
+        
assertEquals("url=http://my-domain.com/my-test&secret_token=configured_Secret-token-42";,
 query);
+    }
+
+    @Test
+    void registrationSendsNoSecretTokenWhenDisabled() throws Exception {
+        String query = 
register("webhook:telegram:bots?webhookPath=/my-test&webhook-external-url=http://my-domain.com";
+                                + "&webhookSecretTokenEnabled=false");
+
+        assertEquals("url=http://my-domain.com/my-test";, query);
+    }
+
+    /**
+     * Starts a webhook route against a mock Telegram API, and returns the 
query of the setWebhook call.
+     */
+    private String register(String webhookUri) throws Exception {
+        WebhookResult result = new WebhookResult();
+        result.setOk(true);
+        result.setResult(true);
+        String response = TelegramTestUtil.serialize(result);
+
+        try (DefaultCamelContext mockContext = new DefaultCamelContext()) {
+            mockContext.addRoutes(new RouteBuilder() {
+                @Override
+                public void configure() {
+                    String api = "netty-http:http://localhost:"; + 
port.getPort() + "/botmock-token/";
+                    from(api + "setWebhook?httpMethodRestrict=GET")
+                            .process(exchange -> setWebhookQueries
+                                    
.add(exchange.getIn().getHeader(Exchange.HTTP_QUERY, String.class)))
+                            .setBody(constant(response));
+                    from(api + "deleteWebhook?httpMethodRestrict=GET")
+                            .setBody(constant(response));
+                }
+            });
+            mockContext.start();
+
+            context().addRoutes(new RouteBuilder() {
+                @Override
+                public void configure() {
+                    restConfiguration()
+                            .host("localhost")
+                            .port(AvailablePortFinder.getNextAvailable());
+
+                    from(webhookUri).to("mock:endpoint");
+                }
+            });
+            context().start();
+            await().atMost(5, TimeUnit.SECONDS).until(() -> 
!setWebhookQueries.isEmpty());
+            context().stop();
+        }
+        assertEquals(1, setWebhookQueries.size());
+        return setWebhookQueries.get(0);
+    }
+
+    @Override
+    public boolean isUseRouteBuilder() {
+        return false;
+    }
+}
diff --git 
a/components/camel-telegram/src/test/java/org/apache/camel/component/telegram/service/TelegramServiceRestBotAPIAdapterErrorTest.java
 
b/components/camel-telegram/src/test/java/org/apache/camel/component/telegram/service/TelegramServiceRestBotAPIAdapterErrorTest.java
new file mode 100644
index 000000000000..d41ec9e4d908
--- /dev/null
+++ 
b/components/camel-telegram/src/test/java/org/apache/camel/component/telegram/service/TelegramServiceRestBotAPIAdapterErrorTest.java
@@ -0,0 +1,138 @@
+/*
+ * 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.telegram.service;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpHeaders;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+import java.util.Optional;
+
+import javax.net.ssl.SSLSession;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.camel.RoutesBuilder;
+import org.apache.camel.RuntimeCamelException;
+import org.apache.camel.component.telegram.TelegramException;
+import org.apache.camel.component.telegram.model.MessageResult;
+import org.apache.camel.component.telegram.util.TelegramMockRoutes;
+import org.apache.camel.component.telegram.util.TelegramTestSupport;
+import org.apache.camel.support.DefaultExchange;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Tests that the errors of the Bot API requests do not show the request URI, 
which contains the authorization token.
+ */
+class TelegramServiceRestBotAPIAdapterErrorTest extends TelegramTestSupport {
+
+    @Test
+    void describeUsesTheMethodOfTheBotApi() {
+        assertEquals("GET getUpdates",
+                TelegramServiceRestBotAPIAdapter.describe("GET",
+                        
URI.create("https://api.telegram.org/bot123456:authorization-token/getUpdates?offset=1&limit=100";)));
+    }
+
+    @Test
+    void errorDoesNotShowTheRequestUri() {
+        TelegramServiceRestBotAPIAdapter adapter = new 
TelegramServiceRestBotAPIAdapter(
+                HttpClient.newHttpClient(), "http://localhost:"; + 
port.getPort(), "mock-token", 1024);
+
+        TelegramException e = assertThrows(TelegramException.class,
+                () -> adapter.setWebhook("http://my-domain.com/my-test";, 
"my-secret-token"));
+
+        assertTrue(e.getMessage().startsWith("Could not GET setWebhook: 401"), 
e.getMessage());
+        assertFalse(e.getMessage().contains("mock-token"), e.getMessage());
+        assertFalse(e.getMessage().contains("my-secret-token"), 
e.getMessage());
+    }
+
+    @Test
+    void asyncErrorDoesNotShowTheRequestUri() {
+        TelegramAsyncHandler handler = new TelegramAsyncHandler(
+                "http://localhost:"; + port.getPort() + 
"/botmock-token/sendMessage", MessageResult.class, new ObjectMapper(),
+                new DefaultExchange(context()));
+
+        RuntimeCamelException e = assertThrows(RuntimeCamelException.class,
+                () -> handler.handleCompressedResponse(response(400, 
"{\"ok\":false}")));
+
+        assertEquals("POST sendMessage responded: 400{\"ok\":false}", 
e.getMessage());
+    }
+
+    private static HttpResponse<InputStream> response(int statusCode, String 
body) {
+        return new HttpResponse<>() {
+            @Override
+            public int statusCode() {
+                return statusCode;
+            }
+
+            @Override
+            public HttpRequest request() {
+                return null;
+            }
+
+            @Override
+            public Optional<HttpResponse<InputStream>> previousResponse() {
+                return Optional.empty();
+            }
+
+            @Override
+            public HttpHeaders headers() {
+                return HttpHeaders.of(Map.of(), (name, value) -> true);
+            }
+
+            @Override
+            public InputStream body() {
+                return new 
ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8));
+            }
+
+            @Override
+            public Optional<SSLSession> sslSession() {
+                return Optional.empty();
+            }
+
+            @Override
+            public URI uri() {
+                return null;
+            }
+
+            @Override
+            public HttpClient.Version version() {
+                return HttpClient.Version.HTTP_1_1;
+            }
+        };
+    }
+
+    @Override
+    protected RoutesBuilder createRouteBuilder() {
+        return getMockRoutes();
+    }
+
+    @Override
+    protected TelegramMockRoutes createMockRoutes() {
+        return new TelegramMockRoutes(port.getPort())
+                .addErrorEndpoint("setWebhook", "GET", 401);
+    }
+}
diff --git 
a/core/camel-util/src/main/java/org/apache/camel/util/SecurityUtils.java 
b/core/camel-util/src/main/java/org/apache/camel/util/SecurityUtils.java
index 9c8a2d6f71cc..e56371556759 100644
--- a/core/camel-util/src/main/java/org/apache/camel/util/SecurityUtils.java
+++ b/core/camel-util/src/main/java/org/apache/camel/util/SecurityUtils.java
@@ -89,6 +89,7 @@ public final class SecurityUtils {
         map.put("validateauth", new SecurityOption(INSECURE_DEV, VALUE_FALSE));
         map.put("validatecertificates", new SecurityOption(INSECURE_SSL, 
VALUE_FALSE));
         map.put("verifyssl", new SecurityOption(INSECURE_SSL, VALUE_FALSE));
+        map.put("webhooksecrettokenenabled", new SecurityOption(INSECURE_DEV, 
VALUE_FALSE));
         map.put("x509hostnameverifier", new SecurityOption(INSECURE_SSL, ""));
         // SECURITY-OPTIONS: END
         SECURITY_OPTIONS = Collections.unmodifiableMap(map);
diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc
index c43d1d8e7c24..56b769a55a3f 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc
@@ -20,6 +20,31 @@ counted each exchange once per member route instead of once 
per entry into the g
 unintended side effect of CAMEL-24590. The counters now count each exchange 
once per entry into the
 group again, as in 4.22.0: calls nested inside another route of the same group 
are not counted again.
 
+=== camel-telegram - webhook requests are verified with a secret token
+
+In webhook mode, Camel now registers the webhook with a secret token, which 
Telegram sends in the
+`X-Telegram-Bot-Api-Secret-Token` header of every webhook request, and rejects 
a webhook request that does not carry
+the token with HTTP 403. The token is the value of the new 
`webhookSecretToken` option or, when that option is not
+set, a value derived from the authorization token. A client that calls the 
webhook endpoint directly, for example in
+a test, must send the header too.
+
+When Camel registers the webhook at startup (`webhookAutoRegister=true`, the 
default), nothing needs to change: each
+upgraded instance registers the webhook with the token, and the instances that 
are not upgraded yet ignore the header.
+
+When the webhook is registered in another way, through the 
`WebhookRoutePolicy` or outside Camel, the registration
+made with the previous version has no secret token, and the upgraded instances 
reject every update until the webhook
+is registered again with it. Register the webhook again first, then upgrade 
the instances that receive the updates:
+instances of the previous version ignore the header. With the 
`WebhookRoutePolicy`, run the registration with the new
+version. Outside Camel, set `webhookSecretToken`, and pass the same value as 
the `secret_token` parameter of
+`setWebhook`.
+
+The default webhook path is a hash of the endpoint URI, so adding an option to 
the URI, such as `webhookSecretToken`,
+changes the path. When the webhook is not registered by Camel at startup, set 
`webhookPath` to keep the path that is
+registered.
+
+To restore the previous behaviour, set `webhookSecretTokenEnabled=false`: the 
webhook is then registered without a
+secret token, and webhook requests are not verified.
+
 === camel-docling - output path handling
 
 The `CamelDoclingOutputFilePath` header, which selects the CLI output 
directory, is now normalized lexically
diff --git 
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/TelegramEndpointBuilderFactory.java
 
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/TelegramEndpointBuilderFactory.java
index 7b1d878f7fd7..5d812294f4fc 100644
--- 
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/TelegramEndpointBuilderFactory.java
+++ 
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/TelegramEndpointBuilderFactory.java
@@ -692,6 +692,65 @@ public interface TelegramEndpointBuilderFactory {
             doSetProperty("authorizationToken", authorizationToken);
             return this;
         }
+        /**
+         * The secret token that Telegram sends in the
+         * X-Telegram-Bot-Api-Secret-Token header of every webhook request. In
+         * webhook mode, Camel registers the webhook with this token, and
+         * rejects a webhook request that does not carry it with HTTP 403. Only
+         * the characters A-Z, a-z, 0-9, _ and - are allowed, up to 256
+         * characters. When not set, a token derived from the authorization
+         * token is used. Set it when the webhook is registered outside Camel,
+         * and register the webhook with the same secret_token.
+         * 
+         * The option is a: <code>java.lang.String</code> type.
+         * 
+         * Group: security
+         * 
+         * @param webhookSecretToken the value to set
+         * @return the dsl builder
+         */
+        default TelegramEndpointConsumerBuilder webhookSecretToken(String 
webhookSecretToken) {
+            doSetProperty("webhookSecretToken", webhookSecretToken);
+            return this;
+        }
+        /**
+         * Whether, in webhook mode, Camel registers the webhook with a secret
+         * token and rejects a webhook request that does not carry it. Set it 
to
+         * false to restore the behaviour of earlier versions, where the 
webhook
+         * was registered without a secret token and webhook requests were not
+         * verified.
+         * 
+         * The option is a: <code>boolean</code> type.
+         * 
+         * Default: true
+         * Group: security
+         * 
+         * @param webhookSecretTokenEnabled the value to set
+         * @return the dsl builder
+         */
+        default TelegramEndpointConsumerBuilder 
webhookSecretTokenEnabled(boolean webhookSecretTokenEnabled) {
+            doSetProperty("webhookSecretTokenEnabled", 
webhookSecretTokenEnabled);
+            return this;
+        }
+        /**
+         * Whether, in webhook mode, Camel registers the webhook with a secret
+         * token and rejects a webhook request that does not carry it. Set it 
to
+         * false to restore the behaviour of earlier versions, where the 
webhook
+         * was registered without a secret token and webhook requests were not
+         * verified.
+         * 
+         * The option will be converted to a <code>boolean</code> type.
+         * 
+         * Default: true
+         * Group: security
+         * 
+         * @param webhookSecretTokenEnabled the value to set
+         * @return the dsl builder
+         */
+        default TelegramEndpointConsumerBuilder 
webhookSecretTokenEnabled(String webhookSecretTokenEnabled) {
+            doSetProperty("webhookSecretTokenEnabled", 
webhookSecretTokenEnabled);
+            return this;
+        }
     }
 
     /**

Reply via email to