Back to How To
How To

How to receive Telegram webhooks on a local computer behind NAT and a firewall

A step-by-step guide to receiving Telegram webhooks on a local computer behind NAT or a firewall with Adal — no public IP address, port forwarding, or self-hosted HTTPS server required.

15 min

Developing a Telegram bot locally often involves more than just dealing with localhost. In many real-world network setups, a developer’s machine is not reachable from the internet at all: it may sit behind a home router, corporate firewall, ISP-level CGNAT, mobile network, or VPN.

Even if you already have an HTTP server running at http://127.0.0.1:3000, Telegram cannot connect to it directly. Telegram webhooks require a public HTTPS URL that is reachable from the internet. Traditionally, that means obtaining a public IP address, opening an inbound firewall port, configuring NAT or port forwarding on a router, registering a domain, and issuing a TLS certificate.

For local development, that infrastructure is unnecessary overhead. In this guide, Telegram will send updates to an Adal Endpoint, and Adal will deliver them to your local handler through an outbound Adal CLI connection. Your computer can remain behind NAT and a firewall: no public IP address, open inbound ports, or self-hosted HTTPS server are required.

What you will build

The final setup will look like this:

Telegram user ↓ Telegram Bot API ↓ HTTPS POST Public Adal Endpoint ↓ Outbound Adal CLI connection ↓ Developer machine behind NAT / firewall ↓ http://127.0.0.1:3000/telegram

Telegram only communicates with the public HTTPS Endpoint in Adal. Your computer does not accept any inbound connections from the internet. Instead, Adal CLI establishes an outbound connection to Adal and receives webhook deliveries for the local Destination through that connection.

This works even when:

  • your computer is connected to home Wi-Fi behind a router;

  • your ISP uses CGNAT and does not provide a public IPv4 address;

  • opening inbound ports is prohibited on your work computer;

  • the application runs inside a local Docker container;

  • you are working through a VPN, mobile network, or restricted corporate network.

Why NAT and firewalls prevent direct Telegram webhook delivery

When Telegram sends a webhook, it needs to open an HTTPS connection to your server. That server must be publicly reachable from the internet.

A local machine, however, is usually part of a private network:

Telegram ↓ Router or ISP public IP address ↓ NAT / firewall ↓ Your local computer: 192.168.x.x or 10.x.x.x

Addresses such as 192.168.x.x, 10.x.x.x, 172.16.x.x, and 127.0.0.1 are not routable from the public internet. Even if you know your router’s external IP address, an inbound connection may be blocked by a firewall or fail to reach the correct machine without explicit NAT configuration.

Adal reverses the direction of the connection. Instead of Telegram connecting to your computer, your computer connects to Adal:

Your computer → Adal CLI → Adal Telegram → public Adal Endpoint

Outbound HTTPS and WebSocket connections are typically allowed even on networks that block inbound connections. This lets your local machine receive Telegram webhooks while remaining completely inaccessible to direct traffic from the internet.

What you need

To follow this guide, you will need:

  • a Telegram bot created through @BotFather;

  • your bot token;

  • an Adal account;

  • Adal CLI installed and authenticated;

  • a local HTTP server that accepts POST requests;

  • Node.js, PHP, Python, or another runtime for your application.

The example below uses Node.js, but the overall setup is independent of the programming language or framework you use.

1. Create a Telegram bot and save its token

Open @BotFather in Telegram and run:

/newbot

BotFather will ask you to choose a bot name and username, then provide a token similar to this:

123456789:AAExampleTokenThatMustRemainPrivate

Do not commit this token to Git, send it in messages, or expose it in frontend code. It grants full access to your bot through the Telegram Bot API.

For local development, store it in an environment variable:

export TELEGRAM_BOT_TOKEN='123456789:AAExampleTokenThatMustRemainPrivate'

In Windows PowerShell:

$env:TELEGRAM_BOT_TOKEN = '123456789:AAExampleTokenThatMustRemainPrivate'

2. Start a local Telegram webhook handler

Create a file named server.mjs:

import http from 'node:http'; const port = 3000; const webhookSecret = process.env.TELEGRAM_WEBHOOK_SECRET; const server = http.createServer(async (request, response) => { if (request.method !== 'POST' || request.url !== '/telegram') { response.writeHead(404); response.end('Not found'); return; } const receivedSecret = request.headers['x-telegram-bot-api-secret-token']; if (!webhookSecret || receivedSecret !== webhookSecret) { response.writeHead(401); response.end('Unauthorized'); return; } let body = ''; request.on('data', (chunk) => { body += chunk; }); request.on('end', () => { try { const update = JSON.parse(body); console.log('Telegram update received:'); console.dir(update, { depth: null }); const message = update.message; if (message?.text) { console.log( `Message from ${message.from.username ?? message.from.id}: ${message.text}` ); } response.writeHead(200, { 'content-type': 'application/json', }); response.end(JSON.stringify({ ok: true })); } catch (error) { console.error('Failed to process Telegram update:', error); response.writeHead(400, { 'content-type': 'application/json', }); response.end(JSON.stringify({ ok: false })); } }); }); server.listen(port, '127.0.0.1', () => { console.log(`Listening on http://127.0.0.1:${port}/telegram`); });

Generate a separate secret to verify requests from Telegram:

openssl rand -base64 32 | tr '+/' '-_' | tr -d '='

Store the generated value in an environment variable:

export TELEGRAM_WEBHOOK_SECRET='your-random-secret'

Then start the server:

node server.mjs

You should see the following output:

Listening on http://127.0.0.1:3000/telegram

Your local server is now ready to receive Telegram updates, but it is still accessible only from your computer. The next step is to create a public receiving endpoint in Adal.

3. Create an Endpoint for Telegram in Adal

Open the Adal Dashboard and create a new Endpoint.

The following settings are recommended:

  • Name: telegram-local

  • Allowed methods: POST

  • Deliver pending requests on connect: enabled

  • Notes: Telegram Bot API updates for local development

After creating the Endpoint, Adal will provide a permanent public URL. It will look similar to this:

https://your-endpoint.se1.adal.cloud

This is the URL you will configure as the webhook URL in the Telegram Bot API.

Do not add a local path such as /telegram to this URL unless you have explicitly configured path routing in your Destination. In this guide, the public Endpoint URL remains at the root path, while /telegram is used only inside the local application.

When you create the Endpoint, Adal will also provide a CLI token. Save it immediately, because it is shown only once. If you lose it, it cannot be recovered; you will need to generate a new token.

4. Create a local Destination through Adal CLI

Within the Endpoint, create a Destination that delivers requests through Adal CLI to the following local address:

http://127.0.0.1:3000/telegram

Create a CLI token for this Destination and connect the local Adal CLI client using the command shown in the Dashboard after the token is created.

Once the connection is established, the Destination should appear as online.

At this point, Adal CLI establishes an outbound connection to Adal. This is the key part of the setup: Telegram and other external services never connect to your computer directly.

Your computer may be behind NAT, a home router, a corporate firewall, VPN, or CGNAT. It does not need a public IP address, port forwarding, an open inbound port, a domain name, or its own TLS certificate. It only needs to be able to establish outbound connections to Adal.

5. Set the webhook in Telegram

Create an environment variable containing the Adal Endpoint URL:

export ADAL_ENDPOINT_URL='https://your-endpoint.se1.adal.cloud'

Then call the setWebhook method:

curl -sS -X POST \ "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/setWebhook" \ -d "url=${ADAL_ENDPOINT_URL}" \ -d 'allowed_updates=["message","callback_query"]' \ -d "secret_token=${TELEGRAM_WEBHOOK_SECRET}"

When the webhook is configured successfully, Telegram will return:

{ "ok": true, "result": true, "description": "Webhook was set" }

The secret_token parameter is especially important. Telegram includes its value in every webhook request through the following header:

X-Telegram-Bot-Api-Secret-Token

The local handler in the example above verifies this header and rejects requests that do not contain the correct secret.

6. Verify that the webhook works

Open a chat with your bot and send it a message:

/start

Your local application should print a Telegram update in the console:

Telegram update received: { update_id: 123456789, message: { message_id: 1, from: { id: 12345678, is_bot: false, first_name: "Yuriy" }, chat: { id: 12345678, type: "private" }, date: 1782921600, text: "/start" } }

At the same time, the Adal Dashboard will show the incoming webhook and its delivery status for your local Destination.

This is useful not only for initial setup, but also for debugging. You can inspect request headers, the request body, and delivery status, then replay a specific update to your local application when needed.

How to check the current Telegram webhook configuration

Telegram provides the getWebhookInfo method for viewing the current webhook status:

curl -sS \ "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/getWebhookInfo"

A successful response may look like this:

{ "ok": true, "result": { "url": "https://your-endpoint.se1.adal.cloud", "has_custom_certificate": false, "pending_update_count": 0, "max_connections": 40, "allowed_updates": [ "message", "callback_query" ] } }

Pay particular attention to the following fields:

  • url — the address Telegram is currently sending updates to;

  • pending_update_count — the number of updates waiting to be delivered;

  • last_error_message — the most recent delivery error;

  • last_error_date — when the most recent error occurred;

  • allowed_updates — the types of updates the bot is configured to receive.

If url is empty, no webhook is configured. The bot may be using long polling instead, or it may not be receiving updates at all.

How to receive Telegram webhooks behind NAT and a firewall

Addresses such as localhost, 127.0.0.1, and ::1 exist only on the computer where the application is running. From Telegram’s perspective, localhost does not point to your MacBook, work laptop, or Docker container. It points to Telegram’s own infrastructure.

But the problem is not limited to localhost. Even a server available on your home network at an address such as 192.168.1.10 cannot be reached directly from the internet. These addresses sit behind a router’s NAT and are not routable on the public internet.

Without Adal, allowing Telegram to reach a local computer would usually require you to:

  1. obtain a public IP address;

  2. configure port forwarding on the router;

  3. open an inbound firewall port;

  4. configure public DNS;

  5. issue and maintain a TLS certificate;

  6. ensure that the network address and routing rules do not change.

In a corporate network, behind CGNAT, or on a mobile connection, some of these steps may not be possible at all.

With Adal, none of this infrastructure is required. Telegram sends the webhook to a public HTTPS Endpoint in Adal, and Adal forwards the request to your local application through the already established outbound Adal CLI connection.

The delivery flow is as follows:

  1. Telegram sends an update to the public HTTPS Endpoint in Adal.

  2. Adal receives and stores the incoming webhook.

  3. Adal creates a delivery for the connected local Destination.

  4. Adal CLI receives that delivery through the outbound connection from your computer.

  5. The CLI forwards the HTTP request to http://127.0.0.1:3000/telegram.

  6. Your handler responds as though Telegram had connected to it directly.

As a result, Telegram webhooks can work on a computer behind NAT and a firewall without opening any inbound ports.

What happens when the local computer is offline

Telegram will continue sending updates to the Adal Endpoint because the public URL remains available even when your laptop is offline.

When the local Destination is unavailable, the delivery is not immediately lost. Adal records it in the delivery history and handles it according to the configured retry policy. Once Adal CLI reconnects, pending webhook deliveries can be sent to your local application.

This is particularly useful during development: you can close your laptop, return to the task later, reconnect the CLI, and inspect the updates that arrived while your local environment was unavailable.

Your bot’s business logic should still account for duplicate delivery. Telegram updates may be delivered more than once, so your handler should be idempotent. At a minimum, store each update_id and avoid performing the same action twice.

How to disable a Telegram webhook

To switch back to long polling with getUpdates, remove the webhook first:

curl -sS -X POST \ "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/deleteWebhook"

To remove the webhook and discard any pending updates at the same time, pass drop_pending_updates:

curl -sS -X POST \ "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/deleteWebhook" \ -d 'drop_pending_updates=true'

Use this option carefully: it deletes unprocessed updates held by Telegram.

Common issues

Telegram returns 409 Conflict

This usually means that your application is trying to use getUpdates while a webhook is already configured.

A Telegram bot cannot use webhooks and long polling at the same time. Check the current configuration:

curl -sS \ "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/getWebhookInfo"

If url is not empty, either disable polling in your application or remove the webhook.

Adal received the webhook, but the local application does not receive it

Check the following:

  1. Adal CLI is connected and the Destination is online.

  2. The local server is listening on 127.0.0.1:3000.

  3. The Destination is configured with the correct address:

http://127.0.0.1:3000/telegram

Also review the delivery history in Adal. It will show the HTTP status code, connection error, or timeout returned by the local handler.

The local application returns 401 Unauthorized

This means that TELEGRAM_WEBHOOK_SECRET does not match the secret configured in Telegram.

Make sure that the same value is used in both places:

  • the environment variable used by the local application;

  • the secret_token parameter passed to setWebhook.

After changing the secret, call setWebhook again so Telegram starts sending the new header value.

Telegram reports a webhook delivery error

Call getWebhookInfo and inspect the last_error_message field.

If the Adal Endpoint receives the request but the local delivery fails, the relevant error will usually appear in the delivery history for that webhook. This makes it easier to separate Telegram → Adal connectivity issues from Adal → localhost delivery issues.

Conclusion

Telegram Bot API requires a public HTTPS URL for webhook delivery, but your local computer does not need to be publicly accessible.

With Adal, you can receive Telegram webhooks on a machine behind NAT, a home router, corporate firewall, CGNAT, VPN, or mobile network. You do not need a public IP address, port forwarding, inbound firewall rules, a domain, or a TLS certificate on the local machine.

Create an Endpoint in Adal, connect a local Destination through Adal CLI, and configure the Endpoint URL with setWebhook. Telegram sends updates to Adal, while your application receives them on localhost through an outbound connection.

This approach lets you develop and debug Telegram bots locally in a restricted network without changing your computer’s network configuration or exposing it to direct access from the internet.