Setting Up a Telegram Webhook Over HTTPS
Register a secure Telegram webhook — HTTPS requirements, secret tokens, allowed updates, fast responses, health checks and testing locally with a tunnel.
On this page
Webhooks let Telegram push updates to your bot instead of your bot polling for them. They’re a great fit for high-volume bots and bots that already run a web server. They also have more moving parts than polling: a public HTTPS URL, a certificate, and a server that answers quickly. This guide sets one up properly.
Requirements
Telegram will only deliver webhooks to URLs that meet these rules:
- HTTPS with a valid TLS certificate. A self-signed certificate is allowed only if you upload it with
setWebhook. - Ports 443, 80, 88 or 8443. Anything else is rejected.
- A reachable public address. Telegram’s servers must be able to connect to it.
On Kerit Cloud, every Telegram server includes an HTTPS endpoint you can register directly, so you don’t need your own domain or certificate. On a VPS, you’d typically put Nginx in front of your bot with a free Let’s Encrypt certificate — see Nginx reverse proxy with free SSL.
Step 1: Receive updates in your app
Your web server needs a route that accepts POST requests with a JSON body — one update per request. With aiogram 3 and FastAPI:
import os
from fastapi import FastAPI, Header, HTTPException, Request
from aiogram import Bot, Dispatcher
from aiogram.types import Update
bot = Bot(os.environ["BOT_TOKEN"])
dp = Dispatcher()
SECRET = os.environ["WEBHOOK_SECRET"]
app = FastAPI()
@app.post("/telegram")
async def telegram_webhook(
request: Request,
x_telegram_bot_api_secret_token: str | None = Header(default=None),
):
if x_telegram_bot_api_secret_token != SECRET:
raise HTTPException(status_code=403)
update = Update.model_validate(await request.json(), context={"bot": bot})
await dp.feed_update(bot, update)
return {"ok": True}
Run it with an ASGI server bound to the port your host provides, such as uvicorn app:app --host 0.0.0.0 --port 8080. grammY and Telegraf have equivalent one-line adapters — see building a grammY bot and hosting a Telegraf bot.
Step 2: Protect the endpoint with a secret token
Anyone who learns your webhook URL could send fake updates — pretending to be an admin, triggering commands or flooding your bot. Telegram solves this with a secret token: you choose a random string when registering, and Telegram sends it in the X-Telegram-Bot-Api-Secret-Token header of every request. Reject requests where it doesn’t match, as the handler above does.
Generate a strong one:
python3 -c "import secrets; print(secrets.token_urlsafe(32))"
Telegram allows 1–256 characters from A-Z, a-z, 0-9, _ and -. Store it as an environment variable, not in code.
As extra defence, you can restrict the endpoint to Telegram’s published source ranges (149.154.160.0/20 and 91.108.4.0/22) at your firewall or proxy — but the secret token alone is the check that matters.
Step 3: Register the webhook
Call setWebhook once. From a terminal:
curl -s "https://api.telegram.org/bot${BOT_TOKEN}/setWebhook" \
-d "url=https://your-endpoint.example/telegram" \
-d "secret_token=${WEBHOOK_SECRET}" \
-d 'allowed_updates=["message","callback_query"]' \
-d "drop_pending_updates=true"
The useful parameters:
| Parameter | What it does |
|---|---|
url |
Where Telegram sends updates |
secret_token |
Sent back in the header of every request |
allowed_updates |
Only the update types you handle — less traffic, less noise |
max_connections |
Parallel connections Telegram may open (1–100, default 40) |
drop_pending_updates |
Discard updates queued before registration |
Most libraries can also register on startup — aiogram’s bot.set_webhook() in a startup hook, grammY’s bot.api.setWebhook(). That’s convenient, but avoid re-registering on every restart if your bot restarts often; it’s a one-time setting.
Step 4: Respond quickly
Telegram waits for your server’s response before sending the next update over that connection, and treats slow or failed responses as delivery failures that it will retry. That leads to two rules:
- Return 200 fast. If a handler needs to do something slow — call an external API, process a file — acknowledge the update and do the work in a background task.
- Make handlers safe to repeat. A retried delivery means the same update may arrive twice. Use the
update_idor the message ID to skip duplicates when it matters, such as for payments.
A handy shortcut: for a simple reply, you can answer the webhook request itself with a Bot API method call in the response body, saving a round trip. Most frameworks handle this for you when configured.
Step 5: Verify it’s working
getWebhookInfo is your diagnostic tool:
curl -s "https://api.telegram.org/bot${BOT_TOKEN}/getWebhookInfo"
Look at:
url— should be your endpoint. Empty means no webhook is set.pending_update_count— updates waiting to be delivered. It should stay near zero.last_error_message— Telegram’s view of what went wrong: timeouts, SSL errors, wrong status codes.
Send your bot a message and watch your server’s logs. If nothing arrives, the last error message almost always explains why.
Testing locally with a tunnel
Your laptop has no public HTTPS address, so Telegram can’t reach it. A tunnel gives it one temporarily:
cloudflared tunnel --url http://localhost:8080
# or: ngrok http 8080
Register the tunnel’s HTTPS URL as your webhook while developing. Remember to point the webhook back at production afterwards — Telegram only delivers to one URL per bot. Many developers find it simpler to poll locally and use webhooks only in production; the same handlers work in both modes.
Common problems
SSL error in last_error_message. The certificate is invalid, expired, or doesn’t match the hostname. Use a certificate from a trusted authority, or upload your self-signed one.
Wrong response from the webhook: 404 Not Found. The path in your registered URL doesn’t match your route.
Wrong response from the webhook: 403 Forbidden. Your secret check is rejecting Telegram — the registered secret_token and your environment variable don’t match.
Connection timed out. The server isn’t reachable on that port, or your handler is too slow.
Polling suddenly returns 409 Conflict. A webhook is active. Call deleteWebhook if you want to go back to polling.
Summary
A secure Telegram webhook needs an HTTPS URL on an allowed port, a route that accepts JSON updates, and a secret token checked on every request. Register it once with setWebhook, limiting allowed_updates to what you handle, respond with 200 quickly and push slow work to the background, and use getWebhookInfo whenever something looks wrong. On Kerit Cloud’s Telegram hosting, each server comes with an HTTPS endpoint ready to register.