RE:NODE

Telegram webhook nefunguje: jak číst getWebhookInfo

Text: Tým RE:NODE
15 min čtení


Když Telegram bot na webhooku přestane dostávat updaty, odpověď je skoro vždy v jednom volání API: getWebhookInfo. Řekne ti, na jakou URL se Telegram snaží doručovat, kterou IP adresu přeložil, kolik updatů čeká ve frontě a hlavně - poslední chybu, kterou tvůj server vrátil, citovanou přímo čitelným textem. Přečti si url, pending_update_count a last_error_message dohromady a poznáš, která ze čtyř věcí je špatně: registrace, TLS, HTTP odpověď, nebo rychlost tvého handleru. Oprav tu jednu věc a čekající updaty se vyprázdní samy.

Tento článek je ta řešící část tématu. Volba mezi pollingem a webhooky, nastavení BotFather, kód knihoven a nasazení jsou v článku Hosting Telegram bota: polling a webhooky; tady už je webhook nastavený a něco nedorazí.

Začni u getWebhookInfo#

Nemá žádné parametry a funguje z jakéhokoli počítače, který zná token, takže ho můžeš spustit z notebooku, zatímco bot běží jinde:

bash
$ curl -s "https://api.telegram.org/bot$BOT_TOKEN/getWebhookInfo"

Rozbitý webhook vypadá nějak takto (zformátováno pro čtení):

json
{  "ok": true,  "result": {    "url": "https://bot.example.com/tg/hook",    "has_custom_certificate": false,    "pending_update_count": 37,    "ip_address": "203.0.113.10",    "last_error_date": 1760511600,    "last_error_message": "Wrong response from the webhook: 404 Not Found",    "max_connections": 40,    "allowed_updates": ["message", "callback_query"]  }}

Pole tak, jak je definuje Bot API:

PoleCo ti říká
urlKam Telegram doručuje. Prázdné znamená žádný webhook - bot je v režimu pollingu
has_custom_certificatetrue, pokud jsi přes setWebhook nahrál certifikát
pending_update_countUpdaty čekající na doručení
ip_addressAdresa, na kterou Telegram skutečně posílá
last_error_dateUnixový čas posledního neúspěšného doručení
last_error_messageČitelný text této chyby
last_synchronization_error_dateChyba synchronizace uvnitř Telegramu, ne na tvé straně
max_connectionsPovolený počet souběžných doručení, 1-100
allowed_updatesTypy updatů, které bot odebírá

Volitelná pole prostě zmizí, když nemají co říct, takže zdravý webhook neukazuje žádné klíče last_error_*, nebo jen staré datum. last_error_date jsou sekundy od epochy; date -d @1760511600 na Linuxu z toho udělá čitelný čas a prvním krokem je porovnat ho s aktuálním časem. Chyba před třemi dny při pending_update_count nula je historie, ne problém.

Pole čti dohromady#

Žádné jednotlivé pole nic nediagnostikuje. Dělají to až kombinace:

Co vidíšCo to znamená
url je prázdnéNení registrovaný žádný webhook. Něco zavolalo deleteWebhook, nebo ho smazal poller
url je špatná nebo staráZastaralá registrace: starý hostname, stará cesta, staging URL
Počet roste, chyba je čerstváTelegram nemůže doručit a zkouší to znovu
Počet roste, žádná čerstvá chybaDoručení uspěje, ale tvůj handler je příliš pomalý
Počet nula, žádná chyba, žádné odpovědiUpdaty dorazí; chyba je v tvém kódu nebo v allowed_updates

První řádek zachytí víc lidí, než by měl. Několik knihoven volá deleteWebhook, když je spustíš v režimu pollingu, takže jedno spuštění bota lokálně s produkčním tokenem "jen něco vyzkoušet" tiše odstraní produkční webhook. Webhook se nevrátí, dokud něco znovu nezavolá setWebhook. Pokud tvůj deploy registruje webhook jen při změně URL, nemusí se sám nikdy vrátit.

Poslední řádek je ten, kde ti getWebhookInfo nepomůže, protože z pohledu Telegramu je všechno v pořádku: update poslal, tvůj server odpověděl 200, hotovo. Nejdřív zkontroluj allowed_updates - pokud obsahuje jen message, stisky tlačítek (callback_query) nikdy nedorazí a updaty chat_member se posílají jen tehdy, když si o ně výslovně řekneš. Pak zkontroluj logy; výjimka spolknutá po odeslání 200 vypadá přesně jako ticho.

Na ip_address se vyplatí podívat pokaždé. Je to adresa, kterou Telegram přeložil z tvého hostname, a pokud to není server, o kterém si myslíš, že to je, máš problém s DNS: A záznam stále ukazuje na starý hostitel, nebo záznam ještě nedoběhl propagaci. Doručování webhooků Telegramu je jen přes IPv4 - návod k webhookům výslovně říká, že IPv6 není podporováno - takže hostname jen se záznamem AAAA webhooky nemůže přijímat vůbec. Jak zjistit, jaké záznamy jméno doopravdy má, popisuje článek DNS záznamy vysvětlené.

Chyby SSL#

Každá last_error_message začínající SSL error znamená, že Telegram se dostal na tvou adresu a TLS handshake odmítl. Text obvykle obsahuje řetězec chyby z OpenSSL, třeba certificate verify failed, a přesné znění se měnilo spolu s verzí OpenSSL v Telegramu, takže se řiď významem, ne kódem.

Zveřejněné požadavky Telegramu jsou krátké a přísné:

  • TLS 1.2 nebo novější. SSLv2, SSLv3, TLS 1.0 a 1.1 jsou odmítnuty.
  • CN nebo některý SAN certifikátu musí odpovídat hostname v URL webhooku.
  • Server musí poslat celý řetězec - tvůj certifikát plus všechny intermediate.
  • Self-signed certifikát funguje jen tehdy, když ho nahraješ parametrem certificate v setWebhook, načež has_custom_certificate ukáže true.
  • TLS je vyžadováno na každém povoleném portu, včetně 80. Port 80 je povolený, prosté HTTP ne.

Chybějící intermediate je klasika. Prohlížeče to často zamaskují, protože si intermediate cachují nebo si je samy stáhnou, takže web v Chrome vypadá v pořádku, zatímco Telegram, který nedělá ani jedno, ho odmítne. Otestuj to tak, jak by to udělal přísný klient:

bash
$ openssl s_client -connect bot.example.com:443 -servername bot.example.com </dev/null \    | grep -E "Verify return code|subject=|issuer="

Verify return code: 0 (ok) je to, co chceš vidět. Kód 21 (unable to verify the first certificate) je chybějící řetězec: v nginxu to znamená, že ssl_certificate ukazuje na samotný certifikát místo souboru s celým řetězcem. Certbot zapisuje oba a použít máš fullchain.pem. Prošlý certifikát dává kód 10 a neshoda hostname je vidět na řádku subject=. Každý z těchto případů podrobně rozebírá článek Chyby SSL certifikátů vysvětlené.

Jedna méně zjevná příčina: self-signed certifikát nahraný před měsíci, později nahrazený skutečným bez nové registrace. Telegram si stále drží ten starý. Zavolej setWebhook znovu bez parametru certificate a has_custom_certificate se vrátí na false.

Wrong response from the webhook#

Wrong response from the webhook: <status> znamená, že TLS handshake proběhl a tvůj server odpověděl, ale ne kódem 2xx. Telegram bere cokoli mimo 2xx jako selhání a zkouší to znovu. Vodítkem je status ve zprávě:

StatusObvyklá příčina
404 Not FoundNeshoda cesty mezi registrovanou URL a tvou route
401 / 403Tvá kontrola secret tokenu odmítá Telegram (viz níže)
405 Method Not AllowedRoute je registrovaná pro GET; Telegram posílá jen POST
301 / 302 / 308Přesměrování, které Telegram bere jako selhání
500 Internal Server ErrorTvůj handler spadl dřív, než odpověděl
502 / 503 / 504Proxy běží, aplikace za ní ne

404 je téměř vždy koncové lomítko, prefix, nebo cesta s tokenem, která už nesedí, protože token byl zneplatněn. Porovnej url z getWebhookInfo znak po znaku s route ve svém kódu. Knihovny, které route skládají z volby url_path a URL z oddělené volby webhook_url, usnadňují změnit jedno a druhé ne.

Přesměrování pochází z vrstvy před tvou aplikací: přesměrování z holé domény na www nebo naopak, proxy, která posílá jeden prefix cesty jinam, nebo framework, který přidává koncové lomítko. Zaregistruj přesnou finální URL, tu, která odpovídá bez přesměrování.

Skupina 5xx se dělí čistě. 500 přišla z tvého kódu, takže stack trace je v logu aplikace. 502 přišla z reverzní proxy, což znamená, že aplikace neběží, spadla při startu, nebo poslouchá jinde, než se proxy dívá. Nejčastější podobou je aplikace navázaná na 127.0.0.1 uvnitř kontejneru, zatímco proxy se připojuje zvenčí, nebo aplikace poslouchající na natvrdo zadaném portu, který není tím, který tarif přidělil. Proč se proxy a aplikace mohou neshodnout v tom, kde aplikace žije, vysvětluje článek Co reverzní proxy doopravdy dělá.

Další zdroj chyb 500, který vypadá jako problém Telegramu: handler, který spadne na jednom konkrétním updatu. Telegram ho zkusí znovu, handler znovu spadne, a protože ten update stále selhává, fronta za ním roste. Jediná poškozená zpráva dokáže zastavit bota, dokud se ten update nezahodí. Obal zpracování updatů tak, aby se výjimka zalogovala a požadavek přesto dostal své 200.

Timeouty a chyby spojení#

Když zpráva mluví o spojení, ne o statusu, Telegram nedostal žádnou HTTP odpověď vůbec. Znění se liší, ale rodiny jsou tyto:

  • Connection refused - adresa se přeložila a něco odpovědělo, že na tom portu nikdo neposlouchá. Proxy neběží, nebo port v URL není otevřený.
  • Connection timed out - pakety nikam nedošly. Firewall zahazuje provoz, špatná IP, nebo domácí router bez přesměrování portu.
  • Read timeout expired - spojení se otevřelo a tvůj server odpovídal příliš dlouho. To je pomalý handler, ne problém sítě.
  • Failed to resolve host - hostname nemá A záznam, který by Telegram mohl použít.

Telegram nezveřejňuje svůj timeout doručení, takže nenavrhuj nic na konkrétní číslo. Navrhni to tak, aby odpověď trvala milisekundy bez ohledu na okolnosti.

Pokud omezuješ příchozí provoz podle zdrojové adresy, návod k webhookům Telegramu uvádí rozsahy, ze kterých doručuje: 149.154.160.0/20 a 91.108.4.0/22. Stejná stránka říká, že se mohou změnit, takže allow-list z nich postavený je něco, co zkontroluj znovu, když doručování najednou začne končit timeouty. Většině lidí poslouží lépe secret token než IP allow-list. Kompromis popisuje článek Pravidla firewallu, na kterých záleží.

Porty a chyby setWebhook#

Webhooky se doručují přesně na čtyři porty: 443, 80, 88 a 8443. Ne 3000, ne 5000, ne 8080. setWebhook odmítne každý jiný port chybou bad webhook už při registraci, takže pokud vidíš tuto chybu, k selhání doručení ses ještě nedostal. Další odmítnutí při registraci: URL, která není HTTPS, a hostname, který se nepřekládá.

Port v URL je port, ke kterému se Telegram připojuje, ne port, na kterém poslouchá tvá aplikace. Běžné uspořádání je reverzní proxy, která ukončuje TLS na 443 a přeposílá na vlastní port aplikace:

Failed to resolve hostSSL error, refused, timeout502, když aplikace neběží200 do pár milisekundTelegram149.154.160.0/20DNS dotazA záznam, IPv4Reverse proxyTLS na 443Bot aplikace0.0.0.0, port tarifu
Kde může doručení webhooku selhat

Každá šipka je jiná rodina last_error_message, a proto toto pole hledání tak rychle zužuje. V tarifech aplikací RE:NODE je toto uspořádání slot proxy: nasměruj A záznam na zobrazenou adresu a certifikát se vydá a obnovuje automaticky v 21denním okně, s TLS na 443 a požadavkem přeposlaným na port, na kterém tvá aplikace poslouchá. Aplikace stále musí poslouchat na 0.0.0.0 a na portu tarifu; tuhle část máš na starosti ty. Provoz vlastní proxy na vlastním stroji popisuje průvodce reverzní proxy v nginxu.

Parametr ip_address v setWebhook ti umožní dát Telegramu pevnou IP, na kterou má posílat, místo překladu hostname. TLS se přesto kontroluje proti hostname. Hodí se, když je DNS v přechodu a nemůžeš čekat, a je to past, když zapomeneš, že byl nastaven, a později bota přesuneš - nový A záznam se pak ignoruje. getWebhookInfo ukazuje, která adresa se používá.

Fronta updatů a drop_pending_updates#

Dokud doručování selhává, updaty se řadí do fronty. Telegram drží nedoručené updaty až 24 hodin, zkouší doručení opakovat, a jakmile odstraníš příčinu, fronta se doručí najednou - omezená parametrem max_connections, který má výchozí hodnotu 40 souběžných požadavků. To má dva účinky, které lidé nečekají. Bot, který byl přes noc rozbitý, odpoví na celou noc zpráv během pár sekund po opravě, a to na konverzace, které skončily před hodinami. A na malém serveru může 40 souběžných doručení stačit k zátěži, která způsobí druhé kolo timeoutů.

Když je fronta bezcenná, zahoď ji. setWebhook i deleteWebhook berou drop_pending_updates:

bash
$ curl -s "https://api.telegram.org/bot$BOT_TOKEN/setWebhook" \    -d "url=https://bot.example.com/tg/hook" \    -d "secret_token=$WEBHOOK_SECRET" \    -d "max_connections=10" \    -d "drop_pending_updates=true"

pending_update_count by měl hned poté ukazovat 0. Pozor, setWebhook nahrazuje celou konfiguraci: když při nové registraci vynecháš secret_token nebo allowed_updates, jsou pryč. Vždy předej celou sadu, kterou chceš.

Frontu nezahazuj ze zvyku. U bota, který zaznamenává objednávky, platby nebo odeslané formuláře, jsou ty čekající updaty skutečná práce, a správný krok je opravit chybu, snížit max_connections na hodnotu, kterou server zvládne, a nechat je vyprázdnit. Pokles počtu sleduj opakovanými voláními getWebhookInfo.

Hlavička secret_token#

secret_token je řetězec, který si zvolíš, 1-256 znaků z A-Z, a-z, 0-9, _ a -, předaný do setWebhook. Telegram ho pak posílá v hlavičce X-Telegram-Bot-Api-Secret-Token při každém doručení a tvůj handler ho porovná a odmítne vše, co nesouhlasí. Je to levný a správný způsob, jak zajistit, že bota může krmit updaty jen Telegram.

Vytváří také konkrétní selhání. Tvůj handler vrátí 401 nebo 403, last_error_message ukazuje Wrong response from the webhook: 403 Forbidden a příčinou je jedna ze tří věcí:

  1. Secret byl v tvém prostředí změněn, ale setWebhook nebyl zavolán s novou hodnotou.
  2. Nová registrace vynechala secret_token, takže Telegram teď neposílá žádnou hlavičku.
  3. Proxy mezi tím odstraňuje neznámé hlavičky. Neobvyklé, ale stojí za kontrolu, když jsou první dvě vyloučené.

Oprava je u všech tří stejná: nastav proměnnou, zavolej setWebhook se stejnou hodnotou a ověř, že se prostředí a Telegram shodují. Secret není v getWebhookInfo vidět, takže jediným důkazem je úspěšné doručení. Drž ho vedle tokenu, ne v repozitáři - důvody jsou v článku proměnné prostředí a secrets.

Odpověz rychle, zpracuj potom#

Pokud pending_update_count stoupá bez chyby, nebo vidíš read timeouty, tvůj handler dělá svou práci dřív, než odpoví. Každé doručení drží jeden ze slotů max_connections, dokud neodpovíš. Handler, který zavolá externí API, zapíše do databáze a pošle dvě zprávy, než vrátí 200, může trvat dvě sekundy; při dávce zpráv se sloty zaplní, doručení se řadí, a nakonec některá vyprší a zkusí se znovu - což zátěž ještě zvyšuje.

Oprava je strukturální: ověř secret, předej update něčemu jinému, odpověz 200 a práci udělej potom.

FastAPI, nejdřív odpověď
import asyncio, osfrom fastapi import FastAPI, Request, Responseapp = FastAPI()SECRET = os.environ["WEBHOOK_SECRET"]queue: asyncio.Queue = asyncio.Queue()@app.post("/tg/hook")async def hook(request: Request):    if request.headers.get("X-Telegram-Bot-Api-Secret-Token") != SECRET:        return Response(status_code=403)    await queue.put(await request.json())    return Response(status_code=200)async def worker():    while True:        update = await queue.get()        try:            await handle(update)          # logika tvého bota        except Exception as exc:            print("update failed:", exc, flush=True)@app.on_event("startup")async def start_worker():    asyncio.create_task(worker())

Fronta v paměti ztratí všechno, co v ní je, když se proces restartuje, což je přijatelné pro chatovacího bota, ale ne pro cokoli, co přijímá platby. Když práce musí restart přežít, dej ji do skutečné fronty; možnosti, které se vejdou na jeden tarif, popisuje článek úlohy na pozadí na malém serveru. Některé knihovny, mimo jiné webhookCallback z grammY, na handler aplikují vlastní timeout a když vyprší, odpoví za tebe - lepší než timeout Telegramu, ale je to znamení, že handler je příliš pomalý, ne řešení.

Také sniž max_connections na hodnotu, kterou server skutečně zvládne paralelně. Na tarifu s 0,5 nebo 1 vCPU není 40 souběžných doručení kapacita, ale souboj o zdroje. Pro malého bota je obvykle poctivá hodnota mezi 5 a 20.

Zpátky na polling#

Někdy je správnou opravou přestat webhook používat. Pokud má bot pár stovek uživatelů, nemáš nazbyt doménu, nebo ladíš a chceš vidět, jak updaty chodí v terminálu, je polling jednodušší a nepřijdeš o nic, na čem záleží.

bash
$ curl -s "https://api.telegram.org/bot$BOT_TOKEN/deleteWebhook" \    -d "drop_pending_updates=false"

S drop_pending_updates=false (výchozí) čekající updaty zůstanou a getUpdates je doručí při dalším dotazu. Potom getWebhookInfo vrací prázdné url a polling funguje. Dokud ho nesmažeš, každé volání getUpdates končí konfliktem 409, což je způsob, jakým lidé zjistí, že zapomenutý webhook je pořád zaregistrovaný. Pamatuj i na opačný směr: návrat k webhooku později vyžaduje novou registraci setWebhook s celou sadou parametrů a deploy by měl poté zkontrolovat getWebhookInfo, ne to předpokládat.

Kontrolní seznam podle pořadí#

Když je pozdě a bot mlčí, projdi to v tomto pořadí:

  1. Spusť getWebhookInfo. Je url nastavená a je to přesně ta URL, kterou tvůj kód obsluhuje?
  2. Je ip_address tvůj server? Pokud ne, oprav DNS, nebo zruš pevně zadanou ip_address.
  3. Přečti last_error_date. Je chyba čerstvá? Pokud ne, problém je v tvém kódu nebo v allowed_updates.
  4. SSL error: zkontroluj řetězec a hostname přes openssl s_client.
  5. Wrong response: status ti řekne, jestli jde o cestu, secret, přesměrování, nebo spadlou aplikaci.
  6. Chyby spojení: ověř, že port je jeden ze čtyř, proxy běží a nic není zablokované firewallem.
  7. Počet roste bez chyby: odpověz nejdřív, zpracuj potom, sniž max_connections.
  8. Opraveno a fronta je zastaralá: zaregistruj znovu s drop_pending_updates=true.

Při práci měj otevřený log aplikace. Oddělit chyby, na kterých záleží, od okolního šumu pomáhá článek Jak číst konzoli serveru bez hádání.

FAQ#

Proč roste pending_update_count, když nejsou žádné chyby?

Telegram doručuje úspěšně, ale tvůj server odpovídá příliš pomalu, aby to stíhal, takže doručení čekají na volný slot spojení. Vrať 200, jakmile je požadavek ověřený, práci udělej v úloze na pozadí a nastav max_connections na hodnotu, kterou tvůj server opravdu zvládne paralelně.

Jak dlouho Telegram drží updaty, které webhook nedostal?

Až 24 hodin. Cokoli staršího se zahodí, ať webhook opravíš včas, nebo ne, takže bot, který byl dva dny mimo provoz, přijde o zprávy prvního dne tak či tak.

Můžu použít webhook na portu 3000 nebo 8080?

Ne přímo. Telegram doručuje jen na 443, 80, 88 a 8443 a setWebhook ostatní porty odmítne. Aplikaci spusť na libovolném portu a před ni dej reverzní proxy na 443.

Následuje Telegram přesměrování na můj webhook?

Počítej s tím, že ne. Odpověď 3xx se zaznamená jako špatná odpověď a doručení se zopakuje. Zaregistruj přesnou finální URL, včetně schématu, hostitele a případného koncového lomítka, které tvůj framework čeká.

Přijdu o zprávy po deleteWebhook?

Ne, pokud nepředáš drop_pending_updates=true. Bez něj čekající updaty zůstanou na straně Telegramu a první volání getUpdates je převezme, v rámci 24hodinového limitu.

Potřebuju IP allow-list, když používám secret_token?

Ne. Secret token už dokazuje, že požadavek přišel od Telegramu, a nerozbije se, když Telegram změní rozsahy adres. Allow-list je další vrstva, ne náhrada, a je to ta, která spíš způsobí výpadek.