Telegram Webhook: Complete Setup Guide for All Languages (2026)
Telegram Webhook: Complete Setup Guide for All Languages (2026)
A Telegram webhook is a URL on your server that Telegram calls every time your bot receives a new message or update. Instead of your bot repeatedly asking Telegram "do you have anything new for me?" (polling), Telegram proactively delivers updates to your server the instant they arrive. This makes webhooks the preferred approach for production Telegram bots: lower latency, lower server load, and better scalability.
This guide explains webhooks thoroughly and provides working setup examples in Python, Node.js, and PHP. Browse related resources in the Developer Tools category.
What Is a Telegram Webhook?
When you configure a webhook, you tell Telegram: "Send all updates for my bot to this URL." Telegram then makes an HTTPS POST request to that URL whenever an update occurs — a new message, a callback query from an inline button, a new chat member, and so on.
The payload is a JSON object containing an Update object as defined in the Telegram Bot API documentation. Your server processes it and returns any HTTP 200 response to acknowledge receipt.
Requirements for a webhook URL:
- Must be HTTPS (TLS 1.2 or 1.3)
- Must use a valid TLS certificate (self-signed certs require special handling)
- Must be publicly reachable from the internet
- Must respond within 60 seconds (Telegram will retry on timeout)
- Port must be one of: 443, 80, 88, or 8443
Webhook vs Long Polling: Which Should You Use?
| Factor | Webhook | Long Polling |
|---|---|---|
| Latency | Near-instant (<100ms) | Up to poll interval (1-2s typical) |
| Server requirements | Public HTTPS URL required | No public URL needed |
| Setup complexity | Moderate (HTTPS, domain) | Very simple |
| Server load | Low (Telegram pushes) | Higher (constant requests) |
| Best for | Production | Development and testing |
| Serverless compatible | Yes | No |
Rule of thumb: Use long polling during development (no HTTPS setup needed), switch to webhooks before going to production.
Setting Up a Telegram Webhook in Python
Using python-telegram-bot v21+ with FastAPI:
pip install python-telegram-bot[webhooks] fastapi uvicorn
# main.py
import os
from fastapi import FastAPI, Request
from telegram import Update, Bot
from telegram.ext import Application, CommandHandler, MessageHandler, filters
TOKEN = os.environ["BOT_TOKEN"]
WEBHOOK_URL = os.environ["WEBHOOK_URL"] # e.g. https://yourdomain.com
# Build the application
application = (
Application.builder()
.token(TOKEN)
.build()
)
# Register handlers
async def start(update: Update, context):
await update.message.reply_text(f"Hello, {update.effective_user.first_name}!")
async def echo(update: Update, context):
await update.message.reply_text(update.message.text)
application.add_handler(CommandHandler("start", start))
application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, echo))
# FastAPI app
app = FastAPI()
@app.on_event("startup")
async def startup():
await application.initialize()
await application.bot.set_webhook(
url=f"{WEBHOOK_URL}/webhook/{TOKEN}",
secret_token=os.environ.get("WEBHOOK_SECRET", "")
)
await application.start()
@app.on_event("shutdown")
async def shutdown():
await application.stop()
await application.shutdown()
@app.post(f"/webhook/{TOKEN}")
async def handle_webhook(request: Request):
secret = request.headers.get("X-Telegram-Bot-Api-Secret-Token", "")
if secret != os.environ.get("WEBHOOK_SECRET", ""):
return {"error": "Unauthorized"}, 403
data = await request.json()
update = Update.de_json(data, application.bot)
await application.process_update(update)
return {"ok": True}
Run with: uvicorn main:app --host 0.0.0.0 --port 443
For a simpler setup with Flask:
from flask import Flask, request
from telegram import Update, Bot
from telegram.ext import Application, CommandHandler
import asyncio, os
TOKEN = os.environ["BOT_TOKEN"]
app_flask = Flask(__name__)
application = Application.builder().token(TOKEN).build()
@app_flask.route(f"/{TOKEN}", methods=["POST"])
def webhook():
data = request.get_json()
update = Update.de_json(data, application.bot)
asyncio.run(application.process_update(update))
return "ok", 200
if __name__ == "__main__":
application.bot.set_webhook(f"https://yourdomain.com/{TOKEN}")
app_flask.run(host="0.0.0.0", port=8443)
Setting Up a Telegram Webhook in Node.js
Using grammy with Express:
npm install grammy express
// server.js
const { Bot, webhookCallback } = require("grammy");
const express = require("express");
const TOKEN = process.env.BOT_TOKEN;
const WEBHOOK_URL = process.env.WEBHOOK_URL;
const SECRET_TOKEN = process.env.WEBHOOK_SECRET || "mysecret";
const bot = new Bot(TOKEN);
// Handlers
bot.command("start", (ctx) => ctx.reply(`Hello, ${ctx.from.first_name}!`));
bot.on("message:text", (ctx) => ctx.reply(`Echo: ${ctx.message.text}`));
// Express setup
const app = express();
app.use(express.json());
// The webhook path uses the secret token for security
app.post(`/webhook/${SECRET_TOKEN}`, webhookCallback(bot, "express"));
app.listen(443, async () => {
await bot.api.setWebhook(`${WEBHOOK_URL}/webhook/${SECRET_TOKEN}`, {
secret_token: SECRET_TOKEN
});
console.log(`Webhook active at ${WEBHOOK_URL}/webhook/${SECRET_TOKEN}`);
});
Using ngrok for local development:
# Terminal 1: start ngrok
ngrok http 443
# Terminal 2: set the public URL and start
WEBHOOK_URL="https://abc123.ngrok-free.app" BOT_TOKEN="..." node server.js
Setting Up a Telegram Webhook in PHP
<?php
// webhook.php — place at your public HTTPS URL
$token = getenv('BOT_TOKEN');
$secret = getenv('WEBHOOK_SECRET');
// Verify the secret token header
$incoming_secret = $_SERVER['HTTP_X_TELEGRAM_BOT_API_SECRET_TOKEN'] ?? '';
if ($secret && $incoming_secret !== $secret) {
http_response_code(403);
exit('Forbidden');
}
$update = json_decode(file_get_contents('php://input'), true);
if (!$update) {
http_response_code(400);
exit('Bad Request');
}
// Helper: send a message
function sendMessage(string $token, int $chat_id, string $text): void {
$url = "https://api.telegram.org/bot{$token}/sendMessage";
$payload = json_encode(['chat_id' => $chat_id, 'text' => $text]);
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $payload,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_RETURNTRANSFER => true,
]);
curl_exec($ch);
curl_close($ch);
}
// Handle the update
$message = $update['message'] ?? null;
if ($message) {
$chat_id = $message['chat']['id'];
$text = $message['text'] ?? '';
if (str_starts_with($text, '/start')) {
$name = $message['from']['first_name'] ?? 'there';
sendMessage($token, $chat_id, "Hello, {$name}!");
} else {
sendMessage($token, $chat_id, "You said: {$text}");
}
}
http_response_code(200);
echo 'OK';
Register the webhook with a one-time curl call:
curl "https://api.telegram.org/bot{TOKEN}/setWebhook" \
-d "url=https://yourdomain.com/webhook.php" \
-d "secret_token=YOUR_SECRET"
Common Webhook Issues and How to Fix Them
Issue: Webhook set but no updates arrive
- Check your URL is reachable:
curl https://yourdomain.com/webhook.php - Verify the webhook is set:
GET https://api.telegram.org/bot{TOKEN}/getWebhookInfo - The response shows
last_error_message— read it carefully - Common errors: "Wrong response from the webhook" (your handler returned non-200), "Connection refused" (server not listening on the right port)
Issue: Duplicate message handling
If your webhook returns a non-200 response, Telegram retries for up to 1 hour with exponential backoff. This can cause duplicate processing. Always return 200 before doing any heavy work (use a queue for processing).
Issue: TLS certificate errors
Telegram requires a valid TLS certificate. Use Let's Encrypt (free) via Certbot for self-managed servers, or use platforms like Render/Railway that handle TLS automatically.
Issue: Switching back from webhook to polling
# Delete the webhook to use polling again
curl "https://api.telegram.org/bot{TOKEN}/deleteWebhook"
FAQ
Can I use a self-signed certificate with Telegram webhooks?
Yes, but it requires uploading your public certificate to Telegram when setting the webhook. This is complex — use Let's Encrypt instead, which is free and trusted by Telegram without extra steps.
What happens if my server is down when Telegram sends a webhook?
Telegram retries failed webhooks for up to 1 hour with increasing delays between attempts. After the retry window expires, the update is dropped. For critical bots, ensure high uptime and consider using Telegram's getUpdates (polling) as a fallback for catching missed updates after a server restart.
Can I set multiple webhooks for the same bot?
No. Each bot can only have one active webhook URL at a time. Setting a new webhook automatically replaces the old one.
How do I verify that requests are genuinely from Telegram?
Use the secret_token parameter when setting your webhook. Telegram will include this token in the X-Telegram-Bot-Api-Secret-Token header of every request. Validate this header in your handler and reject requests that don't include it.
What's the difference between a webhook and a Telegram Mini App?
A webhook receives updates from the Bot API (messages, button presses, etc.) and is server-side. A Mini App is a web application that opens in Telegram's in-app browser and communicates with your bot via the Telegram WebApp JavaScript API. Mini Apps use webhooks (or polling) as their backend communication mechanism.
Share this article