Build a Telegram bot

The end state: a bot that answers every message with a timestamped echo, running as a deployed app on one of your hosts. This is the full version of the first workflow, including the Telegram side.

1. Create the bot with BotFather

  1. In Telegram, open a chat with @BotFather.
  2. Send /newbot, pick a display name, then a username ending in bot.
  3. BotFather replies with the bot token (123456789:AA…). Treat it like a password.
  4. Optional but useful: /setdescription, /setuserpic.

To send yourself messages you’ll also want your chat id: message your new bot once, then visit https://api.telegram.org/bot<TOKEN>/getUpdates — the message.chat.id field is it.

2. Store the credential

In the Nucleus: Admin → Credentials → New, template Telegram bot. Fill in botToken (and chatId if you want a default). The token is encrypted at rest; node configs will carry ${credential.telegram-bot.botToken} — a reference, not the value.

3. The workflow

Three nodes — here is the real document the demo ships with (abridged to the parts that matter):

{
  "schemaVersion": "niner.workflow.v1",
  "name": "Telegram Echo",
  "nodes": [
    {
      "id": "tg-in", "type": "trigger.telegram", "label": "Bot updates",
      "config": {
        "token": "${credential.telegram-bot.botToken}",
        "mode": "polling", "pollTimeoutSeconds": 25
      }
    },
    {
      "id": "stamp", "type": "processing.datetime", "label": "Now (NY time)",
      "config": {
        "operation": "now", "format": "yyyy-MM-dd HH:mm:ss",
        "timezone": "America/New_York", "outputField": "receivedAt"
      }
    },
    {
      "id": "tg-out", "type": "output.telegram", "label": "Echo reply",
      "config": {
        "token": "${credential.telegram-bot.botToken}",
        "chatId": "${credential.telegram-bot.chatId}",
        "operation": "sendMessage",
        "text": "⏰ {{ $json.receivedAt }} — You said: {{ $json.message.text }}"
      }
    }
  ],
  "edges": [
    { "sourceNodeId": "tg-in", "sourcePort": "output", "targetNodeId": "stamp", "targetPort": "input" },
    { "sourceNodeId": "stamp", "sourcePort": "output", "targetNodeId": "tg-out", "targetPort": "input" }
  ],
  "triggers": [{ "nodeId": "tg-in" }]
}

Notes:

  • Polling mode needs no public URL — the trigger long-polls the Bot API (25 s timeout per poll). Webhook mode is available when you have a reachable HTTPS endpoint.
  • The reply’s text uses {{ }} expressions directly in the config — no script node.
  • Replying to the sender (rather than a fixed chatId) works too: {{ $json.message.chat.id }}.

4. Test in Studio

Test workflow, message the bot, watch the run light up. Open the trigger node’s output to see the full Telegram update envelope — handy for finding fields to use in expressions ($json.message.from.first_name, $json.message.chat.id, …).

5. Deploy it

Follow deploy to a host: pick a host, deploy in executable mode, and the bot becomes a standalone binary polling Telegram from that machine. The token is not in the artifact — the app leases it from the vault at each run start.

Going further

The Telegram nodes go well beyond echo: the output node covers send/edit/delete/pin, media uploads (photo, document, audio, video — including binary input from upstream nodes), locations, media groups, chat management, callback-query answers and binary-safe file downloads. See the Telegram node reference and Telegram Trigger reference.