Telegram Webhook: Example Payload, Headers and Signature

The Update object Telegram posts to a bot's webhook, the optional secret-token header, and the trick of answering with an API call in the response body.

No webhook URL yet. Creating one needs no account.

Request details
Requests sent to your webhook URL appear here: method, headers and the body exactly as it arrived.

The button posts the example below to your webhook URL from your browser. It is not sent by Telegram, it carries your browser's User-Agent, and its signature is a placeholder that will not verify. To see a real delivery, follow the steps further down.

Last updated . How this page is checked.

How the webhook URL works. It is a public address that records whatever is sent to it and answers 200. An inbox keeps the 50 most recent requests, accepts bodies up to 256 KB, and is deleted 24 hours after it was created — or sooner if you press Delete URL or our server restarts, because requests are held in memory and never written to disk.

Anyone who has the URL can read what it received. Use sandbox and test-mode accounts, do not route production traffic or customer data through it, and delete it when you are done. Privacy details.

What is a Telegram webhook?

A Telegram bot receives updates in one of two ways: by polling getUpdates, or by registering a webhook with setWebhook, after which Telegram sends an HTTPS POST to your URL for every update. The two are mutually exclusive — while a webhook is set, getUpdates returns an error.

Each request carries one JSON Update. It has an update_id and exactly one other field naming what happened: message, edited_message, callback_query, inline_query and so on.

What a Telegram webhook request looks like

The request line and headers as they reach your server, followed by the body. Ids, tokens and the signature are placeholders.

POST /your/webhook/path HTTP/1.1
Host: your-server.example
Content-Type: application/json
X-Telegram-Bot-Api-Secret-Token: a-secret-you-chose-1234
{
  "update_id": 100000001,
  "message": {
    "message_id": 1365,
    "from": {
      "id": 1111111,
      "is_bot": false,
      "first_name": "Jon",
      "username": "jon_example",
      "language_code": "en"
    },
    "chat": {
      "id": 1111111,
      "first_name": "Jon",
      "username": "jon_example",
      "type": "private"
    },
    "date": 1767225600,
    "text": "/start",
    "entities": [
      {
        "offset": 0,
        "length": 6,
        "type": "bot_command"
      }
    ]
  }
}

Optional fields are simply absent — a photo message has no text, a channel post has no from. chat.id is what you pass back as chat_id to reply; for groups and channels it is a large negative number, so store it in a 64-bit type. Commands are not special: /start is ordinary text with a bot_command entity.

Headers Telegram sends

HeaderExampleWhat it is
Content-Typeapplication/jsonThe body is a JSON Update.
X-Telegram-Bot-Api-Secret-Tokena-secret-you-chose-1234Sent only if you passed secret_token to setWebhook. The value is exactly what you set.

Header names are case-insensitive. Your framework may show them lower-cased, and HTTP/2 always does.

Authenticating the request

Telegram does not sign the body. Authentication is a shared secret in a header: pass secret_token when calling setWebhook (1–256 characters from letters, digits, underscore and hyphen), and every webhook request then carries it in X-Telegram-Bot-Api-Secret-Token. Reject any request where it is missing or different.

Before that option existed, the usual advice was to put the bot token in the webhook path so the URL was unguessable. Prefer the header: URLs end up in access logs, the bot token is a far more valuable secret than a webhook token, and rotating one should not mean rotating the other.

const crypto = require('crypto');

function fromTelegram(req, secretToken) {
  const a = Buffer.from(req.headers['x-telegram-bot-api-secret-token'] || '');
  const b = Buffer.from(secretToken);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

Responses, retries and replying inline

Return a 2xx. On anything else Telegram repeats the update, giving up after what its documentation calls "a reasonable amount of attempts", and updates are kept for at most a day. getWebhookInfo is the debugging tool: it reports pending_update_count and the time and text of the last delivery error.

A Telegram-specific shortcut: the response to a webhook can itself be an API call. Reply with JSON containing a method field and its parameters — {"method":"sendMessage","chat_id":1111111,"text":"Hi"} — and Telegram executes it, saving a round trip. You do not learn whether that call succeeded, so use it for simple acknowledgements.

Catch a real Telegram update

  1. Press Create webhook URL at the top of this page and copy the address.
  2. Call setWebhook for a test bot: https://api.telegram.org/bot<TOKEN>/setWebhook?url=<the address>&secret_token=test123.
  3. Send the bot a message in Telegram.
  4. Read the Update and the secret-token header in the inbox above.
  5. When finished, call deleteWebhook so the bot can use getUpdates again.

Use a throwaway bot for this. While the webhook points here, this inbox — and anyone holding its URL — receives every message sent to the bot, and your real bot receives none.

To run a caught request against your own code, select it and press Open in API Tester: the method, headers and body are loaded into the request console, where you enter your server's public URL and send it. A body you edit will no longer match its signature — that is the signature doing its job.

Things that catch people out

  • Only certain ports. Telegram delivers webhooks to ports 443, 80, 88 and 8443 only, and requires TLS with a valid certificate (or a self-signed one you upload).
  • allowed_updates is sticky. The list you pass to setWebhook persists until you pass another, so a bot can silently stop receiving a type of update that was never re-enabled.
  • One slow handler blocks the queue. Raise max_connections if needed, and acknowledge quickly — pending updates pile up behind a stuck one.

api-tester.com is independent and is not affiliated with Telegram. Providers change header names, limits and retry schedules; treat the official Telegram documentation as the source of truth.

Related examples and tools

Other webhooks that send a plain secret token

References

These links leave api-tester.com, which is independent of the companies and projects they point to.

Telegram webhook FAQ

What does a Telegram webhook payload look like?

A JSON Update object with an update_id and one field describing the event, most often message, which contains message_id, from, chat, date and the text or media.

How do I secure a Telegram bot webhook?

Pass a secret_token when calling setWebhook. Telegram then sends it in the X-Telegram-Bot-Api-Secret-Token header of every request, and your endpoint rejects requests without it.

Can I use getUpdates and a webhook together?

No. They are mutually exclusive. While a webhook is set, getUpdates fails; call deleteWebhook to switch back to polling.

Which ports does Telegram support for webhooks?

Ports 443, 80, 88 and 8443, over HTTPS.

How do I see why Telegram webhook deliveries fail?

Call getWebhookInfo. It returns the configured URL, the number of pending updates and the date and message of the most recent delivery error.