Why connect MikroTik to Telegram?

Because you read alert emails the next morning, but you read phone messages immediately. A router that writes “the main line is down” or “there is a rogue DHCP server on the LAN” while it is happening is worth more than ten graphs reviewed the day after.

In this how-to, we build one thing: a telegram script that sends a message. Once it exists, all the other Dojo how-tos that need to notify someone use it, from Netwatch to the PoE watchdog: one line and the router writes to you.

Diagram: MikroTik Telegram script calling api.telegram.org, the bot forwarding the message to the phone
The router script calls the Telegram API over HTTPS; the bot delivers the message to the phone.

Step 1: how do I create the bot?

Messages are sent by a bot, an automatic Telegram account. You create it by talking to another bot, the official one from Telegram:

  1. on Telegram, search for @BotFather and send it /newbot;
  2. give the bot a name (for example “Office Router”) and a username ending with bot (for example routerufficio_bot);
  3. BotFather replies with the token, a string like 123456789:AAH…. Copy it.

A tip from the field: if you make a typo in the name or the username, do not edit the message, send a new one. BotFather only reads new messages and, if you modify one, it gets lost halfway through the procedure and replies with a generic error. When in doubt, /cancel and start over with /newbot.

⚠️ Warning: the token is the bot’s password. Whoever has it can send messages on its behalf. If it ends up where it should not, BotFather generates a new one and the old one stops working.

Step 2: who should the bot write to?

The bot writes to a chat, identified by a number, the chat_id. To find it:

  1. open the chat with your bot, press Start and send it any message, for example “hi” (a bot cannot write first to someone who has never written to it);
  2. from the browser, open https://api.telegram.org/botTOKEN/getUpdates, replacing TOKEN with your token;
  3. in the response, look for "chat":{"id":: the number that follows is your chat_id.

Do you want alerts in a group, for example the technicians’ group? Add the bot to the group, write a message in the group and repeat step 2: the chat_id of a group is a negative number, for example -1001234567890.

Step 3: can the router reach Telegram?

The router must resolve api.telegram.org and go out to the internet over HTTPS. A quick test:

/ping api.telegram.org count=3

If it responds, you are ready. If it cannot resolve the name, check the router’s DNS (/ip dns print).

Step 4: how do I create the Telegram script?

A script containing the “engine” of the sending. Put your token and your chat_id in place of the examples:

/system script add name=telegram policy=read,write,test source={
:local token "123456789:AAH-esempio-del-token"
:local chat "987654321"
:local testo ([/system identity get name] . ": " . $1)
/tool fetch url=("https://api.telegram.org/bot" . $token . "/sendMessage") http-method=post \
    http-header-field="Content-Type: application/json" check-certificate=yes \
    http-data=[:serialize to=json value={"chat_id"=$chat; "text"=$testo} options=json.no-string-conversion] \
    output=none
}

What it does, line by line:

  • $1 is the text you pass to it; it prepends the router’s name (/system identity), so when you have twenty routers, you immediately know which one is sending the message;
  • /tool fetch calls the Telegram API over HTTPS; check-certificate=yes verifies that the other end is actually Telegram, using the certificates already present in RouterOS;
  • :serialize to=json builds the message in JSON and automatically handles quotes, slashes, and accents, which would break everything if written manually. The json.no-string-conversion option prevents chat_id from being converted into a number with decimals (without it, "987654321" becomes 987654321.000000).

The /tool fetch and all its parameters are documented in the MikroTik manual: Fetch.

Step 5: how do I send the first message?

You turn the script into a function with :parse and then call it. From the terminal, everything goes inside the braces (a :local variable lives only within its block):

{
:local telegram [:parse [/system script get telegram source]]
$telegram "ciao, sono il router"
}

A message like Router Ufficio: ciao, sono il router arrives on your phone. You can also compose the text on the fly:

$telegram ("RouterOS " . [/system resource get version] . ", uptime " . [/system resource get uptime])

These same two lines go into any script that needs to notify you: a scheduler, a Netwatch, or a DHCP alert. You can find how :parse and functions work in the MikroTik manual: Scripting.

⚠️ Warning: why not a global function? It might seem more convenient to define $telegram once with :global. I tried it: it works from the terminal, but in Netwatch scripts the global function appears empty and the message is not sent, with no error in the log. With :parse, each script loads its own copy at runtime and works everywhere, even immediately after a reboot.

Step 6: how do I get notified when the router reboots?

An unrequested reboot is always news: power outage, a crash, or someone unplugging the device. A scheduler at startup tells you:

/system scheduler add name=telegram-avvio start-time=startup on-event={
    :delay 30s
    :local telegram [:parse [/system script get telegram source]]
    $telegram ("riavviato, RouterOS " . [/system resource get version])
}

The :delay 30s gives the router time to establish internet connectivity. In the lab, after a reboot, the message arrived in less than a minute:

CHR: riavviato, RouterOS 7.24.5 (stable)

Step 7: how do I know if something is wrong?

Each send leaves a line in the log:

/log print where topics~"fetch"
  • Download from api.telegram.org FINISHED: the message was sent;
  • FAILED: ERROR parsing http: 401 …: the token is incorrect or has been revoked;
  • FAILED: Status 400, Bad Request: the chat_id is incorrect, or you have never written to the bot from the chat;
  • no line: the script did not run. Check that the script is named exactly telegram and, from the terminal, that you placed the lines inside the braces.

What do I check before putting it into production?

The token is written in the script, and it appears in two places you might not expect. I verified this: /system script export reports it in full, and when you add or modify the script, the system log also records the entire source code, including the token. Therefore:

  • exports and logs you share, for support or on a forum, must be cleaned of the token; if you send logs to an external syslog, remember that the token is there too;
  • access to the router must be protected: follow the Basic Hardening of a MikroTik Router;
  • one bot per client, not one for all: if a token leaks, you only need to regenerate that one.

Now the router knows how to write: in other Dojo how-tos, when a notification is needed, the two lines from Step 5 are sufficient.

Tested in the lab on PNETLab with CHR RouterOS 7.24.5 (stable) and a real bot: creation via BotFather, chat_id from getUpdates, message received on the phone (with accents), JSON with quotes and slashes, message on startup after a reboot, 400 and 401 errors in the log, token visible in the export.

Frequently asked questions

How does a MikroTik router send a Telegram message?

Using /tool fetch towards the Telegram bot API, method sendMessage, passing the chat_id and the text in JSON. You need a bot created with @BotFather and its token.

How do I find the chat_id?

Send a message to the bot, then open https://api.telegram.org/bot<token>/getUpdates: the number after "chat":{"id": is the chat_id. For a group, it is a negative number.

Why is the message not arriving?

Check /log print where topics~"fetch": 401 means wrong token, 400 means wrong chat_id or the bot has never been contacted. If there is no line at all, the script did not start.

Why does the global function $telegram not work in Netwatch?

Because Netwatch scripts do not see global functions created by another session: the variable is empty and the message is not sent. Loading the script with :local telegram [:parse [/system script get telegram source]] makes it work in any context.

Can I send messages with accents and quotes?

Yes, if you build the JSON with :serialize to=json: RouterOS automatically escapes quotes and slashes and lets accents pass through.