Telegram bot webhooks: setWebhook, testing and the secret token
To test a Telegram bot webhook, call setWebhook with a free webhooks.sh bin URL (no signup, 48 hours, 100 requests a day) and every update Telegram sends shows up live as JSON. Telegram only posts over HTTPS to ports 443, 80, 88 or 8443.
A Telegram webhook needs a public HTTPS URL before Telegram will send it anything. The fastest way to see real updates is to run one setWebhook call pointing at a free bin and then send a message to your bot. Here's how to look at the payload, set the parameters, meet Telegram's network requirements, debug delivery errors and verify the secret token.
Point your bot at a free test URL
- Create a free bin in your browser at
view.webhooks.pageor from your terminal withcurl -X POST https://webhooks.sh/v1/bins, which returns JSON containing youringest_urlandview_url. - Call
setWebhookathttps://api.telegram.org/bot<token>/setWebhookwith your bin'singest_url(any path under it works too, sohttps://<bin>.webhooks.page/telegramis fine). - Send your bot a message in Telegram.
- Watch the request show up live at your
view_urlwith its method, path, headers and JSON body.
The bin replies with HTTP 200, which Telegram counts as delivered.
# 1. Create a free bin (or open view.webhooks.page)
curl -X POST https://webhooks.sh/v1/bins
# 2. Point the bot at the ingest_url from step 1
curl "https://api.telegram.org/bot<BOT_TOKEN>/setWebhook" \
-d "url=https://<bin>.webhooks.page/telegram" \
-d "secret_token=test_secret_123"
What a Telegram update looks like
Telegram sends an HTTPS POST carrying one JSON-serialized Update object per update. Every update has an update_id that increases sequentially, which you use to ignore repeated updates or restore their order if they arrive out of sequence. Along with update_id, each update includes at most one optional field such as message, edited_message, channel_post or callback_query, and if you don't set allowed_updates yourself, Telegram's default sends every update type except chat_member, message_reaction and message_reaction_count.
{
"update_id": 815204371,
"message": {
"message_id": 42,
"from": { "id": 123456789, "is_bot": false, "first_name": "Ada", "username": "ada_dev" },
"chat": { "id": 123456789, "first_name": "Ada", "username": "ada_dev", "type": "private" },
"date": 1793000000,
"text": "hello bot"
}
}
setWebhook parameters that matter
Most bots only need url and secret_token in setWebhook, while the rest tune how updates get delivered.
One parameter worth knowing when you're moving a bot around: passing drop_pending_updates as true clears out old queued updates when you switch URLs, so they don't flood your new endpoint the moment it goes live.
| Parameter | What it does | Limits and default |
|---|---|---|
url | HTTPS URL Telegram posts updates to | Empty string removes the webhook |
secret_token | Sent back as X-Telegram-Bot-Api-Secret-Token on every request | 1 to 256 characters: A-Z, a-z, 0-9, _ and - |
max_connections | Simultaneous HTTPS connections for delivery | 1 to 100, default 40 |
allowed_updates | Update types the bot receives | Default: all except chat_member, message_reaction, message_reaction_count |
drop_pending_updates | Drops updates waiting in the queue | true or false |
ip_address | Fixed IP to deliver to instead of DNS | Optional |
certificate | Public key for a self-signed certificate | Optional |
Requirements Telegram checks
- The URL must use HTTPS with TLS 1.2 or newer.
- Only ports
443,80,88or8443are accepted. - The host needs an IPv4 address, since IPv6 isn't supported for webhooks.
- You need a valid certificate chain, or you have to upload your self-signed public key certificate using the
certificateparameter. - Requests come from
149.154.160.0/20and91.108.4.0/22if you filter by IP, though Telegram says that range might change.
A webhooks.sh ingest_url is served over HTTPS on port 443, so it meets these out of the box.
Debug a webhook with getWebhookInfo
When your telegram webhook is not working and updates stop arriving, call getWebhookInfo first. Check pending_update_count to see how many updates are backed up in the queue, and look at last_error_date (in Unix time) and last_error_message for the most recent delivery failure.
If your server returns a non-2XX status, Telegram repeats the request and eventually gives up after a reasonable number of attempts, holding onto undelivered updates for at most 24 hours. And remember that getUpdates won't work at all while a webhook is active, so you have to call deleteWebhook if you want to switch back to polling. If getWebhookInfo doesn't show an error or you think requests are failing before they hit your code, walk through our webhook not received guide.
# Is the webhook set, and what failed last?
curl "https://api.telegram.org/bot<BOT_TOKEN>/getWebhookInfo"
# Remove it and drop the queue, then poll with getUpdates again
curl "https://api.telegram.org/bot<BOT_TOKEN>/deleteWebhook" -d "drop_pending_updates=true"
Check the secret token in your handler
Pass secret_token (1 to 256 characters using A-Z, a-z, 0-9, _ and -) when you call setWebhook, and Telegram will attach an X-Telegram-Bot-Api-Secret-Token header with that value to every request so your handler can compare it in constant time and reject anything that doesn't match. One thing to know when you inspect requests on webhooks.sh: the viewer and API redact any header whose name contains secret or token (along with authorization, cookie and key), so in the bin you'll see that X-Telegram-Bot-Api-Secret-Token arrived, but its value will show as redacted.
import { timingSafeEqual } from "node:crypto";
export function isFromTelegram(headers, expected) {
const got = Buffer.from(headers["x-telegram-bot-api-secret-token"] ?? "");
const want = Buffer.from(expected);
return got.length === want.length && timingSafeEqual(got, want);
}
Keep the URL or move to localhost
A free bin lasts 48 hours and takes 100 requests a day (with bodies up to 100KB), which is plenty for building and testing a bot, just not for running a busy one. If you want to keep that same webhooks.page URL past 48 hours, claim the bin from the bin page with a free Google sign-in before it expires; the timer stops and new requests are kept for 30 days on a rolling basis at the same 100-request daily cap.
When you're ready to test webhooks locally against your own code or handle real traffic, Solo costs $12 a month (see pricing) and gives you a named endpoint on webhooks.host with 250,000 requests a month, 5MB bodies and 90 days of history, plus the wsh CLI so you can run wsh listen <endpoint> --to localhost:3000 to relay live updates straight to your bot on localhost.
FAQ
Can I test a Telegram webhook without a server?
Yes. Point setWebhook at a free bin URL and every update Telegram sends shows up live in your browser, with no signup and no install.
Why does getUpdates stop working after setWebhook?
Telegram's two ways of receiving updates, getUpdates and setWebhook, are mutually exclusive. Call deleteWebhook to remove the webhook and go back to polling.
Which ports can a Telegram webhook use?
A Telegram webhook can only use ports 443, 80, 88 or 8443, over HTTPS with TLS 1.2 or newer.
Does Telegram retry failed webhook requests?
Yes. If your endpoint returns anything other than a 2XX status, Telegram repeats the request and then gives up after a reasonable number of attempts, storing undelivered updates for at most 24 hours.
How do I know a request really came from Telegram?
Set the secret_token parameter when calling setWebhook and check that the X-Telegram-Bot-Api-Secret-Token header on incoming requests matches it in your handler.
How long does a free test URL last?
A free bin lasts 48 hours from creation, or longer if you claim it before expiry with a free Google sign-in, which stops the timer and keeps new requests for 30 days.
Last updated
Try it in 10 seconds.
Create a free bin at view.webhooks.page — no signup, no install. Point your sender at the URL and watch requests land live. Bins expire after 48 hours.