Skip to content

feat(telegram): expose apiRoot config for custom Bot API server#46755

Open
dzianisv wants to merge 2 commits intoopenclaw:mainfrom
dzianisv:feat/telegram-api-root
Open

feat(telegram): expose apiRoot config for custom Bot API server#46755
dzianisv wants to merge 2 commits intoopenclaw:mainfrom
dzianisv:feat/telegram-api-root

Conversation

@dzianisv
Copy link
Copy Markdown

Summary

Add channels.telegram.apiRoot (string, optional) that passes through to grammY's ApiClientOptions.apiRoot. When unset, behavior is unchanged (defaults to https://api.telegram.org).

Also supports TELEGRAM_API_ROOT env var as fallback.

Motivation

I'm building @OpenClawBoxBot (openclaw.vibebrowser.com) — a managed OpenClaw hosting platform via Telegram. Each customer gets an isolated OpenClaw gateway instance running in Kubernetes.

The architecture uses a centralized Telegram gateway proxy: the bot pod receives Telegram updates via long-polling and forwards them to the tenant gateway's webhook. The gateway's Telegram channel processes everything natively (all media types, threading, reactions, streaming). For outbound API calls (sendMessage, sendPhoto, getFile), the gateway needs to route through a proxy in the bot pod that injects the real bot token.

Without apiRoot, the gateway always calls api.telegram.org directly, which means the real bot token must be in every tenant gateway — a security problem for multi-tenant deployments where customers could extract the token.

With apiRoot, I can point outbound calls at http://bot-proxy:8081 and keep the real token centralized.

Use cases

  • Self-hosted Telegram Bot API servers (tdlib/telegram-bot-api) for higher upload limits (up to 2GB) and faster file access
  • Centralized gateway proxies that inject bot tokens (multi-tenant security)
  • Testing with mock Bot API servers

Changes

  • src/config/types.telegram.ts: add apiRoot?: string to TelegramAccountConfig
  • src/config/zod-schema.providers-core.ts: add apiRoot to the Zod validation schema
  • extensions/telegram/src/bot.ts: read apiRoot from config (with TELEGRAM_API_ROOT env fallback) and pass to grammY's ApiClientOptions
  • src/config/schema.help.ts: add help text for the new key

grammY already supports apiRoot as a first-class option — this change just reads the config value and passes it through.

Config example

{
  "channels": {
    "telegram": {
      "apiRoot": "http://my-proxy:8081",
      "botToken": "placeholder",
      // ...
    }
  }
}

Closes #28535

Add channels.telegram.apiRoot (string, optional) that passes through to
grammY's ApiClientOptions.apiRoot. When unset, behavior is unchanged
(defaults to https://api.telegram.org).

Also supports TELEGRAM_API_ROOT env var as fallback.

This enables:
- Self-hosted Telegram Bot API servers (tdlib/telegram-bot-api) for
  higher upload limits and faster file access
- Centralized gateway proxies that inject bot tokens (security:
  multi-tenant deployments where the real token should not leave
  the proxy host)
- Testing with mock Bot API servers

grammY already supports apiRoot as a first-class option — this change
just reads the config value and passes it through.

Closes openclaw#28535
@openclaw-barnacle openclaw-barnacle bot added channel: telegram Channel integration: telegram size: XS labels Mar 15, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 15, 2026

Greptile Summary

This PR adds a new optional apiRoot config field (plus a TELEGRAM_API_ROOT env-var fallback) to the Telegram channel integration, allowing outbound Bot API calls to be routed through a custom server instead of the default https://api.telegram.org. The feature is clean, minimal, and directly maps to grammY's existing first-class ApiClientOptions.apiRoot option.

  • TelegramAccountConfig.apiRoot?: string added with clear JSDoc covering all three intended use cases.
  • Zod schema entry is consistent with the proxy and webhookUrl fields (optional string, no URL-format validation, which matches existing patterns in the file).
  • bot.ts resolves apiRoot from config first, then env var, then undefined, using .trim() to guard against whitespace-only values — correct and consistent with timeoutSeconds handling.
  • Help text in schema.help.ts is correctly placed and clearly worded.
  • No existing behavior is affected when the field is omitted.

Confidence Score: 5/5

  • This PR is safe to merge — it is a purely additive, opt-in config field with no behavioral change when unset.
  • All four changed files make minimal, well-scoped additions that follow existing patterns. No existing logic is modified, the new code path is only exercised when apiRoot is explicitly configured, and grammY already supports this option natively. No tests need updating for the unchanged default path.
  • No files require special attention.

Last reviewed commit: 885cbd7

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 885cbd73d9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +214 to +215
const apiRoot =
telegramCfg?.apiRoot?.trim() || process.env.TELEGRAM_API_ROOT?.trim() || undefined;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Route all Telegram send clients through configured apiRoot

This change only applies apiRoot to the bot client created in createTelegramBot, but other outbound paths still construct separate clients in extensions/telegram/src/send.ts (resolveTelegramClientOptions and resolveTelegramApiContext) without reading account.config.apiRoot. That means flows using sendMessageTelegram/reaction/delete helpers (for example channel outbound and pairing notifications) still call the default Telegram endpoint, so deployments that depend on channels.telegram.apiRoot (proxy/self-hosted Bot API) will see partial failures or endpoint bypass despite setting this config.

Useful? React with 👍 / 👎.

Comment on lines +1414 to +1415
"channels.telegram.apiRoot":
"Custom base URL for all outbound Telegram Bot API calls. Defaults to https://api.telegram.org. Point this at a self-hosted Bot API server or a centralized gateway proxy.",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Add schema label for new Telegram apiRoot help key

A new FIELD_HELP entry is introduced for channels.telegram.apiRoot, but there is no matching key in src/config/schema.labels.ts, which breaks the help/label parity contract enforced by src/config/schema.help.quality.test.ts and leaves label-driven config UIs without a display label for this field. When adding a help key, the corresponding label should be added in the same change.

Useful? React with 👍 / 👎.

Address review feedback:
- extensions/telegram/src/send.ts: add apiRoot to resolveTelegramClientOptions
  so outbound send helpers (pairing, reactions, etc.) also route through
  the configured API root
- src/config/schema.labels.ts: add label for channels.telegram.apiRoot
  to maintain help/label parity
@tata-meow
Copy link
Copy Markdown

tata-meow commented Mar 17, 2026

Thanks for adding apiRoot config support! We deployed a self-hosted Bot API server (with --local flag) and found a few additional places that still hard-code https://api.telegram.org:

Media download (delivery.resolve-media.ts)

This is the most critical gap. downloadAndSaveTelegramFile (line 128) builds the file download URL with a hard-coded base:

const url = `https://api.telegram.org/file/bot${params.token}/${params.filePath}`;

This should use apiRoot from config instead. Additionally, when using --local mode, the Bot API server returns absolute filesystem paths (e.g. /var/lib/telegram-bot-api/.../photos/file_40.jpg) as file_path in getFile responses. These need to be read directly from disk via fs.readFile() rather than fetched over HTTP, since the local server does not serve a /file/bot.../ download endpoint.

The apiRoot parameter also needs to be threaded through resolveMedia()resolveStickerMedia()downloadAndSaveTelegramFile(), and passed from bot-handlers.ts where telegramCfg is available.

SSRF policy (delivery.resolve-media.ts)

TELEGRAM_MEDIA_SSRF_POLICY.allowedHostnames only includes api.telegram.org. When apiRoot points to localhost or 127.0.0.1, media fetches are blocked by the SSRF filter. Needs allowPrivateNetworkRanges: true and the custom hostname added.

Other hard-coded endpoints

File Line Usage
audit-membership-runtime.ts 11 TELEGRAM_API_BASE used for membership audit API calls
api-fetch.ts 6 getChat API call
probe.ts 7 TELEGRAM_API_BASE used for health check probes

These should also respect apiRoot config (or TELEGRAM_API_ROOT env) to be consistent.


We have a working patch for the media download path (including local file read support) in a dedicated branch: tata-meow/openclaw@fix/telegram-apiroot-media.

@Cypherm
Copy link
Copy Markdown
Contributor

Cypherm commented Mar 20, 2026

@tata-meow Great analysis — these are exactly the gaps I ran into as well.

I've addressed all of these in #48842:

  • Media downloads: resolveTelegramApiBase(apiRoot) threaded through resolveMedia()resolveStickerMedia()downloadAndSaveTelegramFile(), with a dynamic buildTelegramMediaSsrfPolicy(apiRoot) that adds the custom hostname and enables private network ranges when needed
  • Probe & audit: both probeTelegram() and auditTelegramGroupMembershipImpl() use resolveTelegramApiBase() instead of the hard-coded constant
  • Config schema: Zod URL validation with telegram.apiRoot config key + TELEGRAM_API_ROOT env var

The --local mode filesystem read path you mentioned is a good follow-up — that's a separate concern (transport mode rather than URL base) and probably warrants its own PR to keep scope clean.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: telegram Channel integration: telegram size: XS

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(telegram): expose apiRoot config for custom Bot API server

3 participants