Aiogram Webhook

Handles the webhook layer for aiogram bots. Registers the endpoint, calls Telegram setWebhook, verifies incoming requests, and manages engine lifecycle.

What it does

FastAPI & aiohttp

Register a webhook endpoint in your existing app. Only the wiring changes.

Single-bot & multi-bot

SingleBotEngine for one bot. TokenEngine to route many bots by token in the URL.

Typed Route & Security

Build setWebhook URLs with Route. Verify secret tokens and IP ranges with Security.

Install

Requires Python 3.10+, aiogram 3.14+, a bot token from @BotFather, and a public HTTPS URL.

uv add "aiogram-webhook[fastapi]"
uv add "aiogram-webhook[aiohttp]"
pip install "aiogram-webhook[fastapi]"
pip install "aiogram-webhook[aiohttp]"

Omit the extra if you build a custom adapter or use route/security helpers only.

Minimal app

Replace BOT_TOKEN, https://example.com, and webhook-secret. base_url must be the HTTPS origin Telegram can reach.

from contextlib import asynccontextmanager

import uvicorn
from aiogram import Bot, Dispatcher, Router
from aiogram.filters import CommandStart
from aiogram.types import Message
from fastapi import FastAPI

from aiogram_webhook import FastAPIAdapter, SingleBotEngine, WebhookConfig
from aiogram_webhook.route import Route
from aiogram_webhook.security import IPCheck, Security, StaticSecretToken

router = Router()


@router.message(CommandStart())
async def start(message: Message) -> None:
    await message.answer("OK")


dispatcher = Dispatcher()
dispatcher.include_router(router)

bot = Bot("BOT_TOKEN")
engine = SingleBotEngine(
    dispatcher,
    bot,
    web=FastAPIAdapter(),
    route=Route(base_url="https://example.com", path="/telegram/webhook"),
    security=Security(IPCheck(), secret_token=StaticSecretToken("webhook-secret")),
    webhook_config=WebhookConfig(drop_pending_updates=True),
)


@asynccontextmanager
async def lifespan(app: FastAPI):
    await engine.set_webhook()
    yield


app = FastAPI(lifespan=lifespan)
engine.register(app)


if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)
from aiohttp import web
from aiogram import Bot, Dispatcher, Router
from aiogram.filters import CommandStart
from aiogram.types import Message

from aiogram_webhook import AiohttpAdapter, SingleBotEngine, WebhookConfig
from aiogram_webhook.route import Route
from aiogram_webhook.security import IPCheck, Security, StaticSecretToken

router = Router()


@router.message(CommandStart())
async def start(message: Message) -> None:
    await message.answer("OK")


dispatcher = Dispatcher()
dispatcher.include_router(router)

bot = Bot("BOT_TOKEN")
engine = SingleBotEngine(
    dispatcher,
    bot,
    web=AiohttpAdapter(),
    route=Route(base_url="https://example.com", path="/telegram/webhook"),
    security=Security(IPCheck(), secret_token=StaticSecretToken("webhook-secret")),
    webhook_config=WebhookConfig(drop_pending_updates=True),
)


async def set_webhook(app: web.Application) -> None:
    await engine.set_webhook()


app = web.Application()
app.on_startup.append(set_webhook)
engine.register(app)


if __name__ == "__main__":
    web.run_app(app, host="0.0.0.0", port=8000)

What happens at runtime

When a user sends /start:

  1. Telegram POSTs JSON to your public URL.
  2. Security checks the secret token and source IP.
  3. The engine feeds the update to aiogram in the background.
  4. Your handler calls message.answer("OK") through the Bot API.
  5. Telegram gets an empty 200 from the webhook request itself.

Step 4 is a separate HTTP call — normal for background mode.