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

  1. Create a free bin in your browser at view.webhooks.page or from your terminal with curl -X POST https://webhooks.sh/v1/bins, which returns JSON containing your ingest_url and view_url.
  2. Call setWebhook at https://api.telegram.org/bot<token>/setWebhook with your bin's ingest_url (any path under it works too, so https://<bin>.webhooks.page/telegram is fine).
  3. Send your bot a message in Telegram.
  4. Watch the request show up live at your view_url with 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.

ParameterWhat it doesLimits and default
urlHTTPS URL Telegram posts updates toEmpty string removes the webhook
secret_tokenSent back as X-Telegram-Bot-Api-Secret-Token on every request1 to 256 characters: A-Z, a-z, 0-9, _ and -
max_connectionsSimultaneous HTTPS connections for delivery1 to 100, default 40
allowed_updatesUpdate types the bot receivesDefault: all except chat_member, message_reaction, message_reaction_count
drop_pending_updatesDrops updates waiting in the queuetrue or false
ip_addressFixed IP to deliver to instead of DNSOptional
certificatePublic key for a self-signed certificateOptional

Requirements Telegram checks

  • The URL must use HTTPS with TLS 1.2 or newer.
  • Only ports 443, 80, 88 or 8443 are 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 certificate parameter.
  • Requests come from 149.154.160.0/20 and 91.108.4.0/22 if 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.