How to set up a webhook for a Telegram bot
In this article, you will learn different ways to set up a webhook for a Telegram bot in the context of the aiogram framework. However, the information here will be useful for more than just that. You can use it as a cheat sheet
Article Contents
- What is a webhook
- Where to start
- Getting a domain
- Installing a web server
- Certificate from Lets Encrypt
- Self-signed certificate
- Connecting to the webhook
What is a webhook
🪝 Webhook – is a mechanism for processing updates by receiving POST requests to a web server and passing them to the bot
With polling, we get updates from the api.telegram.org endpoint, but with a webhook, we provide Telegram with our personal link where it will send updates, and we will catch them
When it's useful
- In a situation where the bot has a very high load, meaning many updates per unit of time. It is known that polling (the getUpdates method) allows you to get a maximum of 100 updates per call
- If we don't want to see failed attempts to get updates in the logs. With polling, every bot constantly makes requests to api.telegram.org, even if there are no updates or if Telegram has server issues. It's important to remember that each getUpdates call has an execution time and a certain load
- This mechanism allows you to distribute updates to your desired entry points and implement horizontal scaling of the project
- If you have many bots on one server or want to create a multibot (a bot that manages many bots). With webhooks, this will be easier and of higher quality
Prerequisites
- Server — Telegram will send updates to a specified URL, which you need to have access to. Therefore, you need to get your own server, for example, a VPS. You can read about organizing this online
- Port — The officially supported ports are
443,80,88, and8443. Other ports are not supported. Don't forget to configure your firewall so the server accepts requests - HTTPS — Telegram will not send updates over an insecure connection (HTTP). The server must have an SSL/TLS certificate (the latter being the successor to the former) to support requests from Telegram. A self-signed certificate is also allowed
- SSL/TLS Version — Currently, TLS 1.2 and higher are supported. Older versions contain vulnerabilities and will not work
- IPv4 — IPv6 is not currently supported
Additional security If there is a server that accepts requests, it can be exploited. For example, someone might send their own POST request, the bot will think it's an update, and something bad will happen. To prevent this, some check the sender's IP address (for Telegram, they are static —
149.154.160.0/20and91.108.4.0/22), while others add their own keys/secrets to Telegram's requests (this is done when setting up the webhook), which are then verified on the server, but the choice is yours
Where to start
- Certificate type — official (approved by a Certificate authority) or self-signed. Currently, official certificates are usually paid, while self-signed ones are always free (because you make them yourself). But, for example, there is a service Let's Encrypt that issues official certificates for free if you already have a domain
- Bot server — for a webhook, the bot itself must run as a server and listen on a specific port. You can set up the server using aiohttp, FastAPI, Flask, and others
- General web server — if the bot server supports WSGI/ASGI, then requests can be handled directly by listening on the appropriate port. But usually, to avoid occupying commonly used ports like
443, general web servers like nginx or Apache (there are others as well) are installed on the server, acting as a reverse proxy and forwarding requests to the required web application (for example, two different domains are two different applications)
This article covers the following options
- Certificate — official from Let's Encrypt (with a paid or free domain) and self-signed
- Bot server — aiohttp and FastAPI
- General web server — nginx
Getting a domain
The easiest way is to buy a domain you like from a domain name registrar (for example, Namecheap, Cloudflare, GoDaddy, Porkbun). But some people don't want to pay for domain rental, for example, if it's something non-commercial. In that case, there are services that allow you to get completely free domains (but this approach has its drawbacks)
If you already have a domain, all that's left is to create a DNS record that points to your server. Just create a record of type A and add your server's IP address to it. That's it, the domain is ready, but you might have to wait a little while for the domain to propagate across the internet
Free domain
I'll show you how to get a free domain using the FreeDNS service. You won't be able to choose an arbitrary name for it (formally, it will be a subdomain)
- Register on the website https://freedns.afraid.org
- Go to the subdomains section and click «Add a subdomain»
- Make sure that the «Type» field says
A - In the «Domain» field, choose your desired domain (if you click «Many many more available» -> «Share Domain Registry», more options will appear)
- In the «Subdomain» field, enter the name of the subdomain (your custom name), it will look like subdomain.domain
- In the «Destination» field, enter the IP address of your server
- Confirm the creation of the subdomain
You are not the owner of a free domain In such services, people let others use their domains. There are no guarantees of stability (you can only look at their uptime). Also, it's better to choose less popular domains to avoid problems with certificate renewal (Let's Encrypt has limits on the frequency of renewals). Remember, this option is not suitable for serious projects
Installing a web server
An example of installing nginx for Debian Linux
- Official instructions — https://nginx.org/en/linux_packages.html#Debian
- Check if it's installed
sudo nginx -v - Add to autostart
sudo systemctl enable nginx - Check the service status
sudo systemctl status nginx - If not running
sudo systemctl start nginx
Certificate from Lets Encrypt
Log in to your server via SSH and install certbot. The official website has an instruction generator for proper installation, all you need is to choose the required system and software
In our case, the command will be
sudo certbot certonly --nginx -d <your-domain>
You will have to wait a few seconds. After obtaining the certificate, certbot will set a timer for its auto-renewal
Congratulations, you now have an official certificate! The certificate files (fullchain.pem — the certificate chain, privkey.pem — the private key) will be located at the following paths. You don't need to move or copy them anywhere
/etc/letsencrypt/live/<your-domain>/fullchain.pem/etc/letsencrypt/live/<your-domain>/privkey.pem
Self-signed certificate
The disadvantages of a self-signed certificate are that you are responsible for its renewal and you will have to send it to Telegram, but more on that later. On the bright side — you can specify an arbitrary lifetime (with Let's Encrypt it's strictly 90 days)
Log in to your server via SSH and generate the certificate. For example, this is how its creation might look with the OpenSSL utility
openssl req -x509 -nodes -days 365 -newkey rsa:4096 -keyout ./cert.key -outform PEM -out ./cert.pem
req– subcommand for certificates- x509 – one of the standards for certificates
days 365– certificate lifetimenewkey rsa:4096– create a key with the RSA algorithm of length 4096 bitsoutform PEM– the required PEM generation formatkeyoutandout– where to place the key and the certificate
During generation, be sure to enter the server's IP address in the Common Name field!
Done, you now have a self-signed certificate!
Connecting to the webhook
You have the certificate, all that's left is to connect the bot to the webhook
Nginx configuration
First, you need to change the nginx configuration so it knows about our certificate and uses HTTPS. A simple example
server {
listen 443 ssl;
server_name <your-domain or server-ip>;
ssl_certificate /path/to/certificate;
ssl_certificate_key /path/to/privatekey;
location / {
proxy_pass http://127.0.0.1:<bot-server-port>;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
bot-server-port – come up with a port on which the bot server will listen. It can be any number from 1024 to 65535. Later, you will also need to specify it when starting the bot
To apply the changes, execute sudo nginx -s reload
Setting the webhook
This is done via the setWebhook method. Here are various examples
If the certificate is official
await bot.set_webhook(
getenv('WEBHOOK_URL'),
secret_token=getenv('WEBHOOK_SECRET'),
allowed_updates=list(ALLOWED_UPDATES),
)
If the certificate is self-signed, you must specify the certificate parameter with the path to the certificate. Make sure the cert.pem file has the correct permissions
await bot.set_webhook(
getenv('WEBHOOK_URL'),
certificate=FSInputFile('/path/to/cert.pem'),
secret_token=getenv('WEBHOOK_SECRET'),
allowed_updates=list(ALLOWED_UPDATES),
)
or manually via the console
curl -F "url=https://serverip-or-domain/webhook_path" -F "secret_token=<SECRET>" -F "certificate=@cert.pem" https://api.telegram.org/bot<TOKEN>/setWebhook
In the last option, you need to be in the directory with the file and be sure to add @, meaning the certificate name here is cert.pem
WEBHOOK_URL— a required parameter for the URL (https://...)secret_token— an optional parameter that I mentioned for additional securityallowed_updates— an optional parameter, specifying which updates can be sent to the bot
Configuring the bot server
I recommend choosing aiohttp because it's more compact and aiogram has an integration with it
Example for aiohttp
from aiogram.webhook.aiohttp_server import (
SimpleRequestHandler,
setup_application,
)
app = web.Application()
SimpleRequestHandler(
dispatcher=dp, bot=bot,
).register(
app, path=WEBHOOK_PATH,
)
setup_application(app, dp, bot=bot)
web.run_app(
app,
host=WEB_SERVER_HOST,
port=WEB_SERVER_PORT,
)
webandapp– a module for interacting with the HTTP protocol and an application instance of aiohttp, respectivelySimpleRequestHandler– a special aiogram filter that catches onlyPOSTrequests and redirects them to the bot for processingsetup_application– a function that helps configure the startup-shutdown processes of the aiohttp application along with the bot
Example for FastAPI
from aiogram import Dispatcher, Bot
from aiogram.types import Update
from fastapi import APIRouter, Depends, Header, HTTPException
from pydantic import SecretStr
from starlette import status
class BotStub:
pass
class DispatcherStub:
pass
class SecretStub:
pass
webhook_router = APIRouter(prefix="/webhook", tags=["Telegram Webhook"])
@webhook_router.post("")
async def webhook_route(
update: Update,
secret: SecretStr = Header(alias="X-Telegram-Bot-Api-Secret-Token"),
expected_secret: str = Depends(SecretStub),
bot: Bot = Depends(BotStub),
dispatcher: Dispatcher = Depends(DispatcherStub),
):
if secret.get_secret_value() != expected_secret:
raise HTTPException(
detail="Invalid secret",
status_code=status.HTTP_401_UNAUTHORIZED,
)
await dispatcher.feed_update(bot, update=update)
return {"ok": True}
def create_app(bot: Bot, dispatcher: Dispatcher, webhook_secret: str) -> FastAPI:
app = FastAPI()
app.dependency_overrides.update(
{
BotStub: lambda: bot,
DispatcherStub: lambda: dispatcher,
SecretStub: lambda: webhook_secret,
}
)
app.include_router(webhook_router)
return app
The key idea is to receive a request, process it according to our requirements, and pass it to the bot via the dispatcher's feed methods
Useful commands
- Check locally if the certificate works
openssl s_client -connect ip_address:443 - View requests to nginx
tail -f /var/log/nginx/access.log - View webhook status (conveniently right in your browser)
https://api.telegram.org/bot<TOKEN>/getWebhookInfo
Found a mistake? Feedback: hello@shaonis.space
