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

  1. What is a webhook
  2. Where to start
  3. Getting a domain
  4. Installing a web server
  5. Certificate from Lets Encrypt
  6. Self-signed certificate
  7. 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

Prerequisites

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/20 and 91.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

This article covers the following options


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)

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


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


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

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

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,
)

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

Found a mistake? Feedback: hello@shaonis.space