<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>grdly Reader</title>
    <link>https://grdly.com</link>
    <description>Read the latest posts from grdly.</description>
    <pubDate>Thu, 16 Apr 2026 19:48:58 +0000</pubDate>
    <item>
      <title>How to set up a webhook for a Telegram bot</title>
      <link>https://grdly.com/devogram/how-to-set-up-a-webhook-for-a-telegram-bot</link>
      <description>&lt;![CDATA[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&#xA;&#xA;Article Contents&#xA;&#xA;What is a webhook&#xA;Where to start&#xA;Getting a domain&#xA;Installing a web server&#xA;Certificate from Lets Encrypt&#xA;Self-signed certificate&#xA;Connecting to the webhook&#xA;&#xA;---&#xA;What is a webhook&#xA;&#xA;🪝 Webhook - is a mechanism for processing updates by receiving POST requests to a web server and passing them to the bot&#xA;&#xA;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&#xA;&#xA;When it&#39;s useful&#xA;&#xA;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&#xA;If we don&#39;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&#39;s important to remember that each getUpdates call has an execution time and a certain load&#xA;This mechanism allows you to distribute updates to your desired entry points and implement horizontal scaling of the project&#xA;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&#xA;&#xA;Prerequisites&#xA;&#xA;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&#xA;Port — The officially supported ports are 443, 80, 88, and 8443. Other ports are not supported. Don&#39;t forget to configure your firewall so the server accepts requests&#xA;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&#xA;SSL/TLS Version — Currently, TLS 1.2 and higher are supported. Older versions contain vulnerabilities and will not work&#xA;IPv4 — IPv6 is not currently supported&#xA;&#xA;  Additional security&#xA;  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&#39;s an update, and something bad will happen. To prevent this, some check the sender&#39;s IP address (for Telegram, they are static — 149.154.160.0/20 and 91.108.4.0/22), while others add their own keys/secrets to Telegram&#39;s requests (this is done when setting up the webhook), which are then verified on the server, but the choice is yours&#xA;&#xA;---&#xA;Where to start&#xA;&#xA;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&#39;s Encrypt that issues official certificates for free if you already have a domain&#xA;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&#xA;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)&#xA;&#xA;This article covers the following options&#xA;&#xA;Certificate — official from Let&#39;s Encrypt (with a paid or free domain) and self-signed&#xA;Bot server — aiohttp and FastAPI&#xA;General web server — nginx&#xA;&#xA;---&#xA;Getting a domain&#xA;&#xA;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&#39;t want to pay for domain rental, for example, if it&#39;s something non-commercial. In that case, there are services that allow you to get completely free domains (but this approach has its drawbacks)&#xA;&#xA;If you already have a domain, all that&#39;s left is to create a DNS record that points to your server. Just create a record of type A and add your server&#39;s IP address to it. That&#39;s it, the domain is ready, but you might have to wait a little while for the domain to propagate across the internet&#xA;&#xA;Free domain&#xA;&#xA;I&#39;ll show you how to get a free domain using the FreeDNS service. You won&#39;t be able to choose an arbitrary name for it (formally, it will be a subdomain)&#xA;&#xA;Register on the website https://freedns.afraid.org&#xA;Go to the subdomains section and click «Add a subdomain»&#xA;Make sure that the «Type» field says A&#xA;In the «Domain» field, choose your desired domain (if you click «Many many more available» -\  «Share Domain Registry», more options will appear)&#xA;In the «Subdomain» field, enter the name of the subdomain (your custom name), it will look like subdomain.domain&#xA;In the «Destination» field, enter the IP address of your server&#xA;Confirm the creation of the subdomain&#xA;&#xA;  You are not the owner of a free domain&#xA;  In such services, people let others use their domains. There are no guarantees of stability (you can only look at their uptime). Also, it&#39;s better to choose less popular domains to avoid problems with certificate renewal (Let&#39;s Encrypt has limits on the frequency of renewals). Remember, this option is not suitable for serious projects&#xA;&#xA;---&#xA;Installing a web server&#xA;&#xA;An example of installing nginx for Debian Linux&#xA;&#xA;Official instructions — https://nginx.org/en/linuxpackages.html#Debian&#xA;Check if it&#39;s installed sudo nginx -v&#xA;Add to autostart sudo systemctl enable nginx&#xA;Check the service status sudo systemctl status nginx&#xA;If not running sudo systemctl start nginx&#xA;&#xA;---&#xA;Certificate from Lets Encrypt&#xA;&#xA;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&#xA;&#xA;In our case, the command will be&#xA;&#xA;sudo certbot certonly --nginx -d your-domain&#xA;&#xA;You will have to wait a few seconds. After obtaining the certificate, certbot will set a timer for its auto-renewal&#xA;&#xA;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&#39;t need to move or copy them anywhere&#xA;&#xA;  /etc/letsencrypt/live/your-domain/fullchain.pem&#xA;  /etc/letsencrypt/live/your-domain/privkey.pem&#xA;&#xA;---&#xA;Self-signed certificate&#xA;&#xA;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&#39;s Encrypt it&#39;s strictly 90 days)&#xA;&#xA;Log in to your server via SSH and generate the certificate. For example, this is how its creation might look with the OpenSSL utility&#xA;&#xA;openssl req -x509 -nodes -days 365 -newkey rsa:4096 -keyout ./cert.key -outform PEM -out ./cert.pem&#xA;&#xA;req - subcommand for certificates&#xA;x509 - one of the standards for certificates&#xA;days 365 - certificate lifetime&#xA;newkey rsa:4096 - create a key with the RSA algorithm of length 4096 bits&#xA;outform PEM - the required PEM generation format&#xA;keyout and out - where to place the key and the certificate&#xA;&#xA;During generation, be sure to enter the server&#39;s IP address in the Common Name field!&#xA;&#xA;Done, you now have a self-signed certificate!&#xA;&#xA;---&#xA;Connecting to the webhook&#xA;&#xA;You have the certificate, all that&#39;s left is to connect the bot to the webhook&#xA;&#xA;Nginx configuration&#xA;&#xA;First, you need to change the nginx configuration so it knows about our certificate and uses HTTPS. A simple example&#xA;&#xA;server {&#xA;    listen 443 ssl;&#xA;    servername your-domain or server-ip;&#xA;&#xA;    sslcertificate /path/to/certificate;&#xA;    sslcertificatekey /path/to/privatekey;&#xA;&#xA;    location / {&#xA;        proxypass http://127.0.0.1:bot-server-port;&#xA;&#xA;        proxysetheader Host $host;&#xA;        proxysetheader X-Real-IP $remoteaddr;&#xA;        proxysetheader X-Forwarded-For $proxyaddxforwardedfor;&#xA;        proxysetheader X-Forwarded-Proto $scheme;&#xA;    }&#xA;}&#xA;&#xA;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&#xA;&#xA;To apply the changes, execute sudo nginx -s reload&#xA;&#xA;Setting the webhook&#xA;&#xA;This is done via the setWebhook method. Here are various examples&#xA;&#xA;If the certificate is official&#xA;&#xA;await bot.setwebhook(&#xA;    getenv(&#39;WEBHOOKURL&#39;),&#xA;    secrettoken=getenv(&#39;WEBHOOKSECRET&#39;),&#xA;    allowedupdates=list(ALLOWEDUPDATES),&#xA;)&#xA;&#xA;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&#xA;&#xA;await bot.setwebhook(&#xA;    getenv(&#39;WEBHOOKURL&#39;),&#xA;    certificate=FSInputFile(&#39;/path/to/cert.pem&#39;),&#xA;    secrettoken=getenv(&#39;WEBHOOKSECRET&#39;),&#xA;    allowedupdates=list(ALLOWEDUPDATES),&#xA;)&#xA;&#xA;or manually via the console&#xA;&#xA;curl -F &#34;url=https://serverip-or-domain/webhookpath&#34; -F &#34;secrettoken=SECRET&#34; -F &#34;certificate=@cert.pem&#34; https://api.telegram.org/botTOKEN/setWebhook&#xA;&#xA;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&#xA;&#xA;WEBHOOKURL — a required parameter for the URL (https://...)&#xA;secrettoken — an optional parameter that I mentioned for additional security&#xA;allowedupdates — an optional parameter, specifying which updates can be sent to the bot&#xA;&#xA;Configuring the bot server&#xA;&#xA;I recommend choosing aiohttp because it&#39;s more compact and aiogram has an integration with it&#xA;&#xA;Example for aiohttp&#xA;&#xA;from aiogram.webhook.aiohttpserver import (&#xA;    SimpleRequestHandler,&#xA;    setupapplication,&#xA;)&#xA;&#xA;app = web.Application()&#xA;SimpleRequestHandler(&#xA;    dispatcher=dp, bot=bot,&#xA;).register(&#xA;    app, path=WEBHOOKPATH,&#xA;)&#xA;setupapplication(app, dp, bot=bot)&#xA;web.runapp(&#xA;    app, &#xA;    host=WEBSERVERHOST, &#xA;    port=WEBSERVERPORT,&#xA;)&#xA;&#xA;web and app - a module for interacting with the HTTP protocol and an application instance of aiohttp, respectively&#xA;SimpleRequestHandler - a special aiogram filter that catches only POST requests and redirects them to the bot for processing&#xA;setupapplication - a function that helps configure the startup-shutdown processes of the aiohttp application along with the bot&#xA;&#xA;Example for FastAPI&#xA;&#xA;from aiogram import Dispatcher, Bot&#xA;from aiogram.types import Update&#xA;from fastapi import APIRouter, Depends, Header, HTTPException&#xA;from pydantic import SecretStr&#xA;from starlette import status&#xA;&#xA;class BotStub:&#xA;    pass&#xA;&#xA;class DispatcherStub:&#xA;    pass&#xA;&#xA;class SecretStub:&#xA;    pass&#xA;&#xA;webhookrouter = APIRouter(prefix=&#34;/webhook&#34;, tags=[&#34;Telegram Webhook&#34;])&#xA;&#xA;@webhookrouter.post(&#34;&#34;)&#xA;async def webhookroute(&#xA;    update: Update,&#xA;    secret: SecretStr = Header(alias=&#34;X-Telegram-Bot-Api-Secret-Token&#34;),&#xA;    expectedsecret: str = Depends(SecretStub),&#xA;    bot: Bot = Depends(BotStub),&#xA;    dispatcher: Dispatcher = Depends(DispatcherStub),&#xA;):&#xA;    if secret.getsecretvalue() != expectedsecret:&#xA;        raise HTTPException(&#xA;            detail=&#34;Invalid secret&#34;,&#xA;            statuscode=status.HTTP401UNAUTHORIZED,&#xA;        )&#xA;    await dispatcher.feedupdate(bot, update=update)&#xA;    return {&#34;ok&#34;: True}&#xA;&#xA;def createapp(bot: Bot, dispatcher: Dispatcher, webhooksecret: str) -  FastAPI:&#xA;    app = FastAPI()&#xA;&#xA;    app.dependencyoverrides.update(&#xA;        {&#xA;            BotStub: lambda: bot,&#xA;            DispatcherStub: lambda: dispatcher,&#xA;            SecretStub: lambda: webhooksecret,&#xA;        }&#xA;    )&#xA;    app.includerouter(webhookrouter)&#xA;    return app&#xA;&#xA;The key idea is to receive a request, process it according to our requirements, and pass it to the bot via the dispatcher&#39;s feed methods&#xA;&#xA;Useful commands&#xA;&#xA;Check locally if the certificate works openssl sclient -connect ip_address:443&#xA;View requests to nginx tail -f /var/log/nginx/access.log&#xA;View webhook status (conveniently right in your browser) https://api.telegram.org/botTOKEN/getWebhookInfo]]&gt;</description>
      <content:encoded><![CDATA[<p>In this article, you will learn different ways to set up a webhook for a Telegram bot in the context of the <a href="https://github.com/aiogram/aiogram" rel="nofollow">aiogram</a> framework. However, the information here will be useful for more than just that. You can use it as a cheat sheet</p>

<h2 id="article-contents" id="article-contents">Article Contents</h2>
<ol><li><a href="#what-is-a-webhook" rel="nofollow">What is a webhook</a></li>
<li><a href="#where-to-start" rel="nofollow">Where to start</a></li>
<li><a href="#getting-a-domain" rel="nofollow">Getting a domain</a></li>
<li><a href="#installing-a-web-server" rel="nofollow">Installing a web server</a></li>
<li><a href="#certificate-from-lets-encrypt" rel="nofollow">Certificate from Lets Encrypt</a></li>
<li><a href="#self-signed-certificate" rel="nofollow">Self-signed certificate</a></li>
<li><a href="#connecting-to-the-webhook" rel="nofollow">Connecting to the webhook</a></li></ol>

<hr>

<h2 id="what-is-a-webhook" id="what-is-a-webhook">What is a webhook</h2>

<p>🪝 <strong>Webhook</strong> – is a mechanism for processing updates by receiving <code>POST</code> requests to a web server and passing them to the bot</p>

<p>With polling, we get updates from the <a href="https://api.telegram.org" rel="nofollow">api.telegram.org</a> endpoint, but with a webhook, we provide Telegram with our personal link where it will send updates, and we will catch them</p>

<h3 id="when-it-s-useful" id="when-it-s-useful">When it&#39;s useful</h3>
<ul><li>In a situation where the bot has a very high load, meaning many updates per unit of time. It is known that polling (the <a href="https://core.telegram.org/bots/api#getupdates" rel="nofollow">getUpdates</a> method) allows you to get a maximum of 100 updates per call</li>
<li>If we don&#39;t want to see failed attempts to get updates in the logs. With polling, every bot constantly makes requests to <a href="https://api.telegram.org" rel="nofollow">api.telegram.org</a>, even if there are no updates or if Telegram has server issues. It&#39;s important to remember that each <a href="https://core.telegram.org/bots/api#getupdates" rel="nofollow">getUpdates</a> call has an execution time and a certain load</li>
<li>This mechanism allows you to distribute updates to your desired entry points and implement horizontal scaling of the project</li>
<li>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</li></ul>

<h3 id="prerequisites" id="prerequisites">Prerequisites</h3>
<ul><li><strong>Server</strong> — 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</li>
<li><strong>Port</strong> — The officially supported ports are <code>443</code>, <code>80</code>, <code>88</code>, and <code>8443</code>. Other ports are not supported. Don&#39;t forget to configure your firewall so the server accepts requests</li>
<li><strong>HTTPS</strong> — 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</li>
<li><strong>SSL/TLS Version</strong> — Currently, TLS 1.2 and higher are supported. Older versions contain vulnerabilities and will not work</li>
<li><strong>IPv4</strong> — IPv6 is not currently supported</li></ul>

<blockquote><p><strong>Additional security</strong>
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&#39;s an update, and something bad will happen. To prevent this, some check the sender&#39;s IP address (for Telegram, they are static — <code>149.154.160.0/20</code> and <code>91.108.4.0/22</code>), while others add their own keys/secrets to Telegram&#39;s requests (this is done when setting up the webhook), which are then verified on the server, but the choice is yours</p></blockquote>

<hr>

<h2 id="where-to-start" id="where-to-start">Where to start</h2>
<ul><li><strong>Certificate type</strong> — official (approved by a <a href="https://en.wikipedia.org/wiki/Certificate_authority" rel="nofollow">Certificate authority</a>) 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 <a href="https://letsencrypt.org" rel="nofollow">Let&#39;s Encrypt</a> that issues official certificates for free if you already have a domain</li>
<li><strong>Bot server</strong> — for a webhook, the bot itself must run as a server and listen on a specific port. You can set up the server using <a href="https://github.com/aio-libs/aiohttp" rel="nofollow">aiohttp</a>, <a href="https://github.com/fastapi/fastapi" rel="nofollow">FastAPI</a>, <a href="https://github.com/pallets/flask" rel="nofollow">Flask</a>, and others</li>
<li><strong>General web server</strong> — if the bot server supports <a href="https://en.wikipedia.org/wiki/Asynchronous_Server_Gateway_Interface" rel="nofollow">WSGI/ASGI</a>, then requests can be handled directly by listening on the appropriate port. But usually, to avoid occupying commonly used ports like <code>443</code>, general web servers like <a href="https://github.com/nginx/nginx" rel="nofollow">nginx</a> or <a href="https://github.com/apache/httpd" rel="nofollow">Apache</a> (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)</li></ul>

<p>This article covers the following options</p>
<ul><li><strong>Certificate</strong> — official from <a href="https://letsencrypt.org" rel="nofollow">Let&#39;s Encrypt</a> (with a paid or free domain) and self-signed</li>
<li><strong>Bot server</strong> — <a href="https://github.com/aio-libs/aiohttp" rel="nofollow">aiohttp</a> and <a href="https://github.com/fastapi/fastapi" rel="nofollow">FastAPI</a></li>
<li><strong>General web server</strong> — <a href="https://github.com/nginx/nginx" rel="nofollow">nginx</a></li></ul>

<hr>

<h2 id="getting-a-domain" id="getting-a-domain">Getting a domain</h2>

<p>The easiest way is to buy a domain you like from a domain name registrar (for example, <a href="https://namecheap.com" rel="nofollow">Namecheap</a>, <a href="https://cloudflare.com" rel="nofollow">Cloudflare</a>, <a href="https://godaddy.com" rel="nofollow">GoDaddy</a>, <a href="https://porkbun.com" rel="nofollow">Porkbun</a>). But some people don&#39;t want to pay for domain rental, for example, if it&#39;s something non-commercial. In that case, there are services that allow you to get completely free domains (but this approach has its drawbacks)</p>

<p>If you already have a domain, all that&#39;s left is to create a <a href="https://en.wikipedia.org/wiki/Domain_Name_System" rel="nofollow">DNS</a> record that points to your server. Just create a record of type <code>A</code> and add your server&#39;s IP address to it. That&#39;s it, the domain is ready, but you might have to wait a little while for the domain to propagate across the internet</p>

<h3 id="free-domain" id="free-domain">Free domain</h3>

<p>I&#39;ll show you how to get a free domain using the <a href="https://freedns.afraid.org" rel="nofollow">FreeDNS</a> service. You won&#39;t be able to choose an arbitrary name for it (formally, it will be a subdomain)</p>
<ul><li>Register on the website <a href="https://freedns.afraid.org" rel="nofollow">https://freedns.afraid.org</a></li>
<li>Go to the <a href="https://freedns.afraid.org/subdomain" rel="nofollow">subdomains</a> section and click «Add a subdomain»</li>
<li>Make sure that the «Type» field says <code>A</code></li>
<li>In the «Domain» field, choose your desired domain (if you click «Many many more available» -&gt; «Share Domain Registry», more options will appear)</li>
<li>In the «Subdomain» field, enter the name of the subdomain (your custom name), it will look like subdomain.domain</li>
<li>In the «Destination» field, enter the IP address of your server</li>
<li>Confirm the creation of the subdomain</li></ul>

<blockquote><p><strong>You are not the owner of a free domain</strong>
In such services, people let others use their domains. There are no guarantees of stability (you can only look at their uptime). Also, it&#39;s better to choose less popular domains to avoid problems with certificate renewal (<a href="https://letsencrypt.org" rel="nofollow">Let&#39;s Encrypt</a> has limits on the frequency of renewals). Remember, this option is not suitable for serious projects</p></blockquote>

<hr>

<h2 id="installing-a-web-server" id="installing-a-web-server">Installing a web server</h2>

<p>An example of installing <a href="https://github.com/nginx/nginx" rel="nofollow">nginx</a> for <a href="https://en.wikipedia.org/wiki/Debian" rel="nofollow">Debian Linux</a></p>
<ul><li>Official instructions — <a href="https://nginx.org/en/linux_packages.html#Debian" rel="nofollow">https://nginx.org/en/linux_packages.html#Debian</a></li>
<li>Check if it&#39;s installed <code>sudo nginx -v</code></li>
<li>Add to autostart <code>sudo systemctl enable nginx</code></li>
<li>Check the service status <code>sudo systemctl status nginx</code></li>
<li>If not running <code>sudo systemctl start nginx</code></li></ul>

<hr>

<h2 id="certificate-from-lets-encrypt" id="certificate-from-lets-encrypt">Certificate from Lets Encrypt</h2>

<p>Log in to your server via <a href="https://en.wikipedia.org/wiki/Secure_Shell" rel="nofollow">SSH</a> and install <a href="https://certbot.eff.org" rel="nofollow">certbot</a>. The official website has an instruction generator for proper installation, all you need is to choose the required system and software</p>

<p>In our case, the command will be</p>

<pre><code class="language-sh">sudo certbot certonly --nginx -d &lt;your-domain&gt;
</code></pre>

<p>You will have to wait a few seconds. After obtaining the certificate, <a href="https://certbot.eff.org" rel="nofollow">certbot</a> will set a timer for its auto-renewal</p>

<p>Congratulations, you now have an official certificate! The certificate files (<code>fullchain.pem</code> — the certificate chain, <code>privkey.pem</code> — the private key) will be located at the following paths. You don&#39;t need to move or copy them anywhere</p>
<ul><li><code>/etc/letsencrypt/live/&lt;your-domain&gt;/fullchain.pem</code></li>
<li><code>/etc/letsencrypt/live/&lt;your-domain&gt;/privkey.pem</code></li></ul>

<hr>

<h2 id="self-signed-certificate" id="self-signed-certificate">Self-signed certificate</h2>

<p>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 <a href="https://letsencrypt.org" rel="nofollow">Let&#39;s Encrypt</a> it&#39;s strictly 90 days)</p>

<p>Log in to your server via <a href="https://en.wikipedia.org/wiki/Secure_Shell" rel="nofollow">SSH</a> and generate the certificate. For example, this is how its creation might look with the <a href="https://www.openssl.org" rel="nofollow">OpenSSL</a> utility</p>

<pre><code class="language-sh">openssl req -x509 -nodes -days 365 -newkey rsa:4096 -keyout ./cert.key -outform PEM -out ./cert.pem
</code></pre>
<ul><li><code>req</code> – subcommand for certificates</li>
<li><a href="https://en.wikipedia.org/wiki/X.509" rel="nofollow">x509</a> – one of the standards for certificates</li>
<li><code>days 365</code> – certificate lifetime</li>
<li><code>newkey rsa:4096</code> – create a key with the RSA algorithm of length 4096 bits</li>
<li><code>outform PEM</code> – the <a href="https://core.telegram.org/bots/self-signed" rel="nofollow">required</a> PEM generation format</li>
<li><code>keyout</code> and <code>out</code> – where to place the key and the certificate</li></ul>

<p>During generation, be sure to enter the server&#39;s IP address in the <code>Common Name</code> field!</p>

<p>Done, you now have a self-signed certificate!</p>

<hr>

<h2 id="connecting-to-the-webhook" id="connecting-to-the-webhook">Connecting to the webhook</h2>

<p>You have the certificate, all that&#39;s left is to connect the bot to the webhook</p>

<h3 id="nginx-configuration" id="nginx-configuration">Nginx configuration</h3>

<p>First, you need to change the <a href="https://github.com/nginx/nginx" rel="nofollow">nginx</a> configuration so it knows about our certificate and uses HTTPS. A simple example</p>

<pre><code class="language-nginx">server {
    listen 443 ssl;
    server_name &lt;your-domain or server-ip&gt;;

    ssl_certificate /path/to/certificate;
    ssl_certificate_key /path/to/privatekey;

    location / {
        proxy_pass http://127.0.0.1:&lt;bot-server-port&gt;;

        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;
    }
}
</code></pre>

<p><code>bot-server-port</code> – come up with a port on which the bot server will listen. It can be any number from <code>1024</code> to <code>65535</code>. Later, you will also need to specify it when starting the bot</p>

<p>To apply the changes, execute <code>sudo nginx -s reload</code></p>

<h3 id="setting-the-webhook" id="setting-the-webhook">Setting the webhook</h3>

<p>This is done via the <a href="https://core.telegram.org/bots/api#setwebhook" rel="nofollow">setWebhook</a> method. Here are various examples</p>

<p>If the certificate is official</p>

<pre><code class="language-python">await bot.set_webhook(
    getenv(&#39;WEBHOOK_URL&#39;),
    secret_token=getenv(&#39;WEBHOOK_SECRET&#39;),
    allowed_updates=list(ALLOWED_UPDATES),
)
</code></pre>

<p>If the certificate is self-signed, you must specify the <code>certificate</code> parameter with the path to the certificate. Make sure the <code>cert.pem</code> file has the correct permissions</p>

<pre><code class="language-python">await bot.set_webhook(
    getenv(&#39;WEBHOOK_URL&#39;),
    certificate=FSInputFile(&#39;/path/to/cert.pem&#39;),
    secret_token=getenv(&#39;WEBHOOK_SECRET&#39;),
    allowed_updates=list(ALLOWED_UPDATES),
)
</code></pre>

<p>or manually via the console</p>

<pre><code class="language-sh">curl -F &#34;url=https://serverip-or-domain/webhook_path&#34; -F &#34;secret_token=&lt;SECRET&gt;&#34; -F &#34;certificate=@cert.pem&#34; https://api.telegram.org/bot&lt;TOKEN&gt;/setWebhook
</code></pre>

<p>In the last option, you need to be in the directory with the file and be sure to add <code>@</code>, meaning the certificate name here is <code>cert.pem</code></p>
<ul><li><code>WEBHOOK_URL</code> — a required parameter for the URL (<code>https://...</code>)</li>
<li><code>secret_token</code> — an optional parameter that I mentioned for additional security</li>
<li><code>allowed_updates</code> — an optional parameter, specifying which updates can be sent to the bot</li></ul>

<h3 id="configuring-the-bot-server" id="configuring-the-bot-server">Configuring the bot server</h3>

<p>I recommend choosing <a href="https://github.com/aio-libs/aiohttp" rel="nofollow">aiohttp</a> because it&#39;s more compact and <a href="https://github.com/aiogram/aiogram" rel="nofollow">aiogram</a> has an integration with it</p>

<h4 id="example-for-aiohttp" id="example-for-aiohttp">Example for aiohttp</h4>

<pre><code class="language-python">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,
)
</code></pre>
<ul><li><code>web</code> and <code>app</code> – a module for interacting with the HTTP protocol and an application instance of <a href="https://github.com/aio-libs/aiohttp" rel="nofollow">aiohttp</a>, respectively</li>
<li><code>SimpleRequestHandler</code> – a special <a href="https://github.com/aiogram/aiogram" rel="nofollow">aiogram</a> filter that catches only <code>POST</code> requests and redirects them to the bot for processing</li>
<li><code>setup_application</code> – a function that helps configure the startup-shutdown processes of the <a href="https://github.com/aio-libs/aiohttp" rel="nofollow">aiohttp</a> application along with the bot</li></ul>

<h4 id="example-for-fastapi" id="example-for-fastapi">Example for FastAPI</h4>

<pre><code class="language-python">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=&#34;/webhook&#34;, tags=[&#34;Telegram Webhook&#34;])


@webhook_router.post(&#34;&#34;)
async def webhook_route(
    update: Update,
    secret: SecretStr = Header(alias=&#34;X-Telegram-Bot-Api-Secret-Token&#34;),
    expected_secret: str = Depends(SecretStub),
    bot: Bot = Depends(BotStub),
    dispatcher: Dispatcher = Depends(DispatcherStub),
):
    if secret.get_secret_value() != expected_secret:
        raise HTTPException(
            detail=&#34;Invalid secret&#34;,
            status_code=status.HTTP_401_UNAUTHORIZED,
        )
    await dispatcher.feed_update(bot, update=update)
    return {&#34;ok&#34;: True}

def create_app(bot: Bot, dispatcher: Dispatcher, webhook_secret: str) -&gt; FastAPI:
    app = FastAPI()

    app.dependency_overrides.update(
        {
            BotStub: lambda: bot,
            DispatcherStub: lambda: dispatcher,
            SecretStub: lambda: webhook_secret,
        }
    )
    app.include_router(webhook_router)
    return app
</code></pre>

<p>The key idea is to receive a request, process it according to our requirements, and pass it to the bot via the <code>dispatcher</code>&#39;s <a href="https://github.com/aiogram/aiogram/blob/dev-3.x/aiogram/dispatcher/dispatcher.py" rel="nofollow">feed</a> methods</p>

<h4 id="useful-commands" id="useful-commands">Useful commands</h4>
<ul><li>Check locally if the certificate works <code>openssl s_client -connect ip_address:443</code></li>
<li>View requests to <a href="https://github.com/nginx/nginx" rel="nofollow">nginx</a> <code>tail -f /var/log/nginx/access.log</code></li>
<li>View webhook status (conveniently right in your browser) <code>https://api.telegram.org/bot&lt;TOKEN&gt;/getWebhookInfo</code></li></ul>
]]></content:encoded>
      <author>devogram</author>
      <guid>https://grdly.com/read/a/kff0te78xy</guid>
      <pubDate>Mon, 13 Oct 2025 13:19:59 +0000</pubDate>
    </item>
    <item>
      <title>The update lifecycle in aiogram</title>
      <link>https://grdly.com/devogram/the-update-lifecycle-in-aiogram</link>
      <description>&lt;![CDATA[This article describes the key aspects of how Telegram bots work and will be useful for both beginners who are just starting out and for advanced developers. Specifically, we&#39;re talking about the aiogram framework, but all the ideas presented can be seen anywhere. I assure you that after reading this, it will be much easier for you to understand all the details of the mechanism as a single process&#xA;&#xA;Article Contents&#xA;&#xA;The concept of an update in Telegram&#xA;Methods of receiving updates&#xA;Processing of updates&#xA;Router and dispatcher&#xA;Middleware&#xA;Filter&#xA;Handler&#xA;&#xA;  Local terms&#xA;  All terms should be understood in the context of Telegram and aiogram. Similar concepts can be found in many places, so don&#39;t take them as universal definitions for everything!&#xA;&#xA;---&#xA;Update&#xA;&#xA;💡 An Update — is an object with data that appears as a result of a certain action from the client&#39;s side, for example, a user or a bot&#xA;&#xA;Examples for better understanding&#xA;&#xA;A user writes a message to the bot — an update is created with all the data by which we understand where, when, and how the message was sent, so it can be processed&#xA;A user joins/leaves a channel or group — a corresponding update appears. Which chat it was, the user&#39;s updated status in the chat...&#xA;An admin bans someone — we see an update with data about this event: in which chat, whom, for how long, and so on&#xA;A user presses a button — we receive an update with data about the button&#xA;&#xA;  Why don&#39;t bots see each other&#39;s messages?&#xA;  A trivial but simple example. Imagine there are two bots in a group. The first one sends the message «chicken», the second one replies «egg». It seems normal, but what if you configure the first bot to reply to «egg» with «chicken» again? Then you get an infinite loop of messages chicken, egg, chicken, egg... This is why Telegram has such a restriction&#xA;&#xA;The term update should be used as a general concept that speaks about some events and their processing. A good sentence would be: «My bot doesn&#39;t catch updates» - this literally means that the bot does not react to the user&#39;s actions in any way&#xA;&#xA;Types of updates&#xA;&#xA;An update should be understood as some event. In turn, each event that has occurred has a defined update type. This is done so that one can easily understand the type of event (new message, user deletion, button click, etc) and standardize the data that will be included in the update. Because it is better to have a certain type of update with predefined fields that can be in it, rather than receiving a universal update and trying to figure out what happened&#xA;&#xA;The most commonly used update types&#xA;&#xA;Message - contains data about a message in a chat: text/media, formatting, sender data, possibly a keyboard, and much more&#xA;CallbackQuery - is responsible for inline buttons, it contains data that was put into the buttons during their creation, user data, chat data, and more&#xA;InlineQuery - comes in inline mode (eg bots like @gif receive them), contains the user&#39;s query, chat type, and other data for managing the output results&#xA;ChatMemberUpdated - stores data about updates of users/bots in a chat, joining/leaving the chat, changing permissions, and so on&#xA;ChatJoinRequest - represents a request to join a chat with the corresponding data&#xA;&#xA;---&#xA;Methods of receiving updates&#xA;  You can manage a bot without running it&#xA;  It is often said «send from under the token» - that is, take the bot&#39;s token and send the necessary request to the Telegram Bot API. To do this, you don&#39;t need to run the update receiving mechanism, but its operation will not interfere in any way&#xA;&#xA;🔄 Polling — a polling mechanism and the simplest way to get updates. The client (that is, our bot on the server) constantly, at small intervals, sends requests to api.telegram.org to get new updates. This mechanism is very easy to try manually, you can send the corresponding request directly in your browser and see the response from Telegram (https://api.telegram.org/botTOKEN/getUpdates, where TOKEN is your bot&#39;s token)&#xA;⏳ Long-polling — a cooler version of polling. The bot again sends requests to api.telegram.org, but not constantly. If there are no new updates, Telegram maintains a connection with the bot&#39;s server until new updates appear. When updates appear, Telegram sends the updates to the bot over the previously opened connection. This is some kind of hybrid of polling + an idea from webhook&#xA;🌐 Webhook — a method of receiving updates in which we do not need to poll the API at all. The key point is to inform Telegram where we want it to send us updates. Thus, our bot just waits for new events. However, this method of receiving updates requires additional configuration&#xA;&#xA;  Which update receiving mechanism should be chosen?&#xA;  Long-polling or Webhook — that is the question. To answer it, one should analyze the load on the bot, the need for its scaling. If in the future the bot will be popular only locally, then polling will be enough. A webhook will make sense, for example, for organizing a high-load platform or for a quality multibot. But the method of receiving updates can always be changed\!&#xA;&#xA;---&#xA;Processing of updates&#xA;&#xA;The full path of update processing includes the following stages&#xA;&#xA;The server with the bot receives an update&#xA;The bot eats the update (feed methods, which pass the update directly to the bot)&#xA;&#xA;Then the update goes through the so-called chain of processors&#xA;&#xA;Passing through outer middlewares&#xA;The update running through the hierarchy of routers&#xA;Filters for the update (custom and built-in)&#xA;It gets into inner middlewares&#xA;Event processing in a handler&#xA;Post-processing&#xA;&#xA;  Knowledge vs. trial and error&#xA;  Not every person who uses aiogram understands how an update passes through all these objects. It is important to be able to see the big picture. Then you will be able to quickly understand where and how to implement this or that bot logic!&#xA;&#xA;---&#xA;Router and dispatcher&#xA;&#xA;We received an update, what&#39;s next? Obviously, the bot should respond to some things and not to others — that is, filter the events that are relevant to it. Here, registrations and processors come into play — they are the foundation of the entire filtering system&#xA;&#xA;📌 Registration can be understood as setting a processor for the specified update type in a certain order (priority) relative to other processors&#xA;🌀 Processors can be handlers, filters, and middlewares that somehow interact with the update (more on each of them a little later). The point is that when aiogram receives an update, it will try to «catch it with processors», and in the order of their registration. First, the framework will try to give the update to those processors that were registered earlier, and only then (if it did not fit any of them) to others&#xA;&#xA;  Registrations are important&#xA;  One of the most common mistakes among beginners is precisely the misunderstanding of the nature of registrations. Keep this in mind — and you will have fewer thoughts about why this update first goes there and not here&#xA;&#xA;Now let&#39;s move on to the organization of catching updates, namely to routers. Perhaps you have only used aiogram 2.X and routers scare you, it&#39;s not clear why they are needed. Then I will try to change your mind!&#xA;&#xA;📡 Router — is an object that represents a branch of registered processors, it can also have a name&#xA;🎩 Dispatcher — the root (main) router that contains all the nested routers of the bot, stores the storage and FSM strategy, and is responsible for starting the bot (or feeding it updates). You can also register processors with it&#xA;&#xA;Purely theoretically, one dispatcher as a router would be enough to handle all events. But using routers has its advantages&#xA;&#xA;Modularity:&#xA;   Each router is responsible for a specific functionality of the bot&#xA;   This improves code organization and makes it more structured&#xA;   Examples:&#xA;     The admin router contains handlers only for administrators&#xA;     The user router processes commands from regular users&#xA;     The payment router is responsible for all payment-related operations&#xA;   This approach simplifies code navigation and maintenance&#xA;&#xA;Registration:&#xA;   Instead of registering each processor in the dispatcher, you can manage entire branches of processors&#xA;   This makes it easier to set the priority of update processing&#xA;   Example:&#xA;     First, updates from the user router are processed&#xA;     Then, if the update is not processed, it goes to the admin router&#xA;   Important: the order of registration of processors within each router also matters&#xA;&#xA;Flexibility:&#xA;   You can easily enable or disable entire groups of functionality&#xA;   This is useful for:&#xA;     Testing new features without affecting the main code&#xA;     Implementing «red buttons» to quickly disable certain functions&#xA;     Creating multibots where different sets of functions can be enabled depending on the context&#xA;   Example: you can temporarily disable the payment router for maintenance without affecting other bot functions&#xA;&#xA;Privacy:&#xA;   Each router can have its own filters and middlewares&#xA;   This allows:&#xA;     Setting access rights checks once for the entire set of handlers&#xA;     Logging actions within a specific functionality&#xA;   Examples:&#xA;     The admin router can have a filter that checks for administrative rights for all its handlers&#xA;     The payment router can log all financial transactions without affecting other parts of the bot&#xA;&#xA;Hierarchy:&#xA;   Routers can contain other routers, creating a tree-like structure&#xA;   This allows:&#xA;     Implementing complex update processing systems&#xA;     Creating flexible access rights systems&#xA;   Example:&#xA;     The main user router can contain sub-routers beginner, advanced, premium&#xA;     Each sub-router has its own set of commands and functions available for the corresponding user level&#xA;&#xA;  None of these advantages exist with a single dispatcher&#xA;  But don&#39;t think badly of the dispatcher. It serves more general purposes. The text above is aimed at convincing you not to use only one dispatcher in your bot (unless, of course, it&#39;s a primitive functionality). P.S. Switch to aiogram 3.X&#xA;&#xA;How an update propagates through routers, their filters, and handlers (middlewares are not shown)&#xA;&#xA;---&#xA;Middleware&#xA;&#xA;🔐 A Middleware — is an object that acts as an intermediary link between processors and works with an update at the level of its type 1]. It has two types of registration [2]. Its use comes down to calling or not calling the next processor in the chain in the right place (according to the bot&#39;s logic) [3]. Middleware provides the opportunity for pre, «inner», post, and outgoing processing of updates [4]. They can be implemented as classes or [functions (the first option is more common)&#xA;&#xA;[1] Middleware registration is only possible at the update type level, so the content of the internal data of the update does not play any role for it (but no one said that during processing, middleware cannot act as a filter for specific data in the update)&#xA;&#xA;2] Let&#39;s deal with the two types of registrations, for this, let&#39;s look at the figure below ([original)&#xA;&#xA;Organization of middleware&#xA;&#xA;As you can see, there are outer middleware and middleware, in simple terms — outer and inner. It would be correct to think that they have different registration priorities for an update&#xA;&#xA;What is the difference between these registrations?&#xA;&#xA;If there are 2 middlewares, one registered as outer, and the other as inner (for the same type), then a newly-arrived update will first meet the outer one, and only then the inner one&#xA;The outer middleware will be called for every update with the registered type, which cannot be said for inner registration&#xA;In the outer middleware, when processing an update, it is not known which handler it will potentially go to, that is, everything is at the update type level. But a middleware registered internally will have access to the update only after it has passed through all the previous processors, including filters. This will mean that the bot must have some specific handlers to react to this update&#xA;&#xA;  Registration for any update&#xA;  A middleware can be registered in such a way that any updates (of the update type) will come to it. The type of registration does not affect this in any way. Formally, outer and inner registration for the update type are different things, but in fact, they will work the same. P.S. It&#39;s better to use outer for this&#xA;&#xA;3] About calling the next processor in the chain. For example, let&#39;s look at this middleware code ([original)&#xA;&#xA;from aiogram import BaseMiddleware&#xA;from aiogram.types import Message&#xA;&#xA;class CounterMiddleware(BaseMiddleware):&#xA;    def init(self) -  None:&#xA;        self.counter = 0&#xA;&#xA;    async def call(&#xA;        self,&#xA;        handler: Callable[[Message, Dict[str, Any]], Awaitable[Any]],&#xA;        event: Message,&#xA;        data: Dict[str, Any]&#xA;    ) -  Any:&#xA;        self.counter += 1&#xA;        data[&#39;counter&#39;] = self.counter&#xA;        return await handler(event, data)&#xA;&#xA;Special attention should be paid to the await handler(event, data) call. This action passes the update further down the chain of processors. If there is a case in the middleware (for example, under certain conditions) where await handler(event, data) is not called, it will mean that the update is discarded/dropped. That is, none of the processors below will get access to the update as if it never existed. Therefore, we can say that in general, the job of a middleware is to pass data from the update somewhere and/or drop it under certain filtering conditions&#xA;&#xA;[4] About the types of processing&#xA;&#xA;Pre-processing (outer registration) is useful in cases where a minimum of information is sufficient for some actions and they need to be done as early as possible&#xA;«Inner» processing (author&#39;s name) occurs in inner middlewares. Knowing that the update has passed all the filters and is potentially going to a handler (where the bot reacts to a message from the user), it can be processed accordingly&#xA;Post-processing involves some actions in the middleware after calling the next processor in the chain and can be present in both outer and inner registrations. This is useful if the processing in the middleware can be done after the update has completed its path down the chain of processors below. Thus, the update will not be delayed for an optional action that can be done after it has passed the entire chain&#xA;Outgoing processing using middlewares is a slightly separate topic that not many people know about. The essence is to process the actions of the bot itself (the BaseRequestMiddleware class)&#xA;&#xA;Dependency Injection&#xA;&#xA;In aiogram, the Dependency Injection mechanism allows processors to receive dependencies (data necessary for the call) from other objects that have them or received them in the same way before. These dependencies can be obtained in different ways depending on the type of processor:&#xA;&#xA;Middleware — receives dependencies through the data argument — a dictionary from which the required data can be extracted by key&#xA;Filters and handlers — dependencies are passed as optional keyword arguments. To access them, you need to specify the required keys directly in the function parameters&#xA;Filters in handlers — can use MagicData or the .as() method to receive dependencies&#xA;These different methods allow for flexible management of dependencies and ensure efficient access to data in different parts of the application&#xA;&#xA;Delegator middleware&#xA;&#xA;This is an author&#39;s name from the word delegate (to pass on), which describes middlewares that pass data to processors using Dependency Injection. Perhaps, the delegator middleware is mainly used to obtain a database session so that the processors below have a convenient interface for querying it. Also, data processed in a certain way that depends on the update can be passed (eg something filtered from the update data or data on the bot&#39;s side related to the user). To use this mechanism in the context of middlewares, you need to add data through the data argument. This is a dictionary whose data is passed to the processors below&#xA;&#xA;---&#xA;Filter&#xA;&#xA;🕸 A Filter — is probably the widest range of processors. However, they are divided into two main types: custom and out-of-the-box. Here we will discuss custom ones, and we will discuss the rest later. A filter can be:&#xA;&#xA;an asynchronous function&#xA;a synchronous function&#xA;an anonymous function&#xA;any awaitable object&#xA;a subclass of aiogram.filters.base.Filter&#xA;an instance of MagicFilter&#xA;&#xA;When called, filters must return a boolean or a dictionary, which determines whether the update has passed the filtration (it only fails on False)&#xA;&#xA;Unlike middleware, the task of a filter is simply to filter the update. But sometimes, the filtering logic can be more complex than a simple comparison. For example, it makes sense to write a custom filter if you need to check whether the update belongs to a group admin&#xA;&#xA;An example of a simple filter that compares the message text with a given one (original)&#xA;&#xA;from aiogram import Router&#xA;from aiogram.filters import Filter&#xA;from aiogram.types import Message&#xA;&#xA;router = Router()&#xA;&#xA;class MyFilter(Filter):&#xA;    def init(self, mytext: str) -  None:&#xA;        self.mytext = mytext&#xA;&#xA;    async def call(self, message: Message) -  bool:&#xA;        return message.text == self.mytext&#xA;&#xA;@router.message(MyFilter(&#34;hello&#34;))&#xA;async def myhandler(message: Message): &#xA;    ...&#xA;&#xA;Combinations of filters&#xA;&#xA;If several filters are set during registration, logical AND will be used (the update must pass all of them). It is also allowed to have several registrations for one processor, in which case logical OR will be used (it is enough for the update to pass the filters of at least one registration). You can also use the magic filter syntax for combinations&#xA;&#xA;Delegator filter&#xA;&#xA;Yes, they can pass data using Dependency Injection. This is possible if the filter returns a dictionary. Then its data can be obtained in the following processors. This is very convenient for obtaining certain data from an update&#xA;&#xA;---&#xA;Handler&#xA;&#xA;🦾 A Handler — is a function registered to process updates. It is the final processor (the update goes no further) in which it is customary to interact with the user. The function should not return anything and can be registered both with and without filters. But at the same time, a handler can be implemented as a subclass&#xA;&#xA;  Ghost handler&#xA;  A common cause of errors for beginners is a handler without filters. All updates for which it is registered will go into such a handler. Therefore, if you understand that an update is going somewhere it shouldn&#39;t, remember if you have such a handler. In general, its use is not bad practice, you just need to work with it carefully&#xA;&#xA;Not much can be said about the handler itself, but we can talk about the filters that work with it. We discussed custom filters above, now it&#39;s time for the built-in ones&#xA;&#xA;Built-in filters for handlers&#xA;&#xA;Command - a convenient filter for catching commands, especially if you need to get the command arguments (eg get 123 from the command /start 123)&#xA;ChatMemberUpdated - a flexible filter for updates about user updates in chats (eg you can filter only new users)&#xA;Magic filters - lambda-like filters for comparing update data (as main filters, eg filtering text F.text == &#34;hello&#34;)&#xA;MagicData - allows you to compare update data with data passed through Dependency Injection&#xA;Callback Data Factory - a filter construction tool that suggests using data embedded in an inline button&#xA;Exceptions - filters for catching errors in processors]]&gt;</description>
      <content:encoded><![CDATA[<p>This article describes the key aspects of how Telegram bots work and will be useful for both beginners who are just starting out and for advanced developers. Specifically, we&#39;re talking about the <a href="https://github.com/aiogram/aiogram" rel="nofollow">aiogram</a> framework, but all the ideas presented can be seen anywhere. I assure you that after reading this, it will be much easier for you to understand all the details of the mechanism as a single process</p>

<h2 id="article-contents" id="article-contents">Article Contents</h2>
<ol><li><a href="#update" rel="nofollow">The concept of an update in Telegram</a></li>
<li><a href="#methods-of-receiving-updates" rel="nofollow">Methods of receiving updates</a></li>
<li><a href="#processing-of-updates" rel="nofollow">Processing of updates</a></li>
<li><a href="#router-and-dispatcher" rel="nofollow">Router and dispatcher</a></li>
<li><a href="#middleware" rel="nofollow">Middleware</a></li>
<li><a href="#filter" rel="nofollow">Filter</a></li>
<li><a href="#handler" rel="nofollow">Handler</a></li></ol>

<blockquote><p><strong>Local terms</strong>
All terms should be understood in the context of Telegram and aiogram. Similar concepts can be found in many places, so don&#39;t take them as universal definitions for everything!</p></blockquote>

<hr>

<h2 id="update" id="update">Update</h2>

<p>💡 An <strong>Update</strong> — is an object with data that appears as a result of a certain action from the client&#39;s side, for example, a user or a bot</p>

<h3 id="examples-for-better-understanding" id="examples-for-better-understanding">Examples for better understanding</h3>
<ul><li>A user writes a message to the bot — an update is created with all the data by which we understand where, when, and how the message was sent, so it can be processed</li>
<li>A user joins/leaves a channel or group — a corresponding update appears. Which chat it was, the user&#39;s updated status in the chat...</li>
<li>An admin bans someone — we see an update with data about this event: in which chat, whom, for how long, and so on</li>
<li>A user presses a button — we receive an update with data about the button</li></ul>

<blockquote><p><strong>Why don&#39;t bots see each other&#39;s messages?</strong>
A trivial but simple example. Imagine there are two bots in a group. The first one sends the message «chicken», the second one replies «egg». It seems normal, but what if you configure the first bot to reply to «egg» with «chicken» again? Then you get an infinite loop of messages chicken, egg, chicken, egg... This is why Telegram has such a restriction</p></blockquote>

<p>The term update should be used as a general concept that speaks about some events and their processing. A good sentence would be: «My bot doesn&#39;t catch updates» – this literally means that the bot does not react to the user&#39;s actions in any way</p>

<h3 id="types-of-updates" id="types-of-updates">Types of updates</h3>

<p>An update should be understood as some event. In turn, each event that has occurred has a defined update type. This is done so that one can easily understand the type of event (new message, user deletion, button click, etc) and standardize the data that will be included in the update. Because it is better to have a certain type of update with predefined fields that can be in it, rather than receiving a universal update and trying to figure out what happened</p>

<h3 id="the-most-commonly-used-update-types" id="the-most-commonly-used-update-types">The most commonly used update types</h3>
<ul><li><a href="https://core.telegram.org/bots/api#message" rel="nofollow">Message</a> – contains data about a message in a chat: text/media, formatting, sender data, possibly a keyboard, and much more</li>
<li><a href="https://core.telegram.org/bots/api#callbackquery" rel="nofollow">CallbackQuery</a> – is responsible for inline buttons, it contains data that was put into the buttons during their creation, user data, chat data, and more</li>
<li><a href="https://core.telegram.org/bots/api#inlinequery" rel="nofollow">InlineQuery</a> – comes in inline mode (eg bots like <a href="https://t.me/gif" rel="nofollow">@gif</a> receive them), contains the user&#39;s query, chat type, and other data for managing the output results</li>
<li><a href="https://core.telegram.org/bots/api#chatmemberupdated" rel="nofollow">ChatMemberUpdated</a> – stores data about updates of users/bots in a chat, joining/leaving the chat, changing permissions, and so on</li>
<li><a href="https://core.telegram.org/bots/api#chatjoinrequest" rel="nofollow">ChatJoinRequest</a> – represents a request to join a chat with the corresponding data</li></ul>

<hr>

<h2 id="methods-of-receiving-updates" id="methods-of-receiving-updates">Methods of receiving updates</h2>

<blockquote><p><strong>You can manage a bot without running it</strong>
It is often said «send from under the token» – that is, take the bot&#39;s token and send the necessary request to the Telegram Bot API. To do this, you don&#39;t need to run the update receiving mechanism, but its operation will not interfere in any way</p></blockquote>
<ul><li><strong>🔄 Polling</strong> — a polling mechanism and the simplest way to get updates. The client (that is, our bot on the server) constantly, at small intervals, sends requests to <a href="https://api.telegram.org" rel="nofollow">api.telegram.org</a> to get new updates. This mechanism is very easy to try manually, you can send the corresponding request directly in your browser and see the response from Telegram (<code>https://api.telegram.org/botTOKEN/getUpdates</code>, where <code>TOKEN</code> is your bot&#39;s token)</li>
<li><strong><a href="https://docs.aiogram.dev/en/dev-3.x/dispatcher/long_polling.html" rel="nofollow">⏳ Long-polling</a></strong> — a cooler version of polling. The bot again sends requests to <a href="https://api.telegram.org" rel="nofollow">api.telegram.org</a>, but not constantly. If there are no new updates, Telegram maintains a connection with the bot&#39;s server until new updates appear. When updates appear, Telegram sends the updates to the bot over the previously opened connection. This is some kind of hybrid of polling + an idea from webhook</li>
<li><strong><a href="https://docs.aiogram.dev/en/dev-3.x/dispatcher/webhook.html" rel="nofollow">🌐 Webhook</a></strong> — a method of receiving updates in which we do not need to poll the API at all. The key point is to inform Telegram where we want it to send us updates. Thus, our bot just waits for new events. However, this method of receiving updates requires additional configuration</li></ul>

<blockquote><p><strong>Which update receiving mechanism should be chosen?</strong>
Long-polling or Webhook — that is the question. To answer it, one should analyze the load on the bot, the need for its scaling. If in the future the bot will be popular only locally, then polling will be enough. A webhook will make sense, for example, for organizing a high-load platform or for a quality multibot. But the method of receiving updates can always be changed!</p></blockquote>

<hr>

<h2 id="processing-of-updates" id="processing-of-updates">Processing of updates</h2>

<h3 id="the-full-path-of-update-processing-includes-the-following-stages" id="the-full-path-of-update-processing-includes-the-following-stages">The full path of update processing includes the following stages</h3>
<ul><li>The server with the bot receives an update</li>
<li>The bot eats the update (<a href="https://github.com/aiogram/aiogram/blob/dev-3.x/aiogram/dispatcher/dispatcher.py" rel="nofollow">feed methods</a>, which pass the update directly to the bot)</li></ul>

<h3 id="then-the-update-goes-through-the-so-called-chain-of-processors" id="then-the-update-goes-through-the-so-called-chain-of-processors">Then the update goes through the so-called chain of processors</h3>
<ul><li>Passing through outer middlewares</li>
<li>The update running through the hierarchy of routers</li>
<li>Filters for the update (custom and built-in)</li>
<li>It gets into inner middlewares</li>
<li>Event processing in a handler</li>
<li>Post-processing</li></ul>

<blockquote><p><strong>Knowledge vs. trial and error</strong>
Not every person who uses aiogram understands how an update passes through all these objects. It is important to be able to see the big picture. Then you will be able to quickly understand where and how to implement this or that bot logic!</p></blockquote>

<hr>

<h2 id="router-and-dispatcher" id="router-and-dispatcher">Router and dispatcher</h2>

<p>We received an update, what&#39;s next? Obviously, the bot should respond to some things and not to others — that is, filter the events that are relevant to it. Here, registrations and processors come into play — they are the foundation of the entire filtering system</p>
<ul><li><strong>📌 Registration</strong> can be understood as setting a processor for the specified update type in a certain order (priority) relative to other processors</li>
<li><strong>🌀 Processors</strong> can be handlers, filters, and middlewares that somehow interact with the update (more on each of them a little later). The point is that when aiogram receives an update, it will try to «catch it with processors», and in the order of their registration. First, the framework will try to give the update to those processors that were registered earlier, and only then (if it did not fit any of them) to others</li></ul>

<blockquote><p><strong>Registrations are important</strong>
One of the most common mistakes among beginners is precisely the misunderstanding of the nature of registrations. Keep this in mind — and you will have fewer thoughts about why this update first goes there and not here</p></blockquote>

<p>Now let&#39;s move on to the organization of catching updates, namely to routers. Perhaps you have only used aiogram 2.X and routers scare you, it&#39;s not clear why they are needed. Then I will try to change your mind!</p>
<ul><li><strong><a href="https://docs.aiogram.dev/en/dev-3.x/dispatcher/router.html" rel="nofollow">📡 Router</a></strong> — is an object that represents a branch of registered processors, it can also have a name</li>
<li><strong><a href="https://docs.aiogram.dev/en/dev-3.x/dispatcher/dispatcher.html" rel="nofollow">🎩 Dispatcher</a></strong> — the root (main) <strong>router</strong> that contains all the nested routers of the bot, stores the storage and FSM strategy, and is responsible for starting the bot (or feeding it updates). You can also register processors with it</li></ul>

<p>Purely theoretically, one dispatcher as a router would be enough to handle all events. But using routers has its advantages</p>
<ol><li><p>Modularity:</p>
<ul><li>Each router is responsible for a specific functionality of the bot</li>
<li>This improves code organization and makes it more structured</li>
<li>Examples:
<ul><li>The <code>admin</code> router contains handlers only for administrators</li>
<li>The <code>user</code> router processes commands from regular users</li>
<li>The <code>payment</code> router is responsible for all payment-related operations</li></ul></li>
<li>This approach simplifies code navigation and maintenance</li></ul></li>

<li><p>Registration:</p>
<ul><li>Instead of registering each processor in the dispatcher, you can manage entire branches of processors</li>
<li>This makes it easier to set the priority of update processing</li>
<li>Example:
<ul><li>First, updates from the <code>user</code> router are processed</li>
<li>Then, if the update is not processed, it goes to the <code>admin</code> router</li></ul></li>
<li>Important: the order of registration of processors within each router also matters</li></ul></li>

<li><p>Flexibility:</p>
<ul><li>You can easily enable or disable entire groups of functionality</li>
<li>This is useful for:
<ul><li>Testing new features without affecting the main code</li>
<li>Implementing «red buttons» to quickly disable certain functions</li>
<li>Creating multibots where different sets of functions can be enabled depending on the context</li></ul></li>
<li>Example: you can temporarily disable the <code>payment</code> router for maintenance without affecting other bot functions</li></ul></li>

<li><p>Privacy:</p>
<ul><li>Each router can have its own filters and middlewares</li>
<li>This allows:
<ul><li>Setting access rights checks once for the entire set of handlers</li>
<li>Logging actions within a specific functionality</li></ul></li>
<li>Examples:
<ul><li>The <code>admin</code> router can have a filter that checks for administrative rights for all its handlers</li>
<li>The <code>payment</code> router can log all financial transactions without affecting other parts of the bot</li></ul></li></ul></li>

<li><p>Hierarchy:</p>
<ul><li>Routers can contain other routers, creating a tree-like structure</li>
<li>This allows:
<ul><li>Implementing complex update processing systems</li>
<li>Creating flexible access rights systems</li></ul></li>
<li>Example:
<ul><li>The main <code>user</code> router can contain sub-routers <code>beginner</code>, <code>advanced</code>, <code>premium</code></li>
<li>Each sub-router has its own set of commands and functions available for the corresponding user level</li></ul></li></ul></li></ol>

<blockquote><p><strong>None of these advantages exist with a single dispatcher</strong>
But don&#39;t think badly of the dispatcher. It serves more general purposes. The text above is aimed at convincing you not to use only one dispatcher in your bot (unless, of course, it&#39;s a primitive functionality). P.S. Switch to aiogram 3.X</p></blockquote>

<p>How an update propagates through routers, their filters, and handlers (middlewares are not shown)</p>

<hr>

<h2 id="middleware" id="middleware">Middleware</h2>

<p>🔐 A <strong><a href="https://docs.aiogram.dev/en/dev-3.x/dispatcher/middlewares.html" rel="nofollow">Middleware</a></strong> — is an object that acts as an intermediary link between processors and works with an update at the level of its type <strong>[1]</strong>. It has two types of registration <strong>[2]</strong>. Its use comes down to calling or not calling the next processor in the chain in the right place (according to the bot&#39;s logic) <strong>[3]</strong>. Middleware provides the opportunity for pre, «inner», post, and outgoing processing of updates <strong>[4]</strong>. They can be implemented as classes or <a href="https://docs.aiogram.dev/en/dev-3.x/dispatcher/middlewares.html#function-based" rel="nofollow">functions</a> (the first option is more common)</p>

<p><strong>[1]</strong> Middleware registration is only possible at the update type level, so the content of the internal data of the update does not play any role for it (but no one said that during processing, middleware cannot act as a filter for specific data in the update)</p>

<p><strong>[2]</strong> Let&#39;s deal with the two types of registrations, for this, let&#39;s look at the figure below (<a href="https://docs.aiogram.dev/en/dev-3.x/_images/basics_middleware.png" rel="nofollow">original</a>)</p>

<p><img src="https://docs.aiogram.dev/en/dev-3.x/_images/basics_middleware.png" alt="Organization of middleware"></p>

<p>As you can see, there are outer middleware and middleware, in simple terms — outer and inner. It would be correct to think that they have different registration priorities for an update</p>

<h3 id="what-is-the-difference-between-these-registrations" id="what-is-the-difference-between-these-registrations"><strong>What is the difference between these registrations?</strong></h3>
<ul><li>If there are 2 middlewares, one registered as outer, and the other as inner (for the same type), then a newly-arrived update will first meet the outer one, and only then the inner one</li>
<li>The outer middleware will be called for every update with the registered type, which cannot be said for inner registration</li>
<li>In the outer middleware, when processing an update, it is not known which handler it will potentially go to, that is, everything is at the update type level. But a middleware registered internally will have access to the update only after it has passed through all the previous processors, including filters. This will mean that the bot must have some specific handlers to react to this update</li></ul>

<blockquote><p><strong>Registration for any update</strong>
A middleware can be registered in such a way that any updates (of the update type) will come to it. The type of registration does not affect this in any way. Formally, outer and inner registration for the update type are different things, but in fact, they will work the same. P.S. It&#39;s better to use outer for this</p></blockquote>

<p><strong>[3]</strong> About calling the next processor in the chain. For example, let&#39;s look at this middleware code (<a href="https://docs.aiogram.dev/en/dev-3.x/dispatcher/middlewares.html#class-based" rel="nofollow">original</a>)</p>

<pre><code class="language-python">from aiogram import BaseMiddleware
from aiogram.types import Message


class CounterMiddleware(BaseMiddleware):
    def __init__(self) -&gt; None:
        self.counter = 0

    async def __call__(
        self,
        handler: Callable[[Message, Dict[str, Any]], Awaitable[Any]],
        event: Message,
        data: Dict[str, Any]
    ) -&gt; Any:
        self.counter += 1
        data[&#39;counter&#39;] = self.counter
        return await handler(event, data)
</code></pre>

<p>Special attention should be paid to the <code>await handler(event, data)</code> call. This action passes the update further down the chain of processors. If there is a case in the middleware (for example, under certain conditions) where <code>await handler(event, data)</code> is not called, it will mean that the update is discarded/dropped. That is, none of the processors below will get access to the update as if it never existed. Therefore, we can say that in general, the job of a middleware is to pass data from the update somewhere and/or drop it under certain filtering conditions</p>

<h3 id="4-about-the-types-of-processing" id="4-about-the-types-of-processing"><strong>[4]</strong> About the types of processing</h3>
<ul><li>Pre-processing (outer registration) is useful in cases where a minimum of information is sufficient for some actions and they need to be done as early as possible</li>
<li>«Inner» processing (author&#39;s name) occurs in inner middlewares. Knowing that the update has passed all the filters and is potentially going to a handler (where the bot reacts to a message from the user), it can be processed accordingly</li>
<li>Post-processing involves some actions in the middleware after calling the next processor in the chain and can be present in both outer and inner registrations. This is useful if the processing in the middleware can be done after the update has completed its path down the chain of processors below. Thus, the update will not be delayed for an optional action that can be done after it has passed the entire chain</li>
<li>Outgoing processing using middlewares is a slightly separate topic that not many people know about. The essence is to process the actions of the bot itself (the <code>BaseRequestMiddleware</code> class)</li></ul>

<h3 id="dependency-injection" id="dependency-injection">Dependency Injection</h3>

<p>In aiogram, the <a href="https://docs.aiogram.dev/en/dev-3.x/dispatcher/dependency_injection.html" rel="nofollow">Dependency Injection</a> mechanism allows processors to receive dependencies (data necessary for the call) from other objects that have them or received them in the same way before. These dependencies can be obtained in different ways depending on the type of processor:</p>
<ul><li><strong>Middleware</strong> — receives dependencies through the <code>data</code> argument — a dictionary from which the required data can be extracted by key</li>
<li><strong>Filters and handlers</strong> — dependencies are passed as optional keyword arguments. To access them, you need to specify the required keys directly in the function parameters</li>
<li><strong>Filters in handlers</strong> — can use <code>MagicData</code> or the <code>.as()</code> method to receive dependencies
These different methods allow for flexible management of dependencies and ensure efficient access to data in different parts of the application</li></ul>

<h3 id="delegator-middleware" id="delegator-middleware">Delegator middleware</h3>

<p>This is an author&#39;s name from the word delegate (to pass on), which describes middlewares that pass data to processors using <a href="https://docs.aiogram.dev/en/dev-3.x/dispatcher/dependency_injection.html" rel="nofollow">Dependency Injection</a>. Perhaps, the delegator middleware is mainly used to obtain a database session so that the processors below have a convenient interface for querying it. Also, data processed in a certain way that depends on the update can be passed (eg something filtered from the update data or data on the bot&#39;s side related to the user). To use this mechanism in the context of middlewares, you need to add data through the <code>data</code> argument. This is a dictionary whose data is passed to the processors below</p>

<hr>

<h2 id="filter" id="filter">Filter</h2>

<p>🕸 A <strong><a href="https://docs.aiogram.dev/en/dev-3.x/dispatcher/filters/index.html" rel="nofollow">Filter</a></strong> — is probably the widest range of processors. However, they are divided into two main types: custom and out-of-the-box. Here we will discuss custom ones, and we will discuss the rest later. A filter can be:</p>
<ul><li>an asynchronous function</li>
<li>a synchronous function</li>
<li>an anonymous function</li>
<li>any awaitable object</li>
<li>a subclass of <code>aiogram.filters.base.Filter</code></li>
<li>an instance of <code>MagicFilter</code></li></ul>

<p>When called, filters must return a boolean or a dictionary, which determines whether the update has passed the filtration (it only fails on <code>False</code>)</p>

<p>Unlike middleware, the task of a filter is simply to filter the update. But sometimes, the filtering logic can be more complex than a simple comparison. For example, it makes sense to write a custom filter if you need to check whether the update belongs to a group admin</p>

<p>An example of a simple filter that compares the message text with a given one (<a href="https://docs.aiogram.dev/en/dev-3.x/dispatcher/filters/index.html#own-filter-example" rel="nofollow">original</a>)</p>

<pre><code class="language-python">from aiogram import Router
from aiogram.filters import Filter
from aiogram.types import Message

router = Router()


class MyFilter(Filter):
    def __init__(self, my_text: str) -&gt; None:
        self.my_text = my_text

    async def __call__(self, message: Message) -&gt; bool:
        return message.text == self.my_text


@router.message(MyFilter(&#34;hello&#34;))
async def my_handler(message: Message): 
    ...
</code></pre>

<h3 id="combinations-of-filters" id="combinations-of-filters">Combinations of filters</h3>

<p>If several filters are set during registration, logical <strong>AND</strong> will be used (the update must pass all of them). It is also allowed to have several registrations for one processor, in which case logical <strong>OR</strong> will be used (it is enough for the update to pass the filters of at least one registration). You can also use the magic filter syntax for combinations</p>

<h3 id="delegator-filter" id="delegator-filter">Delegator filter</h3>

<p>Yes, they can pass data using <a href="https://docs.aiogram.dev/en/dev-3.x/dispatcher/dependency_injection.html" rel="nofollow">Dependency Injection</a>. This is possible if the filter returns a dictionary. Then its data can be obtained in the following processors. This is very convenient for obtaining certain data from an update</p>

<hr>

<h2 id="handler" id="handler">Handler</h2>

<p>🦾 A <strong>Handler</strong> — is a function registered to process updates. It is the final processor (the update goes no further) in which it is customary to interact with the user. The function should not return anything and can be registered both with and without filters. But at the same time, a handler can be implemented as a <a href="https://docs.aiogram.dev/en/dev-3.x/dispatcher/class_based_handlers/index.html" rel="nofollow">subclass</a></p>

<blockquote><p><strong>Ghost handler</strong>
A common cause of errors for beginners is a handler without filters. All updates for which it is registered will go into such a handler. Therefore, if you understand that an update is going somewhere it shouldn&#39;t, remember if you have such a handler. In general, its use is not bad practice, you just need to work with it carefully</p></blockquote>

<p>Not much can be said about the handler itself, but we can talk about the filters that work with it. We discussed custom filters above, now it&#39;s time for the built-in ones</p>

<h3 id="built-in-filters-for-handlers" id="built-in-filters-for-handlers">Built-in filters for handlers</h3>
<ul><li><a href="https://docs.aiogram.dev/en/latest/dispatcher/filters/command.html" rel="nofollow">Command</a> – a convenient filter for catching commands, especially if you need to get the command arguments (eg get <code>123</code> from the command <code>/start 123</code>)</li>
<li><a href="https://docs.aiogram.dev/en/latest/dispatcher/filters/chat_member_updated.html" rel="nofollow">ChatMemberUpdated</a> – a flexible filter for updates about user updates in chats (eg you can filter only new users)</li>
<li><a href="https://docs.aiogram.dev/en/latest/dispatcher/filters/magic_filters.html" rel="nofollow">Magic filters</a> – lambda-like filters for comparing update data (as main filters, eg filtering text <code>F.text == &#34;hello&#34;</code>)</li>
<li><a href="https://docs.aiogram.dev/en/latest/dispatcher/filters/magic_data.html" rel="nofollow">MagicData</a> – allows you to compare update data with data passed through Dependency Injection</li>
<li><a href="https://docs.aiogram.dev/en/latest/dispatcher/filters/callback_data.html" rel="nofollow">Callback Data Factory</a> – a filter construction tool that suggests using data embedded in an inline button</li>
<li><a href="https://docs.aiogram.dev/en/latest/dispatcher/filters/exception.html" rel="nofollow">Exceptions</a> – filters for catching errors in processors</li></ul>
]]></content:encoded>
      <author>devogram</author>
      <guid>https://grdly.com/read/a/qb4lnkpvzt</guid>
      <pubDate>Mon, 13 Oct 2025 07:22:04 +0000</pubDate>
    </item>
  </channel>
</rss>