Telegram Bot Monitoring: Why Your Health Checks Miss Outages

Telegram Bot Monitoring: Why Your Health Checks Miss Outages

Telegram bot monitoring fails in a specific way, because Telegram bot outages share a signature that is unusually hard to detect: the bot is running and receiving nothing.

Almost every real failure produces that state. And the health checks people install — process alive, port listening, HTTP 200 on /health — all pass during it, because the process genuinely is fine. The break is upstream, between Telegram and your handler, in a path no local check inspects.

The result is the classic Telegram outage: two hours down, every dashboard green, discovered when a user complains.

What actually breaks

Certificate expiry. Webhooks require valid HTTPS. When the certificate lapses, Telegram's delivery fails and your server never sees the request. Covered in detail in the certificate requirement — it is one of the most invisible, since nothing on your side changes.

Webhook misconfiguration. A deploy resets the webhook URL, or something calls setWebhook with the wrong address, or deleteWebhook runs and nothing re-registers. Updates go nowhere.

Non-2XX during deploy. Telegram retries: the docs say that on a response "different from 2XY" it "will repeat the request and give up after a reasonable amount of attempts." A rollout returning 502 for a couple of minutes normally loses nothing — the updates queue and arrive once you return 2XX, and you can watch the backlog drain via pending_update_count. Telegram does not document how many attempts "reasonable" is, so a long outage can still lose updates, and nothing is retained beyond 24 hours.

Two pollers. Two instances calling getUpdates for the same bot compete for updates. Symptom: updates handled erratically, and both processes logging HTTP 409 "Conflict: terminated by other getUpdates request; make sure that only one bot instance is running" as each call kicks the other off. Alerting on that 409 is faster and more specific than counting processes. (That error comes from the Bot API server, not the API reference.) This happens most often when a "restart" leaves the old process alive — a watchdog that checks whether the process exists will helpfully start a second one.

Free-tier sleep. The instance suspends; a webhook delivery arrives during cold start and gets a connection error or timeout. Telegram's documented retry behaviour covers responses carrying an HTTP status other than 2XY, so how it treats a refused connection is not documented. The user's reply is late, and may or may not be lost. Repeated sleeps keep pending_update_count elevated, and nothing is retained past 24 hours.

Token revoked. Someone regenerates the token in @BotFather. Every API call fails immediately — one of the few loud failures on this list.

Blocked by users at scale. Not an outage, but it looks like falling engagement. Sends fail while a user has the bot blocked. That is reversible — my_chat_member is delivered in private chats "only when the bot is blocked or unblocked by the user" — so track it in both directions rather than treating a block as terminal.

Flood limits. Sustained 429s during a broadcast. Handled properly it is a delay; handled badly it looks like an outage.

Why the usual checks fail

CheckCatchesMisses
Process aliveCrashEverything above except a crash
Port listeningBind failureCertificate, webhook config, token
/health returns 200App-level crashAnything upstream of your app
CPU / memoryResource exhaustionAll of it

Every one of these measures your side of the connection. Most of the failures above are outside it — in Telegram's delivery path, in your certificate, or in your own deployment topology — and none of them show up as an unhealthy process.

There is a general principle here worth stating plainly: a monitor inside the failure domain cannot report on the failure domain. A health check that runs on the same box, checking the same process, is structurally incapable of seeing "Telegram cannot reach me."

The checks that work

1. Update arrival rate — the single most valuable signal.

Record the timestamp of the last update received. Alert when no updates arrive during hours you normally receive them.

This catches every failure in the list that stops delivery — certificate, webhook config, non-2XX during deploy, free-tier sleep, token. You do not need to know which one it is to know you are down. It does not catch the three that leave inbound traffic intact: flood limits, which is a send-side failure; users blocking the bot; and two pollers splitting updates between them. Those need the API error-rate check, my_chat_member, and the 409 alert respectively.

Tune the threshold to your traffic. A busy bot should alert after 15 minutes of silence; a quiet one needs a longer window and daytime-only logic.

2. getWebhookInfo, every few minutes.

This is Telegram telling you it cannot reach you. It reports the pending update count and the last delivery error. It is the most direct signal available and almost nobody polls it.

A growing pending count means updates are arriving faster than they are being consumed — either Telegram cannot deliver them, or your handler is too slow, or max_connections is too low. Pair it with last_error_date to tell those apart. Note that last_error_date is a timestamp of the most recent error and is not cleared on recovery, so compare it against the current time rather than testing for presence.

3. An end-to-end canary.

A separate account that messages your bot every 15 minutes and verifies a reply. The only check that proves the entire path — Telegram → certificate → proxy → app → response — actually works.

Run it from somewhere other than your bot's infrastructure. A canary on the same host dies with the host.

4. Served certificate expiry.

Daily, via a real TLS handshake against the live endpoint, alerting three weeks out. Not "is the renewal timer enabled" — check what is actually being served.

5. Duplicate instance detection.

If polling, ensure exactly one poller. A process-existence check is not enough — check for more than one, since the failure mode is two, not zero.

An alerting baseline

CheckIntervalAlert
Updates receivedcontinuous0 for 15 min during active hours
getWebhookInfo error5 minlast_error_date within last 10 min (now − last_error_date < 600)
getWebhookInfo pending5 mingrowing over 3 samples
End-to-end canary15 minno reply within 60s
Certificate expirydaily21 / 10 / 3 days
Poller instance count5 min≠ 1
API error ratecontinuoussustained non-429 errors

Where the alert must go

Two rules learned the expensive way.

Alerts must leave the box. A monitor running on the same infrastructure it watches cannot tell you when that infrastructure is gone. Push to a phone, a chat, an external service — anywhere outside the failure domain.

Add an external dead-man's switch. A service that expects a regular ping and alerts when it stops. It covers the case nothing on-box can: the whole machine dying, the monitoring itself failing, the network disappearing.

Everything else tells you when something specific broke. A dead-man's switch tells you when everything broke, which is the case your own monitoring is guaranteed to miss.

The mindset

Stop asking "is my bot running?" — it almost always is.

Ask "is my bot receiving and replying?" That question has a different answer during every outage described here, and it is the only one worth building monitoring around.


API behaviour quoted from Telegram's Bot API documentation, verified September 2026.