How to monitor your bot's uptime and get alerts

Have your bot send a heartbeat to an external monitor every minute. When the signal stops, the monitor sends an alert to Discord or Telegram.

Updated 6 min read

Learning objectives

After reading this article you will be able to:

  • Understand why the monitor must live outside the bot's machine
  • Set up a heartbeat in Node.js and in Python
  • Get down and recovery alerts on Discord and Telegram
On this page

Every bot goes down eventually: a token gets revoked, memory runs out, a library throws an exception nobody handled. The problem is finding out too late, when someone in the server tells you the bot has been "offline" for hours.

Uptime monitoring makes that discovery automatic. The setup below works for any bot without an HTTP port, with code in Node.js (discord.js v14) and Python (discord.py 2.x) and alerts on Discord and Telegram. If your bot is offline right now, see why a Discord bot goes offline.

Botonly if isReady()Monitoranother machineheartbeat every minutesignal stopsping: bot has no portalertDiscordwebhookTelegramchat
Whoever reports the outage sits outside: the bot only emits the signal, and the monitor alerts when it stops arriving.

Why can't the bot tell you it went down?#

A dead process can't run any code. That holds for a fatal exception, for the operating system killing the process when it runs out of memory, and for a whole server that lost power or network. In every one of these cases, your bot's console.log and try/catch can't help.

The rule: whatever reports the outage has to live outside the bot. The bot emits a periodic signal, and a monitor on another machine notices when the signal disappears. This pattern is called a heartbeat (also a push monitor or dead man's switch).

There are two kinds of check, and bots almost always need the second:

Type How it works Good for
Active (ping/HTTP) The monitor requests a URL or port on your app Websites, APIs, Telegram webhooks
Heartbeat (push) Your app requests a monitor URL at every interval Gateway bots, workers, cron jobs

Discord bots keep an outbound WebSocket connection to the Gateway and listen on no port, so there is nothing to test from outside. The heartbeat works around that.

Which external monitor should you use?#

Any service that offers a heartbeat monitor and webhook or Telegram notifications will do. Two common paths:

  • Uptime Kuma: an open source monitor you host yourself. It has a Push monitor type with a URL shaped like /api/push/<token>, and notifications to Discord and Telegram. The project's documentation describes that API as internal, with no stability guarantee. Kuma also has to run outside the bot's machine, or it goes down with it.
  • Hosted service: UptimeRobot, for example, has a no-cost plan for personal and non-profit projects, with limits on the number of monitors and the check interval. Heartbeat monitors and some alert integrations may be paid-plan only, so check the pricing page before choosing.

Either way, the process is the same: create a heartbeat monitor, copy the URL it generates, and set up the alert channel.

How do you send a heartbeat from a Node.js bot?#

Keep the URLs in environment variables, never in code. With "type": "module" in package.json and discord.js v14:

js
// monitor.js
const HEARTBEAT_URL = process.env.HEARTBEAT_URL; // full monitor URL
const ALERT_WEBHOOK_URL = process.env.ALERT_WEBHOOK_URL; // channel webhook

export async function alert(text) {
  try {
    await fetch(ALERT_WEBHOOK_URL, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ content: text.slice(0, 2000) }),
      signal: AbortSignal.timeout(10_000),
    });
  } catch (err) {
    console.error("Failed to send alert:", err);
  }
}

export function startHeartbeat(client) {
  setInterval(async () => {
    if (!client.isReady()) return; // no Gateway, no signal
    try {
      await fetch(HEARTBEAT_URL, { signal: AbortSignal.timeout(10_000) });
    } catch (err) {
      console.error("Heartbeat failed:", err);
    }
  }, 60_000);
}
js
// index.js
import { Client, Events, GatewayIntentBits } from "discord.js";
import { alert, startHeartbeat } from "./monitor.js";

const client = new Client({ intents: [GatewayIntentBits.Guilds] });

client.once(Events.ClientReady, (c) => {
  console.log(`Online as ${c.user.tag}`);
  startHeartbeat(c);
  alert(`Bot started: ${c.user.tag}`);
});

client.on(Events.Error, (err) => alert(`Client error: ${err.message}`));
client.on(Events.ShardDisconnect, (event, id) =>
  alert(`Shard ${id} disconnected (code ${event.code})`),
);

process.on("unhandledRejection", (err) => alert(`Unhandled rejection: ${err}`));

client.login(process.env.DISCORD_TOKEN);

The key piece is client.isReady(): the heartbeat only goes out when the bot is actually connected to the Gateway. If the process is alive but the bot lost its session and never reconnected, the signal stops and you get alerted. A heartbeat that always fires, even when disconnected, only proves the process exists.

In recent discord.js v14 releases the ready event was renamed to clientReady, and Events.ClientReady points to the right name for the installed version. The old form still shows up in the v14 docs, but marked as deprecated.

What about Python (discord.py 2.x)?#

python
import os
import aiohttp
import discord
from discord.ext import tasks

HEARTBEAT_URL = os.environ["HEARTBEAT_URL"]

class Bot(discord.Client):
    async def setup_hook(self):
        self.session = aiohttp.ClientSession()
        self.heartbeat.start()

    @tasks.loop(seconds=60)
    async def heartbeat(self):
        if not self.is_ready():
            return
        try:
            timeout = aiohttp.ClientTimeout(total=10)
            async with self.session.get(HEARTBEAT_URL, timeout=timeout):
                pass
        except Exception as err:
            print("Heartbeat failed:", err)

    async def close(self):
        self.heartbeat.cancel()
        if getattr(self, "session", None):  # setup_hook may not have run
            await self.session.close()
        await super().close()

bot = Bot(intents=discord.Intents.default())
bot.run(os.environ["DISCORD_TOKEN"])

Same logic: the signal only goes out when is_ready() is true.

How do you get the alert on Discord or Telegram?#

There are two kinds of alert, and each takes a different path:

  1. Outage detected by the monitor (the signal stopped): set the notification channel in the monitor itself. On Discord, create a webhook in the channel settings under Integrations and paste its URL. On Telegram, create a bot with BotFather, add it to the chat and enter the token and the chat_id.
  2. Events the bot can see (error, shard disconnect, restart): your code calls the webhook itself, like the alert function above. The Discord webhook accepts a JSON POST and returns 204 No Content on success.

To send the same alert to Telegram straight from code, the Bot API accepts JSON on sendMessage:

js
export async function alertTelegram(text) {
  const url = `https://api.telegram.org/bot${process.env.TELEGRAM_TOKEN}/sendMessage`;
  await fetch(url, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ chat_id: process.env.TELEGRAM_CHAT_ID, text }),
  });
}

Use a dedicated channel or chat for alerts. Mix them with regular chat and people mute the channel, so the down alert goes unnoticed.

How do you check that monitoring works?#

Test the outage before you need it:

  1. Start the bot and confirm the monitor shows "up".
  2. Stop the process and wait for the configured interval plus the tolerance.
  3. Confirm the alert reached the right channel, then restart the bot and check that the "recovered" message arrives.

Common mistakes:

  • Monitor on the same machine as the bot: if that machine goes down, the monitor goes with it and nobody is notified.
  • Heartbeat that ignores the bot's state: if the signal comes from a loop that does not know whether the Gateway is connected, it keeps firing while the bot is mute. Tie the signal to isReady(), as above.
  • Monitor interval shorter than the bot's: the monitor alerts constantly. Keep the monitor interval equal to or longer than the bot's and allow at least one missed beat.
  • Exposed webhook: anyone with the URL can post in your channel. Keep it in an environment variable and generate a new one if it leaks.
  • Alerts without context: include the bot name and the shard in the message, or you will not know which one went down.

How to do this on Square Cloud#

On Square Cloud, your bot runs in an isolated, always-on container, with automatic restart (on by default for dashboard deploys) when the process exits with an error. That reduces outages (see how to keep a bot online 24/7), but you still need to know when they happen. An invalid token or a code error can send the bot into a crash loop, and automatic restart does not fix the cause. Running out of memory stops the application until you raise its RAM and start it again.

Outage alerts still come from the external heartbeat monitor, with the alert channel configured there. The code in this article runs unchanged: set HEARTBEAT_URL, ALERT_WEBHOOK_URL and DISCORD_TOKEN as application environment variables in the dashboard, the API, the CLI or the VS Code extension. The change takes effect on the next restart.

When an alert arrives, live logs help you investigate: they are in the dashboard, the CLI, VS Code and the mobile app. CPU, RAM and network metrics are recorded every 5 minutes and kept for 24 hours, so a memory spike near the time of the outage shows up there. For a method to read them, see how to read your app's logs.

The libraries and features supported for Discord, Telegram and WhatsApp are listed in bots on Square Cloud.

Bot hosting

Keep your bot online 24/7 with automatic restarts

Learn more

FAQs

Why can't the bot just tell me when it goes down?

A bot that is down cannot report anything. A dead process, a powered-off server or a network outage are all silent. The down alert has to come from an external monitor that notices the missing signal.

Can I monitor a bot that has no website or HTTP port?

Yes. Discord, Telegram and WhatsApp bots usually only open outbound connections, so there is nothing to probe from outside. A heartbeat (push) monitor solves this: the bot calls a monitor URL at regular intervals.

What heartbeat interval should I use?

One minute is a good default. Set the monitor to tolerate one or two missed beats before alerting, so a single network hiccup does not trigger a false alarm.

Can the Discord alert come from a webhook?

Yes. A channel webhook accepts a JSON POST with a content field of up to 2000 characters. Paste the webhook URL into the monitor's notification settings or call it directly from your code.

Next articleHow to make your first Discord bot and put it onlineTo make a Discord bot, register an app in the Developer Portal, write the code with discord.js, invite the bot to your server and host the process. Full walkthrough.