Skip to main content
Smithers ships two Telegram surfaces:
  • @smithers-orchestrator/telegram for bots that run inside serverless functions, workflow tasks, or tests without owning a long-lived polling process (this page’s first half).
  • @smithers-orchestrator/integrations/telegram for workflows that wait on Telegram durably: listen for messages, and approve gated steps from the chat with inline buttons or a Mini App (Approve from the chat).
The package follows the integration shape proven by Eliza’s Telegram plugin: separate bot lifecycle/API access, normalized incoming messages, formatting helpers, and a fake test client. Smithers keeps the implementation raw fetch instead of Telegraf so Vercel Functions, Cloudflare Workers, and Sandbox tasks can call the Bot API without starting a local polling loop.

Install

Send messages

createTelegramClient retries Bot API 429 and 5xx responses plus transient network/read failures. 429 honors Telegram’s retry_after parameter; other retryable failures use exponential backoff. Permanent 4xx responses are not retried.

Verify webhooks

normalizeTelegramUpdate handles message, edited_message, channel_post, and edited_channel_post updates and ignores bot-authored messages by default. isTelegramChatAllowed follows Eliza’s fail-closed access-control shape: missing or blank TELEGRAM_ALLOWED_CHATS allows all chats, a JSON array of chat ids restricts processing to those ids, and malformed values block all chats until the configuration is fixed.

Test with a fake

Use the fake in local e2e tests when production uses real Telegram credentials.

Approve from the chat

<Telegram.Approval> turns a workflow approval into inline Approve and Reject buttons in a chat and resolves it from the button press, using the durable long-poll source. No web server, no gateway wiring. The decision has the same shape as the core <Approval> component: { approved, note, decidedBy, decidedAt }. Register the two internal node schemas with telegramApprovalSchemas, then drop the component in:
For a menu instead of Approve and Reject, pass mode="select" with options; the decision becomes { selected, notes }. For the run to receive the press, a Telegram source must be polling. Run one next to your workflow:
WaitForEvent wakes on the first callback query for the chat, so run one interactive approval per chat at a time, or pass threadId (a forum topic) to isolate concurrent approvals. Any chat member can press a button, so keep approvals in a private or allowlisted chat.

Mini App approvals

For a richer decision (read a diff, pick an option, add a note) inside Telegram, add a Mini App button. miniApp opens a web_app page in the chat alongside the plain buttons:
A Mini App exposes a signed initData string. Your backend must verify it before trusting the user. Never trust initDataUnsafe.
Verification is HMAC-SHA256 with a crossed key: secret = HMAC_SHA256(key="WebAppData", message=botToken), and the data is authentic when hex(HMAC_SHA256(key=secret, message=dataCheckString)) equals the hash field, where dataCheckString is every field except hash, formatted key=value, sorted, and newline-joined. It runs on Web Crypto, so the same call works in the Smithers runtime and in a Cloudflare Worker. verifyTelegramWebAppInitDataSignature verifies the newer Ed25519 signature from a third party with only the numeric bot id. A runnable reference (a static Mini App page plus a Worker endpoint that verifies initData for real) ships in apps/telegram-site: site/approve.html and src/handleApprove.ts.