Telegram Webhooks vs Polling: The Decision, With Failure Modes

Telegram Webhooks vs Polling: The Decision, With Failure Modes

Choosing between a Telegram webhook and polling decides what your infrastructure has to look like — and how your bot will eventually break. Every bot receives updates one of these two ways.

Long polling (getUpdates): your bot repeatedly asks Telegram for updates. Webhooks (setWebhook): Telegram sends an HTTPS POST to your server when something happens.

What webhooks actually require

This is where people underestimate the commitment. From the API documentation:

HTTPS, mandatory. The url parameter is documented as an "HTTPS URL to send updates to." There is no plaintext option.

One of exactly four ports: "Ports currently supported for webhooks: 443, 80, 88, 8443." Not 3000, not 8080. If your platform assigns arbitrary ports, you need a proxy in front.

A publicly reachable address. No localhost, no private network, no machine behind NAT without a tunnel.

A valid certificate — or a self-signed one uploaded via the certificate parameter, in which case Telegram checks against the key you supplied. The docs are specific about how: "Please upload as InputFile, sending a String will not work." Post it as multipart/form-data in PEM (ASCII BASE64) format, and make sure the certificate's CN or SAN matches the domain in your webhook URL — for a self-signed certificate the IP is acceptable as CN.

All of the above describes the hosted API at api.telegram.org. Running your own Local Bot API Server lifts three of these constraints and raises a fourth limit — the docs list "Use an HTTP URL for the webhook.", "Use any local IP address for the webhook.", "Use any port for the webhook." and "Set max_webhook_connections up to 100000." That is why you will occasionally see people claim plaintext webhooks work.

Useful optional parameters most people never set:

What polling requires

A process with outbound internet access — and no webhook set on the token. The two are mutually exclusive: getUpdates "will not work if an outgoing webhook is set up." If you have ever called setWebhook on this token, call deleteWebhook first; getWebhookInfo returns an empty url field when no webhook is set.

getUpdates takes offset, limit (1–100, default 100) and timeout. One note from the docs that matters: timeout "Defaults to 0, i.e. usual short polling. Should be positive, short polling should be used for testing purposes only."

Always set a positive timeout. Leaving it at 0 means hammering Telegram with empty requests — that is short polling, and the documentation explicitly says it is for testing. Use 30 or so for real long polling.

The failure modes

This is the part worth reading twice, because the two approaches fail in completely different ways.

Webhooks fail silently

Certificate expiry. Your TLS certificate lapses. Telegram cannot complete an HTTPS request. Updates stop arriving. Your bot process is running perfectly and reports no error — it is simply never called. Monitoring that checks whether your bot is alive will show green while your bot receives nothing.

This is one of the failure modes we see most often, and it is invisible to every naive health check. Certificates expire on a schedule; if renewal breaks quietly, you find out when a user complains.

Any non-2XX response. The documentation says: "In case of an unsuccessful request (a request with response HTTP status code different from 2XY), we will repeat the request and give up after a reasonable amount of attempts."

"A reasonable amount of attempts" is not defined, and neither is the retry interval — so you cannot compute how long an outage has to last before Telegram stops trying. What the docs do say is that undelivered updates are "stored on the server until the bot receives them either way, but they will not be kept longer than 24 hours", and that getWebhookInfo reports pending_update_count, the "Number of updates awaiting delivery". Whether an update Telegram has stopped retrying is still counted there is not documented — we don't know. Assume a long outage costs you updates; do not assume a brief 502 window does.

Slow responses. If your handler does the work before responding, Telegram waits. Respond 200 immediately, process asynchronously.

Silent misconfiguration. Wrong port, expired DNS, a proxy stripping the path — none of these raise an error in your process; you just see no updates. Telegram does record them: getWebhookInfo returns last_error_date and last_error_message, "Error message in human-readable format for the most recent error that happened when trying to deliver an update via webhook." The failure is invisible only if nobody reads that endpoint.

Polling fails loudly

Your process dies. Obvious: it stops, alerts fire, you restart it. Updates queue at Telegram and arrive when you come back — for up to 24 hours. Retention is not indefinite. The docs say incoming updates "will not be kept longer than 24 hours" — for webhooks and polling alike. An outage longer than a day loses updates whichever transport you run.

Network loss. Your requests fail visibly in your own logs.

Conflicting instances. Two processes polling the same bot fight over updates. This one is genuinely confusing to debug — symptoms are intermittent missed messages — but it shows up as errors rather than silence.

The pattern: polling failures announce themselves; webhook failures are silence that looks like calm.

The comparison

WebhooksLong polling
InfrastructureHTTPS, public, 4 portsOutbound internet only
LatencyLowestSlightly higher
Efficiency at scaleBetterWasteful at high volume
Behind NAT / local devNo (needs a tunnel)Yes
Failure visibilitySilentLoud
Certificate managementRequired, ongoingNone
Lost updates on outagePossibleQueued — but only for 24 hours
Setup complexityHigherTrivial

Choosing

Use polling when:

Use webhooks when:

That last condition is not optional. If you cannot guarantee your certificate will never silently expire, polling is the more reliable choice regardless of what is theoretically more efficient.

If you run webhooks, monitor these

  1. Certificate expiry, with an alert weeks ahead — not at expiry
  2. Update arrival rate — alert when it drops to zero, since zero is what failure looks like
  3. getWebhookInfo — it reports pending update count and last error; poll it periodically
  4. Non-2XX rate on your endpoint
  5. End-to-end: a canary that actually messages the bot and confirms a reply

Numbers two and five are the ones that catch the certificate case, because they measure delivery rather than liveness — and number three names the cause, since last_error_message reports the failure in human-readable form. Number one is the only one that catches it before users do. Detecting bot downtime works through each of these in detail.

The pragmatic recommendation

Start with polling. It is simpler, and it fails visibly: the outage shows up in your own process rather than only in getWebhookInfo. Retention is the same either way — up to 24 hours. It is also what makes free hosting workable while you are still proving the idea. Move to webhooks when volume justifies it and when you have the operational maturity to monitor a silent failure mode — and remember the switch is not additive in either direction: setWebhook stops getUpdates working, and you must call deleteWebhook before going back.

Plenty of production bots serving large audiences run long polling successfully. The efficiency argument for webhooks only becomes compelling at scale — and the reliability argument runs the other way until your monitoring is genuinely good.


Parameters and constraints quoted from Telegram's Bot API documentation, verified September 2026.