Telegram bots can receive updates two ways: long-polling with getUpdates, or a
webhook where Telegram pushes each update to a URL you set. Webhooks are the right
choice for anything running as a service — no polling loop, updates arrive as they
happen. But Telegram’s webhook model differs from Stripe’s or GitHub’s in one
important way: it does not sign updates. Authenticity is your responsibility,
established when you register the webhook. This guide covers setting up Telegram
webhooks, securing them without a signature, and diagnosing the ones that fail.
What Telegram webhooks are
When you register a webhook with the Bot API method setWebhook, Telegram sends each
new Update as an HTTP POST to your URL. An Update is a JSON object with a
monotonically increasing update_id and exactly one content field naming what
happened — message, callback_query, my_chat_member, and so on. Your bot reacts
by calling Bot API methods (sendMessage, answerCallbackQuery) in response.
Common use cases
- Command and message handling — respond to
messageupdates for chat commands and conversations. - Inline keyboards — handle
callback_querywhen a user taps a button. - Membership changes — react to
my_chat_memberwhen the bot is added to, or removed from, a chat, or its permissions change. - Moderation and workflows — process edited messages, channel posts, or chat-join requests depending on what your bot does.
Configuring the webhook in Telegram
There is no dashboard — you call the Bot API. setWebhook takes the public HTTPS url, an optional secret_token (strongly recommended, covered below), an optional allowed_updates list to receive only the update types you handle, and a max_connections hint. Telegram requires HTTPS and accepts only a small set of ports
(commonly 443, 80, 88, and 8443). getWebhookInfo reports the current registration
and its health; deleteWebhook removes it.
The Bot API is stable but its details do change, so confirm current parameters
against Telegram’s setWebhook documentation.
Only one delivery method can be active at a time: setting a webhook disables getUpdates, and vice versa.
Recommended endpoint setup
Telegram delivers updates in order and waits for your response, so a slow handler holds up the queue. Acknowledge fast and process asynchronously:
- Check the
X-Telegram-Bot-Api-Secret-Tokenheader (see below) and reject mismatches. - Enqueue the update keyed by
update_id, and return2xxpromptly. - Do the real work — API calls, database writes — off the request path.
- Use
allowed_updatesat registration so you never receive types you ignore.
Because Telegram sends updates sequentially and waits, doing heavy work synchronously
slows every subsequent update. HookWatch endpoints can filter by event type as an
extra edge control, but allowed_updates is your first line — set it narrowly.
Verifying authenticity
Telegram does not sign updates, so you cannot verify an HMAC. Authenticity rests on two things:
- A secret token. When you pass
secret_tokentosetWebhook, Telegram sends it back on every request in theX-Telegram-Bot-Api-Secret-Tokenheader. Compare it, in constant time, against the value you set and reject anything that does not match. - A secret URL. Because there is no signature, treat the webhook URL itself as a credential — a long, unguessable path — so an attacker cannot even find the endpoint to probe it.
POST /tg/8f3c1a9e2b7d4f60 HTTP/1.1
Host: bot.example.com
Content-Type: application/json
X-Telegram-Bot-Api-Secret-Token: 9d4f2c7a1e8b6033f5a...
{"update_id":874216531,"message":{"message_id":42,"text":"/start"}} Reject any request whose secret-token header is missing or wrong before you parse the body. With no signature to verify, this check plus a secret URL and HTTPS is the whole of your authenticity story. HookWatch’s inbound signature schemes are HMAC-based and so do not apply to Telegram’s unsigned updates; the secret-token header is the mechanism to rely on here.
Common failure scenarios
- Not HTTPS, or a rejected certificate — Telegram requires a valid public HTTPS endpoint; a self-signed or incomplete chain (without the documented handling) fails to deliver.
- Growing
pending_update_count—getWebhookInfoshows updates queuing because your endpoint is failing or too slow; Telegram holds them and keeps retrying. - A revealing
last_error_message—getWebhookInforeports the last delivery error (a connection refusal, a TLS problem, a non-2xx). Read it first; it usually names the cause. - Silent method conflict — calling
getUpdateswhile a webhook is set (or the reverse) means updates seem to vanish. - Slow handler stalling the queue — synchronous work delays every following update because delivery is ordered.
Testing workflow
getWebhookInfo is the primary diagnostic: it returns the current url, pending_update_count, last_error_date, and last_error_message, which together
tell you whether Telegram can reach you and what went wrong last. There is no
“resend” button — Telegram keeps retrying pending updates on its own — so fixing the
endpoint and watching pending_update_count drain is the loop.
For local development, expose your handler with a tunnel and point setWebhook at the
public HTTPS URL so real Telegram updates reach your machine. See testing webhooks locally; hookwatch tunnel forwards captured deliveries to a local process so Telegram can reach your laptop
through a HookWatch endpoint over public TLS. Remember to deleteWebhook (or reset it)
when you switch back to your deployed bot.
Retry behaviour
Telegram queues undelivered updates and keeps retrying, holding them until your endpoint accepts them or they age out, rather than dropping them immediately on the first failure. The exact retention and retry timing can change, so rely on the behaviour — a full queue that drains once you recover — and confirm current details in the Bot API docs. For your handler:
- Return
2xxquickly; a non-2xx or a timeout leaves the update queued and blocks the ones behind it, since delivery is ordered. - A backed-up
pending_update_countis the signal that your endpoint, not Telegram, is the problem. - Once you recover, expect a burst as the queue drains — your handler must tolerate that rate.
The general principles are in retry design for webhook consumers.
Idempotency
Dedupe on update_id, which is unique per update and increases monotonically.
Because Telegram will resend a queued update until you acknowledge it, the same update_id can arrive more than once — record processed ids and make the effect
idempotent so you do not answer one /start twice or double-post a reply. A duplicate
you have already handled should still return 2xx. Full patterns are in processing duplicates exactly once.
Debugging checklist
- Call
getWebhookInfoand readurl,pending_update_count, andlast_error_message. - Confirm the endpoint is public HTTPS on an accepted port with a valid certificate chain.
- Verify the
X-Telegram-Bot-Api-Secret-Tokenheader is present and matches what you set. - Confirm no
getUpdatescall is competing with the webhook. - Check the handler returns
2xxquickly so the ordered queue does not stall. - Confirm
allowed_updatesincludes the types you expect and excludes the ones you do not. - Match application logs to the update by
update_id. - Confirm duplicate updates are deduped and still answered
2xx. - Watch
pending_update_countdrain after a fix to confirm recovery.
See also Telegram webhooks and a Telegram webhook not receiving updates for Telegram-specific detail, and the general debugging walkthrough.
How HookWatch helps
Because Telegram gives you only getWebhookInfo and a last_error_message to work
with, a record of the actual requests is worth a lot. Point a HookWatch endpoint at
your bot and every update is captured whole — the X-Telegram-Bot-Api-Secret-Token header, the update_id, the body, your response, and each attempt — so a failed callback_query still shows exactly what Telegram sent and what your handler
returned. Replay a captured update against a fixed handler instead of waiting for the
queue, pause an endpoint while you deploy and resume it, and filter to the update
types you handle. Repeated failures group into an incident, so a stalled bot is one
alert with the evidence attached rather than a silently growing queue.