← back to blog

Telegram webhook secret token: how to verify a webhook request is genuine

Why this matters

If your bot uses a webhook instead of long polling, Telegram pushes updates to a URL you control by sending an HTTP POST request with a JSON body. That means your endpoint is public. Anyone who finds the URL can POST a fake update to it, and if your code trusts whatever arrives, you’ll process forged messages, forged callback queries, or forged payment notifications as if they came from Telegram.

This isn’t a hypothetical. Webhook URLs leak through logs, error trackers, browser history on a shared machine, or simple guessing if the path is predictable. We run webhook receivers for hosted bots as part of our infrastructure, and the first thing we check on any new setup is whether the endpoint actually verifies the sender before doing anything with the payload.

The fix Telegram provides is a secret token. It’s a small piece of configuration, it costs nothing to set up, and skipping it is one of the more common gaps we see when reviewing a bot’s webhook handler.

How webhook delivery actually works

When you call setWebhook, Telegram registers your URL as the destination for updates. From that point on, Telegram’s servers make outbound HTTPS requests to your endpoint whenever there’s a new update: a message, an edited message, a callback query, an inline query, and so on. Your server has no way to “pull” from Telegram in this mode; you’re purely a receiver.

Two things follow from this. First, your endpoint must serve HTTPS with a certificate that a normal TLS client will trust (or you upload a self-signed certificate directly to Telegram via the certificate parameter on setWebhook, in which case Telegram pins that specific cert). Second, because the connection is inbound to you, verifying who’s on the other end is entirely your responsibility. TLS proves the traffic wasn’t tampered with in transit and that it’s headed to your domain. It says nothing about whether the request body actually originated from Telegram’s infrastructure rather than a script somebody wrote to hit your URL directly.

The secret token

setWebhook accepts an optional secret_token parameter. You choose the value yourself: 1 to 256 characters, restricted to A-Z, a-z, 0-9, underscore, and hyphen. Once set, Telegram includes that exact value on every webhook request it sends you, in a header called X-Telegram-Bot-Api-Secret-Token.

Your job on the receiving end is simple in concept: read that header, compare it against the value you configured, and reject the request if it’s missing or doesn’t match. Anyone who doesn’t know your secret token can’t produce a request that passes this check, because the header isn’t something they can guess or derive from the bot token, the chat ID, or anything else public.

A few practical points worth getting right:

Treat the secret token like a credential. Generate it with a proper random source rather than typing something memorable. Store it in the same place you store your bot token, not in a config file that ends up in a public repo.

Set it before you start receiving real traffic, ideally at the same time you first call setWebhook. There’s a window between registering a webhook URL and adding the secret check where the endpoint is live but unprotected, so don’t leave that gap open longer than you have to.

Compare the header using a constant-time comparison function rather than a plain string equality check. Most languages have one (hmac.compare_digest in Python, crypto.timingSafeEqual in Node). This matters less for a value this length than it would for something shorter, but it costs nothing to do it properly and it’s the kind of habit that keeps you out of trouble on other checks later.

Reject early. Check the header before you parse the JSON body, before you touch your database, before you do anything else. A request that fails the secret check should get a 401 or 403 and nothing more.

What this does and doesn’t protect against

The secret token confirms that whoever sent the request knew the value you configured with Telegram. It’s a shared-secret check, functionally similar to a webhook signing key used by other platforms, except here Telegram sends the raw value rather than an HMAC signature over the payload. That’s a meaningful difference: a signature would let you verify the body wasn’t altered after Telegram sent it. The secret token only verifies the sender had the right header at the time of the request. In practice, this is fine, because the request arrives over HTTPS and the body isn’t separately exposed to tampering between Telegram and your server. The header check and TLS together cover the realistic threat, which is unauthorized parties hitting your endpoint directly, not a man-in-the-middle altering a request that’s already encrypted in transit.

What it doesn’t do is replace validation of the content itself. A malicious actor who somehow obtained your secret token (through a log leak, a misconfigured proxy that echoes headers, or a compromised server) could still send arbitrary payloads that pass the check. So the secret token narrows who can reach your handler, but you still want normal input validation on the update itself: check that chat IDs and user IDs match what you expect for the context, don’t trust unbounded string fields blindly, and apply the same input hygiene you’d use for any other external input.

Why IP allowlisting isn’t a real substitute

Some people try to secure webhook endpoints by allowlisting the IP ranges Telegram sends from, on the theory that if you only accept connections from Telegram’s servers, you don’t need anything else. The problem is that Telegram doesn’t publish a fixed, guaranteed set of source IPs for webhook delivery, and infrastructure on their end can change without notice. An allowlist built by observing traffic for a while will eventually go stale, and when it does, you either start silently dropping legitimate updates or you widen the range so much it stops being a meaningful control.

We’ve seen bots go quiet for stretches because someone built an IP filter years ago that Telegram’s infrastructure has since outgrown. The secret token doesn’t have this failure mode: it’s a value you control, it doesn’t expire on its own, and it doesn’t depend on Telegram’s network topology staying static. If you want defense in depth, layering a loose network-level restriction on top of the secret token check is reasonable, but the secret token should be the actual gate, not the IP filter.

Rotation and failure handling

If you ever suspect the secret token has leaked, generate a new one and call setWebhook again with the updated value. There’s no separate “rotate” endpoint; setting a new secret token is just setting the webhook again with a different value. Do this the same way you’d rotate any other credential you suspect was exposed, and check your logs afterward for a burst of 401s, which would tell you someone was still hitting the endpoint with the old value.

On the failure path, log rejected requests with enough detail to spot a pattern (timestamp, source IP, whether the header was present at all) without logging the header value itself. If you’re seeing a steady trickle of failed requests, that’s usually just scanners or old bookmarks hitting a URL. A sudden spike right after you rotate the token is expected and not a concern. A spike with no rotation event behind it is worth investigating.

A short checklist

Set the secret token in the same setWebhook call where you register your URL, not as an afterthought. Store it as a credential, not a config comment. Check the X-Telegram-Bot-Api-Secret-Token header before touching the request body. Use a constant-time comparison. Don’t rely on IP allowlisting as your primary control. Still validate the update’s content on top of the header check, since the secret token tells you who’s knocking, not whether what they’re carrying makes sense for your bot.

None of this is complicated, but it’s the kind of setup step that’s easy to skip when you’re focused on getting the bot working and easy to forget once it’s running. If you’re setting up or auditing a webhook-based bot and want a second pair of eyes on how the receiving end is configured, that’s the kind of hosting and infrastructure work we do at telegramvault.org.

Get new guides and videos first — join the Telegram channel.

need infra for this today?