# Telegram Send messages to a chat or channel through your own Telegram bot. URL: https://docs.qaldy.com/connectors/telegram/ - **Status:** [[Available]] - **Shelf:** Messaging - **How you connect:** Bot token - **Regions:** global and Kazakhstan - **The service’s own API reference:** https://core.telegram.org/bots/api Apps send messages to a chat or channel through **your own bot**. The gate calls `api.telegram.org` with the token; app code only ever sees `{ messageId }`. ## Connect it in two minutes 1. In Telegram, open **@BotFather** → `/newbot` → pick a name and a username ending in `bot`. 2. BotFather replies with a token like `7712345678:AAH…`. Copy the whole line. 3. Qaldy → **Connectors** → Telegram → **Connect** (or **Replace the key**) → paste it → **Seal and connect**. It is sealed to the gate's key and never shown again. 4. Press **Test connection**. The gate calls `getMe` with the token: "Works. Telegram answered 200 in … ms", or "Telegram refused the key (401)" (a mistyped or revoked token: replace it). 5. Add the bot to the chat or channel the app should post to (for a channel, as an admin). 6. Get the chat id: send any message in that chat, then open `https://api.telegram.org/bot/getUpdates` in your own browser and read `chat.id` (groups and channels are negative, like `-1001234567890`). Do this on your machine only; the token is in that address. ## Use it from an app Manifest: `{ "id": "telegram", "actions": ["sendMessage"], "scope": "#roastery" }` (the scope is the human name shown in the permission sentence). List only the actions the app calls. ```ts await ctx.connectors.telegram.sendMessage({ chatId: '-1001234567890', text: 'Beans below 5 kg' }) ``` The gate forwards the Bot API body (`chat_id`, `text`, `disable_notification`, `reply_markup`) to `POST https://api.telegram.org/bot/sendMessage` and replaces the token with `[redacted]` in the reply, including its percent-encoded form. ### Actions | Action | What it does | Input | |---|---|---| | `sendMessage` | A text message, with an optional inline keyboard | `{ chatId, text, silent?, inlineKeyboard? }` | | `sendPhoto` | A photo **by https URL** | `{ chatId, photoUrl, caption?, silent?, inlineKeyboard? }` | | `sendDocument` | A file **by https URL** | `{ chatId, documentUrl, caption?, silent?, inlineKeyboard? }` | | `editMessageText` | Changes the text (and buttons) of a message the bot sent | `{ chatId, messageId, text, inlineKeyboard? }` | | `answerCallbackQuery` | Answers a button press | `{ callbackQueryId, text?, showAlert? }` | All five are writes. The SDK checks Telegram's own limits before calling (text ≤ 4096 and caption ≤ 1024 characters, ≤ 8 buttons a row and 100 in all, `callbackData` 1 to 64 bytes, an https `url`, exactly one of the two per button), so a bad keyboard fails with `invalid_input` and nothing is sent. ```ts const sent = await ctx.connectors.telegram.sendMessage({ chatId, text: 'Approve order 42?', inlineKeyboard: [ [ { text: 'Approve', callbackData: 'ok:42' }, { text: 'Reject', callbackData: 'no:42' }, ], [{ text: 'Open the order', url: 'https://app.example.com/orders/42' }], ], }) await ctx.connectors.telegram.editMessageText({ chatId, messageId: sent.messageId, text: 'Order 42 approved', }) ``` A person's press arrives as a `callback_query` update with an id; the app answers it with `answerCallbackQuery({ callbackQueryId })`. (Receiving updates in an app, the webhook trigger, is planned. Qaldy's own approvals do receive them: see "Approvals and alerts in Telegram" below.) ### What the gate holds Telegram to - **Only these five Bot API methods are forwarded.** A policy that lists any other (`getUpdates`, `setWebhook`, `deleteMessage`, `banChatMember`…) is still refused: `403 egress_blocked`, reason `action_not_supported`. `setWebhook` or `getUpdates` could redirect or read the bot's whole inbox. - **A photo or document URL is egress.** Telegram's own servers fetch it, outside the gate's reach, so it could carry data to any host. The gate therefore requires the URL to be `https` on port 443 with a host **on the app's egress allowlist** (the manifest lists the domain and a person approves it): otherwise `403 egress_blocked` (`domain_not_allowed`, the host named in the audit row, the URL never recorded). `attach://…`, plain http, IP literals, userinfo and non-string values are refused (`media_not_allowed`). A Telegram **file id** (an earlier upload) passes: nothing is fetched. The gate forwards the body it parsed (re-serialised), so a key sent twice can't slip a second, unchecked URL past it. - Message text can still send data to the chat the app names; that is what the connector is for, and the manifest sentence says which chat. ## Approvals and alerts in Telegram (Preview) Qaldy can use the same bot to put your team's approvals on a phone, send alerts to a chat and answer a few questions. **Preview:** built against Telegram's Bot API documentation and a fake Bot API in tests; the first run against a live bot may need a fix. Turn it on in **Settings → Telegram**. **Use a bot of its own for this.** A bot has exactly one webhook. Qaldy sets it so Telegram sends messages and button presses to your workspace; if the bot already sends somewhere else (an automation's Telegram trigger, n8n, another tool), Qaldy says so and asks before replacing that. Make a second bot with @BotFather and connect it as a second Telegram connection. ### Turn it on (an admin) 1. Connect the bot on **Connectors → Telegram** (above). 2. **Settings → Telegram → Turn on Telegram**, pick the connection. Qaldy asks the bot who it is (`getMe`), then sets its webhook to `https:///v1/telegram/` with a fresh `secret_token`. The token stays sealed at the gate throughout; the API keeps only a peppered digest of the `secret_token`. 3. **Check delivery** asks Telegram how deliveries are going (pending updates, the last error). **Turn off Telegram** removes the webhook; people stay linked. ### Link your account (everyone) 1. Settings → Telegram → **Link my Telegram** shows a one-time code (`QLD-` and eight characters, ten minutes, once) and an **Open in Telegram** link. 2. Press Start in the bot's chat (the link sends `/start ` for you), or send the code in a **private** chat. A code posted in a group is cancelled on sight, because anyone there could use it. 3. The page notices by itself. **Send me a test approval** sends a message shaped like a real one; its buttons go through every check a real press does, then decide nothing and say so. **Unlink** removes the link. A Telegram account is one person per workspace. Linking proves one thing: whoever sent the code is whoever held the console session that made it. From then on the bot knows your messages by Telegram's own `from.id`, and asks Qaldy fresh, every time, whether you are still a member. ### Approvals When an approval is asked of you, the bot sends a message with the approval's title, the app, who asked, what the run already did and what happens next if you approve, with **Approve** and **Reject** buttons and a link to the run. Pressing a button: - is accepted only from the Telegram account linked to the person it was asked of, in the chat the message was sent to, on a message Qaldy sent (the button data is signed for the workspace); - decides the approval exactly as a click in the console would (`approval.decide`, with `via: telegram` in the audit row), and the waiting run carries on; - is safe to press twice, or after someone decided in the console: the second answer is "already approved/rejected". The message is rewritten to say how it ended and loses its buttons, whoever decided and however it ended (also when it ran out of time). A message that could not be sent is retried a minute later, three attempts in all. The approval itself is never touched by a failed send: it can always be decided in the console. ### Commands In your private chat with the bot, answered with your own permissions: | Command | Answers | |---|---| | `/approvals` | what is waiting for you, as messages with the buttons | | `/runs` | the latest runs of the apps you can open | | `/status ` | how one app is doing and when it last ran (apps you cannot open are "not found") | | `/cap` | this month's use against your plan; spend figures only for admins | | `/alerts` | (admins) send the workspace's alerts to this chat; `/alerts off` stops it | | `/unlink` | stop using this chat for Qaldy | In a group the bot answers `/alerts` (from a linked admin) and nothing else. The bot answers in the language you linked in (English, Russian or Kazakh) and writes plain text only. ### Alerts and the digest Every alert rule keeps its own places. The workspace's **ops chat** hears them as well: error-rate, quiet and budget alerts, and heartbeat alerts for automations. Choose it by sending `/alerts` in a chat the bot is in (an admin, in a group or in your own chat), or type a chat id or `@channel` in Settings (the bot writes a test message first, and nothing is saved if it cannot). **Send the weekly digest there too** adds a short version of the Monday digest; the email is unchanged. Anyone in the ops chat can read these alerts, so choose it deliberately. ### When it breaks | You see | Why | Do | |---|---|---| | Needs attention, "Telegram would not take the address" | The address is not https or not reachable from the internet | Fix `QALDY_API_PUBLIC_URL`, then Turn on again | | Check delivery shows pending updates and a 401 | The webhook secret changed (Telegram still has an old one) | Turn on again | | "This bot already sends its messages to …" | The bot has another webhook | Use a separate bot, or confirm the replacement | | The bot says "isn't linked" | That Telegram account never sent a code | Link it in Settings | | The bot does not answer in a group | A bot in a group only sees commands, and answers `/alerts` only | Send `/alerts` | ## When it breaks | You see | Why | Do | |---|---|---| | Expired — reconnect | Telegram answered 401/404 to a test | Replace the key with a fresh token from @BotFather (`/token`) | | `chat not found` from the app | The bot isn't in that chat, or the id is wrong | Add the bot, re-read `chat.id` | | `connector_not_connected` | No token stored | Connect it on /connectors | | Blocked: `action_not_declared` | The app's manifest doesn't list `sendMessage` | Add it and publish a new version | The City shows every call as a parcel between the app and the Telegram gate; failures and blocks drop the gate's barrier.