Webhook Mode¶
This document describes the current webhook runtime.
Source of truth:
pytmbot/webhook.pypytmbot/pytmbot_instance.pypytmbot/main.pypytmbot/models/telegram_models.py
When To Use It¶
Use webhook mode when you want Telegram to push updates to the bot instead of relying on long polling.
Enable it with:
or the Docker image equivalent.
Required Configuration¶
The following webhook_config fields are required:
urlwebhook_portlocal_port
Optional fields:
trusted_proxy_ipsadditional_telegram_ip_rangescertcert_key
Related CLI fields:
--webhook--socket_host
Startup Behavior¶
At startup the runtime:
- creates a FastAPI application
- generates a random webhook path token
- generates a random Telegram secret token
- registers the webhook with Telegram
- starts Uvicorn locally on
local_port
If webhook startup fails, the launcher falls back to polling mode.
Security Controls¶
Webhook requests are accepted only after all of the following checks pass:
- path token matches the active webhook path
X-Telegram-Bot-Api-Secret-Tokenmatches the active secret (compared in constant time)- source IP is accepted as Telegram traffic
- request is not blocked by the webhook rate limiter
IP validation details:
- built-in Telegram IPv4 and IPv6 ranges are enforced
additional_telegram_ip_rangesextends the allowlist; ranges wider than/16(IPv4) or/32(IPv6) are rejected at startuptrusted_proxy_ipscontrols whether forwarded headers from reverse proxies are trusted;0.0.0.0/0and::/0are rejected at startup- the client address is resolved as described in Client Address Resolution
Client Address Resolution¶
The address checked against the Telegram ranges, and used as the rate-limit key, is resolved for each request:
- Without an
X-Forwarded-Forheader, the connecting peer is the client. - With the header, the request is refused (
403) unlesstrusted_proxy_ipsis set and the connecting peer is one of those proxies. - The header is read from the right. Each proxy appends the address it received the request from, so the right end is written by your proxies and the left end by whoever sent the request. Entries that are trusted proxies are skipped; the first entry that is not a trusted proxy is the client. If every entry is a trusted proxy, the leftmost one is the client.
- Entries left of the client are ignored, even when they are not IP addresses. A malformed entry that has to be read
(the client or a proxy hop right of it) fails the request with
400, as does a header longer than 512 characters. - IPv4-mapped IPv6 addresses (
::ffff:149.154.167.220) are treated as their IPv4 address for the proxy check, the Telegram check and rate limiting. List IPv4 proxies in plain IPv4 form.
Examples with trusted_proxy_ips: ['10.0.0.0/8'] and the request arriving from 10.0.0.5:
X-Forwarded-For |
Client |
|---|---|
149.154.167.220 |
149.154.167.220 |
149.154.167.220, 203.0.113.7 |
203.0.113.7 |
203.0.113.7, 149.154.167.220, 10.0.0.7 |
149.154.167.220 |
10.0.0.7, 10.0.0.8 |
10.0.0.7 |
In the second row the left entry was supplied by the sender and is ignored, so the request is refused.
Every proxy hop between Telegram and the bot must be listed in trusted_proxy_ips. A hop that is not listed is
taken for the client, and the request is refused because that address is not a Telegram address.
Rate Limiting¶
Two webhook rate limiters are used:
- main webhook path:
10requests /10seconds per client address (resolved as above) - unknown paths / 404s:
5requests /10seconds per IP
Ban behavior:
- repeated abuse can trigger an IP ban
- ban TTL is
3600seconds - ban state is persisted under
webhook_ratelimit/<port>/in the runtime state directory outside pytest when that directory is writable
Runtime state directory resolution:
PYTMBOT_STATE_DIRwhen set- otherwise
$XDG_STATE_HOME/pytmbotwhenXDG_STATE_HOMEis set - otherwise
~/.local/state/pytmbot, or/dev/shm/pytmbot-<uid>when that cannot be written (read-only root filesystem)
Credential Rotation¶
Webhook credentials rotate automatically.
Current settings:
- rotation threshold:
10_000processed requests - grace period for previous credentials:
300seconds
During the grace period, old and new credentials are both accepted.
TLS Behavior¶
local_portmust be>= 1024- if both
certandcert_keyare valid files, Uvicorn can serve HTTPS directly - if TLS files are missing or invalid, the server logs a warning and continues without in-process TLS
- reverse-proxy TLS termination is supported and is the recommended deployment model
Reverse Proxy Notes¶
Recommended pattern:
- public HTTPS listener in Nginx / Traefik / Nginx Proxy Manager
- internal pyTMbot webhook listener on
local_port trusted_proxy_ipsset only to proxy addresses you control, one entry (or CIDR) per hop
Nginx on the same host, forwarding to the bot over loopback:
location /webhook/ {
proxy_pass http://127.0.0.1:5001;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
$proxy_add_x_forwarded_for appends Telegram's address to whatever header the sender supplied. The bot reads the
header from the right, so a forged entry on the left is ignored. proxy_set_header X-Forwarded-For $remote_addr;
works as well: it replaces the header with the connecting address.
With several proxies in a row (for example a load balancer in front of nginx), every hop must append to
X-Forwarded-For ($proxy_add_x_forwarded_for in nginx), and every hop must be in trusted_proxy_ips:
webhook_config:
trusted_proxy_ips:
- '127.0.0.1/32' # nginx, connecting to the bot
- '10.0.0.0/24' # load balancers in front of nginx