Your bot works with getUpdates polling, you switch to a webhook for
production, and… nothing. No updates, no errors in your logs — because the failure is
happening between Telegram and your server, where you can't see it. Here's how to make
every step visible.
Before debugging your server, confirm what Telegram actually sends. Create a bin
(button below, or curl https://hookden.pages.dev/new) and set it as your webhook:
$ curl "https://api.telegram.org/bot<TOKEN>/setWebhook?url=https://hookden.pages.dev/h/YOUR_BIN"
Send your bot a message and watch the dashboard: the full Update JSON
arrives — message, callback_query, my_chat_member,
whatever your bot is subscribed to — with every header Telegram sends. Now you know
exactly what your handler must parse, and you have real payloads to
copy as curl / Python and replay against your handler locally.
This works because bins are HTTPS on port 443 with a valid certificate — which is precisely what Telegram requires (see the error table below).
$ curl "https://api.telegram.org/bot<TOKEN>/getWebhookInfo"
This is the single most useful debugging call. Look at:
last_error_message / last_error_date — Telegram tells you
why the last delivery failed (connection refused, cert error, timeout, non-2xx).pending_update_count — updates queued because deliveries are failing.url — confirms which endpoint is actually registered (deploys and
teammates overwrite this more often than you'd think).setWebhook time. Put your bot behind a
reverse proxy on 443 or use 8443 directly.deleteWebhook to go back to
polling, or stop the poller.last_error_message — Telegram
requires HTTPS with a verified (or explicitly uploaded self-signed) certificate,
including intermediate certificates. A missing chain works in Chrome but fails
Telegram's stricter verification. Test your chain with
openssl s_client -connect yourhost:443 -showcerts.last_error_message but no updates — check that
your firewall allows Telegram's subnets 149.154.160.0/20 and
91.108.4.0/22, and note webhooks are IPv4-only — an AAAA-only DNS record
won't receive anything.Set a secret when registering the webhook:
$ curl "https://api.telegram.org/bot<TOKEN>/setWebhook?url=…&secret_token=my-secret-123"
Telegram then sends X-Telegram-Bot-Api-Secret-Token: my-secret-123 with
every update. Point the webhook at a bin first and you can see the header arrive with
your own eyes before writing the check into your handler — captured headers show it
verbatim.
Telegram refuses plain-HTTP and localhost URLs, which is why most tutorials reach for
ngrok. But Telegram bots don't reply through the webhook response — they call
sendMessage etc. over the regular API — so all you need is the
inbound updates, and those can be relayed:
$ curl -s https://hookden.pages.dev/cli -o hookden && chmod +x hookden $ ./hookden relay YOUR_BIN http://localhost:8080/webhook
Set the webhook to your bin once; every update is re-delivered to your local bot
byte-exact (the secret_token header included, so your verification code runs
unchanged). Your laptop needs no public IP, no tunnel daemon, no TLS setup. Full details:
webhooks to localhost without a tunnel.
When you're done inspecting, point the webhook back at your real endpoint (or
deleteWebhook to resume polling). Add
drop_pending_updates=true if you don't want the queued backlog replayed at
your bot.
No signup needed. Or from your terminal: curl https://hookden.pages.dev/new