Telegram Bot API
🌐 English

How to Set Up a Telegram Bot Webhook: setWebhook, Secret Token and Troubleshooting

· 8 min read

A Telegram bot webhook is an HTTPS URL on your server to which Telegram sends every update as a JSON POST request, so your bot no longer has to poll getUpdates. You register it with one call, setWebhook with your url and a secret_token, and check delivery with getWebhookInfo. This guide covers the requirements, a copy-paste handler that verifies the secret token, and fixes for the errors people hit most, including n8n on localhost.

Webhook vs getUpdates

getUpdates (long polling)Webhook
Who connectsYour bot asks Telegram for updatesTelegram POSTs updates to your URL
NeedsOutgoing internet access onlyA public HTTPS endpoint
Works on localhostYesOnly through a tunnel or proxy
Good forDevelopment, simple botsProduction, serverless, high traffic

The two methods are mutually exclusive: while a webhook is set, getUpdates returns 409 Conflict. Either way, Telegram keeps undelivered updates for up to 24 hours.

Telegram webhook requirements

A local Bot API server lifts some of these limits (HTTP, any port), but with api.telegram.org the rules above apply.

Set the webhook URL with setWebhook

Generate a random secret first, for example with openssl rand -hex 32, then register the URL:

curl -X POST "https://api.telegram.org/bot123456789:AAEexampleTokenReplaceWithYours/setWebhook" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/telegram/webhook",
    "secret_token": "replace-with-your-random-secret",
    "allowed_updates": ["message", "callback_query"],
    "drop_pending_updates": true,
    "max_connections": 40
  }'

On success the API answers {"ok": true, "result": true, "description": "Webhook was set"}.

ParameterMeaning
urlRequired. HTTPS URL for updates; an empty string removes the webhook.
secret_token1–256 characters A-Z a-z 0-9 _ -, sent back in the X-Telegram-Bot-Api-Secret-Token header.
allowed_updatesUpdate types to receive. If omitted, the previous setting is kept; an empty list means all types except chat_member, message_reaction and message_reaction_count.
drop_pending_updatesDiscard updates waiting for delivery.
max_connectionsSimultaneous HTTPS connections, 1–100 (default 40). Lower it to protect a small server.
certificatePublic key certificate for self-signed setups, uploaded as a file.
ip_addressFixed IP address to send requests to instead of the one resolved via DNS.

For a quick one-off setup, the browser address bar works too: https://api.telegram.org/bot<token>/setWebhook?url=https://example.com/telegram/webhook. With a self-signed certificate, upload it as multipart/form-data:

curl -F "url=https://203.0.113.10:8443/telegram/webhook" \
  -F "certificate=@public.pem" \
  "https://api.telegram.org/bot123456789:AAEexampleTokenReplaceWithYours/setWebhook"

Prefer a form? Fill in setWebhook in the Bot API Tester and execute it from your browser.

Check delivery with getWebhookInfo

curl "https://api.telegram.org/bot123456789:AAEexampleTokenReplaceWithYours/getWebhookInfo"
{
  "ok": true,
  "result": {
    "url": "https://example.com/telegram/webhook",
    "has_custom_certificate": false,
    "pending_update_count": 14,
    "last_error_date": 1793266500,
    "last_error_message": "Wrong response from the webhook: 500 Internal Server Error",
    "max_connections": 40,
    "allowed_updates": ["message", "callback_query"]
  }
}

Run the same check in getWebhookInfo in the Bot API Tester whenever updates stop arriving.

Verify the X-Telegram-Bot-Api-Secret-Token header

Anyone who discovers your webhook URL can POST fake updates to it. With secret_token set, Telegram adds the secret to every request in the X-Telegram-Bot-Api-Secret-Token header; reject anything that doesn't match, using a constant-time comparison. Python with Flask (pip install flask):

import hmac
import os

from flask import Flask, abort, jsonify, request

app = Flask(__name__)
SECRET = os.environ["WEBHOOK_SECRET"]

@app.post("/telegram/webhook")
def telegram_webhook():
    received = request.headers.get("X-Telegram-Bot-Api-Secret-Token", "")
    if not hmac.compare_digest(received.encode(), SECRET.encode()):
        abort(403)
    update = request.get_json(silent=True) or {}
    message = update.get("message") or {}
    if "text" in message:
        # reply inside the webhook response: no extra API call needed
        return jsonify(method="sendMessage", chat_id=message["chat"]["id"],
                       text="Echo: " + message["text"])
    return "", 200

The reply trick is documented by Telegram: you may answer a webhook request with a JSON body that contains method and its parameters. You can't see whether that call succeeded, so make a regular API request when you need the result. The same check in Node.js 18+ without dependencies (save as server.mjs):

import http from "node:http";
import crypto from "node:crypto";

const SECRET = Buffer.from(process.env.WEBHOOK_SECRET);

function hasValidSecret(req) {
  const received = Buffer.from(req.headers["x-telegram-bot-api-secret-token"] ?? "");
  return received.length === SECRET.length && crypto.timingSafeEqual(received, SECRET);
}

function handleUpdate(update) {
  console.log("update", update.update_id); // your bot logic goes here
}

http.createServer((req, res) => {
  if (req.method !== "POST" || req.url !== "/telegram/webhook") {
    res.writeHead(404).end();
    return;
  }
  if (!hasValidSecret(req)) {
    res.writeHead(403).end();
    return;
  }
  let body = "";
  req.on("data", (chunk) => (body += chunk));
  req.on("end", () => {
    res.writeHead(200).end(); // acknowledge first, then process
    try {
      handleUpdate(JSON.parse(body));
    } catch (err) {
      console.error("bad update", err);
    }
  });
}).listen(8080);

Port 8080 isn't one of the four webhook ports, so run the app behind a reverse proxy such as nginx or Caddy that terminates TLS on 443 and forwards requests to it. Keep the secret in sync: if your server has a new secret but the webhook still has the old one, your own handler answers 403 and Telegram keeps retrying.

Respond with 2xx quickly

Telegram treats any response other than 2XX as a failed delivery: it repeats the request and gives up after a reasonable number of attempts, while new updates queue up behind it. Practical rules:

deleteWebhook: switch back to getUpdates

curl "https://api.telegram.org/bot123456789:AAEexampleTokenReplaceWithYours/deleteWebhook?drop_pending_updates=true"

drop_pending_updates=true discards the backlog; leave it out to receive queued updates with getUpdates. Calling setWebhook with an empty url removes the webhook as well. Afterwards getWebhookInfo shows an empty url and polling works again.

Troubleshooting Telegram webhook errors

ErrorCause and fix
Bad Request: bad webhook: An HTTPS URL must be provided for webhookThe URL starts with http://, often a local n8n or dev server. Expose it over HTTPS.
setWebhook rejects the portOnly 443, 80, 88 and 8443 are accepted. Change the port or proxy through 443.
409 Conflict from getUpdatesA webhook is active. Call deleteWebhook before polling.
TLS or certificate errors in last_error_messageIncomplete chain, CN/SAN mismatch, or a self-signed certificate that wasn't uploaded.
Wrong response from the webhook: …Your server returned a non-2xx status: a wrong path, a crashing handler or a secret mismatch.
The webhook URL keeps changingAnother service calls setWebhook with the same token. A bot has exactly one webhook URL, so give each service its own bot.
No updates for group messagesPrivacy mode is on, or the update type is missing from allowed_updates.

Telegram webhook with n8n

The n8n Telegram Trigger receives updates through a webhook: it registers its own URL for your bot with setWebhook. Three consequences follow:

Related guides: keep your bot token secret, since it is part of every API call your handler makes, and Telegram Bot API limits for sending replies at scale. New to bots? Start with how to create a Telegram bot.

Frequently asked questions

How do I set a webhook URL for my Telegram bot?

Call setWebhook, for example https://api.telegram.org/bot<token>/setWebhook?url=https://example.com/telegram/webhook. The URL must be public HTTPS on port 443, 80, 88 or 8443. Confirm the result with getWebhookInfo.

What is the secret token in a Telegram webhook?

An optional secret_token of 1–256 characters (A-Z, a-z, 0-9, _, -) that you pass to setWebhook. Telegram sends it in the X-Telegram-Bot-Api-Secret-Token header of every webhook request, so your handler can reject requests without it.

Can I use getUpdates and a webhook at the same time?

No, the two are mutually exclusive. While a webhook is set, getUpdates fails with 409 Conflict; call deleteWebhook to go back to polling.

Why does n8n show “An HTTPS URL must be provided for webhook”?

The n8n Telegram Trigger registers its own URL with setWebhook. If n8n runs on http://localhost, Telegram rejects that URL. Make n8n reachable at a public HTTPS address, for example through a reverse proxy or a tunnel, and let it register that address.

How do I delete a Telegram bot webhook?

Call deleteWebhook, optionally with drop_pending_updates=true to discard queued updates. Calling setWebhook with an empty url also removes it; getWebhookInfo then shows an empty url.

Can I use a self-signed certificate for a Telegram webhook?

Yes. Upload the public certificate in PEM format as the certificate parameter using multipart/form-data. Certificates from a trusted CA need no upload, but your server must send the full chain, including intermediates.

How to Create a Telegram Bot: Step by Step with BotFather and Python

How to create a Telegram bot step by step: register it with @BotFather, get the token, test it with getMe and send a first message with curl or Python.

Telegram Bot API 10.0–10.3: Rich Messages, Ephemeral Messages, Guest Mode and More

What's new in Telegram Bot API 10.0–10.3: guest mode, rich messages, streamed drafts, ephemeral messages, join request queries, with JSON examples.

Telegram Bot API Limits: Rate Limits, 429 Errors, File Sizes and Message Length

Telegram Bot API rate limits explained: 30 msg/s, 1 msg/s per chat, 20/min in groups, handling 429 retry_after, plus file size and message length limits.

How to Send a Message with the Telegram Bot API (sendMessage)

Telegram Bot API send message guide: sendMessage with curl, Python and JavaScript, HTML and MarkdownV2 formatting, inline buttons and error fixes.

Telegram Bot Token: How to Get It, Find It Again, Check It and Keep It Safe

Telegram bot token guide: get one from @BotFather, find the token of an existing bot, check it with getMe, revoke a leaked token and store it safely.

How to Get a Telegram Chat ID for a User, Group, Channel or Topic

How to get a Telegram chat ID for a user, group, supergroup, channel or forum topic with getUpdates, plus fixes for an empty result and 409 Conflict.

How to Test a Telegram Mini App: From Browser Mock to Real Telegram

Test a Telegram Mini App step by step: browser mock mode, localhost via HTTPS tunnel, test environment, DevTools per platform, initData validation.