Bot API reference
Two ways to receive an update, one of them for production
A bot receives updates either by calling `getUpdates` in a loop (long polling) or by registering an HTTPS endpoint with `setWebhook` and having Telegram POST to it. They are mutually exclusive: calling `getUpdates` while a webhook is set returns an error until the webhook is deleted.
Webhooks vs long polling: the exact figures
- Webhook ports
- 443, 80, 88 or 8443, HTTPS only
- Production choice
- Webhook. Long polling is a development tool
- Mutually exclusive
- getUpdates fails while a webhook is registered
- Transport
- HTTPS on port 443, 80, 88 or 8443
- Handler contract
- Respond 200 immediately; process asynchronously
- Diagnostics
- getWebhookInfo reports pending count and last error
As of 2025-10-01, Telegram Bot API 13.4
What it means in practice
Long polling holds a request open until an update arrives or a timeout expires, then immediately reopens it. It needs no public address, no certificate and no inbound firewall rule, which is exactly why it is the right choice on a laptop. Nothing about it scales: every instance polling the same token competes for the same updates, the process must stay alive to receive anything, and a restart leaves a window where updates queue server-side.
A webhook inverts the relationship. Telegram POSTs each update to an HTTPS URL you register once, which means the bot is a request handler rather than a long-lived process — and that is what makes serverless runtimes viable. It requires a valid certificate on port 443, 80, 88 or 8443, a publicly reachable address, and a handler that responds quickly.
That last requirement is the one that catches people. Telegram treats a slow or failing endpoint as unhealthy and will retry, so a handler that performs the actual work before responding produces duplicate processing under load. The correct shape is to acknowledge immediately with 200 and do the work afterwards — on Cloudflare Workers that is `ctx.waitUntil`, on Node an explicit queue. Responding first is not an optimisation; it is what stops one slow database write becoming three deliveries of the same message.
Switching between them is a live operation with a sharp edge. `deleteWebhook` accepts `drop_pending_updates`, and whether to use it is a real decision rather than a default: dropping discards everything queued while the endpoint was down, which is correct after a long outage of a chat bot and wrong for a payment bot where the queue may contain a completed checkout. `getWebhookInfo` reports the pending count and the last error, and it is the first thing to check when a bot has gone quiet.
For production the answer is a webhook, without much nuance. It is cheaper to run, it survives restarts without losing updates, it scales horizontally because Telegram is doing the distribution, and it is a prerequisite for the secret-token validation that stops anyone else posting fabricated updates at your endpoint.
Handling it in code
// Acknowledge first, work afterwards. The order is the whole point.
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
if (request.headers.get('X-Telegram-Bot-Api-Secret-Token') !== env.WEBHOOK_SECRET) {
return new Response('Not found', { status: 404 })
}
const update = await request.json()
// Telegram retries anything slow or failing. Doing the work before responding is
// how one slow write becomes three copies of the same message.
ctx.waitUntil(handleUpdate(update, env))
return new Response('ok')
},
}Questions this raises
Can I run long polling and a webhook at the same time?
No. They are mutually exclusive by design: while a webhook is registered, `getUpdates` returns an error. This is the usual cause of a local bot that suddenly receives nothing — a webhook is still set from a previous deploy, and `deleteWebhook` is the fix.
What happens to updates while my webhook endpoint is down?
Telegram queues them and retries for a period, then discards them. `getWebhookInfo` reports how many are pending and what the last error was, which makes it the first diagnostic to run rather than the last.
Should I use drop_pending_updates when redeploying?
It depends on what the queue might contain, and it is worth deciding deliberately rather than copying a snippet. Dropping is right for a conversational bot after a long outage, where replaying an hour of stale messages confuses people. It is wrong for a payment or booking bot, where a pending update may be the only record that a checkout completed.
Is a self-signed certificate acceptable?
Telegram supports uploading a self-signed certificate with setWebhook, but there is rarely a reason to now that free automated certificates are universal. Using a normal certificate removes an entire class of expiry and trust-chain failure that is unpleasant to diagnose from the outside.
Related limits
A public endpoint accepts anything until you validate it, which is covered in the webhook secret token.
Why the same update can legitimately arrive twice, and what to do about it, is in at-least-once delivery.
For what Telegram does and does not tell you about a message after it is sent, see the absence of delivery receipts.
The same at-least-once reasoning applied to third-party systems is set out in the webhooks integration page.
What a webhook runtime actually costs to operate for a year is broken down in what hosting costs.