Heartbeat Telemetry
Monitor can receive lightweight application-level heartbeats from your bot. This provides a second liveness signal alongside Discord presence and helps the dashboard identify processes that have stopped reporting.
Shows whether the bot's Discord gateway connection is online. Presence alone cannot prove that your application code is still executing normally. Outage alerts and downtime records are driven by presence.
Confirms that your bot is actively running application code and can make outbound HTTP requests to Monitor. Heartbeat telemetry is informational only.
Keep your token secret
Step 1 — Generate your token
- Open the Monitor dashboard for your server.
- Select your monitored bot.
- Locate the Heartbeat Telemetry section.
- Click Get Heartbeat Token and copy the token shown once.
- Store it as
MONITOR_HEARTBEAT_TOKENin the monitored bot's hosting environment. - Restart or redeploy the monitored bot so it can read the new environment variable.
MONITOR_HEARTBEAT_TOKEN=mb_live_your_secret_token_hereStep 2 — Install aiohttp
Monitor's example uses aiohttp for a lightweight async HTTP client.
pip install -U aiohttpStep 3 — Basic Heartbeat Integration
For non-sharded bots, simply submit the required bot_id and interval_seconds. Non-sharded bots omit the shards field entirely.
import os
import logging
import aiohttp
from discord.ext import commands, tasks
log = logging.getLogger("monitor.heartbeat")
MONITOR_HEARTBEAT_URL = "https://botmonitor.pages.dev/api/public/heartbeat"
class MonitorHeartbeat(commands.Cog):
"""Basic heartbeat for non-sharded bots (omits the shards array)."""
def __init__(self, bot: commands.Bot):
self.bot = bot
self.session: aiohttp.ClientSession | None = None
async def cog_load(self):
self.session = aiohttp.ClientSession(
timeout=aiohttp.ClientTimeout(total=10)
)
self.heartbeat_loop.start()
async def cog_unload(self):
self.heartbeat_loop.cancel()
if self.session and not self.session.closed:
await self.session.close()
@tasks.loop(seconds=60)
async def heartbeat_loop(self):
token = os.getenv("MONITOR_HEARTBEAT_TOKEN")
if not token or not self.bot.user or not self.session:
return
payload = {
"bot_id": str(self.bot.user.id),
"interval_seconds": 60,
}
try:
async with self.session.post(
MONITOR_HEARTBEAT_URL,
headers={"Authorization": f"Bearer {token}"},
json=payload,
) as response:
if response.status != 200:
log.warning(
"Monitor heartbeat failed: HTTP %s",
response.status,
)
except Exception:
log.exception("Monitor heartbeat request failed")
@heartbeat_loop.before_loop
async def before_heartbeat(self):
await self.bot.wait_until_ready()
async def setup(bot: commands.Bot):
await bot.add_cog(MonitorHeartbeat(bot))
Shard Telemetry
Automatic Detection
shards array in its heartbeat request.Monitor does not determine shard health independently; the monitored bot reports the shard state. Shard telemetry is informational only and will not trigger the normal bot offline/outage system, downtime records, grace periods, or outage pings.
Exact Shard JSON Payload
When reporting shards, include the shards array inside your standard heartbeat payload:
{
"bot_id": "123456789012345678",
"interval_seconds": 60,
"shards": [
{
"shard_id": 0,
"status": "healthy",
"latency_ms": 42,
"guild_count": 150
},
{
"shard_id": 1,
"status": "healthy",
"latency_ms": 51,
"guild_count": 147
}
]
}Supported Shard Fields
| Field | Type | Required | Description & Validation |
|---|---|---|---|
| shard_id | integer | Required | Zero-indexed shard ID (integer from 0 to 1000). Must be unique within the payload. |
| status | string | Required | Must be one of: healthy, degraded, or disconnected. |
| latency_ms | integer | null | Optional | Round-trip WebSocket latency in milliseconds (0 to 10000). Pass null if disconnected. |
| guild_count | integer | null | Optional | Number of Discord servers assigned to this shard (0 to 10,000,000). Pass null if unknown. |
Supported Shard Statuses
Shard WebSocket is fully connected, authenticated, and handling Discord gateway heartbeats normally.
Shard is experiencing high latency, reconnect loops, backoff delays, or partial packet loss.
Shard gateway connection dropped, failed to resume, or is currently closed.
AutoShardedBot discord.py Example
Unlike basic heartbeats, this sharded example inspects bot.latencies and guild assignments to build and transmit the shards array.
import os
import logging
import aiohttp
from discord.ext import commands, tasks
log = logging.getLogger("monitor.heartbeat")
MONITOR_HEARTBEAT_URL = "https://botmonitor.pages.dev/api/public/heartbeat"
class MonitorShardedHeartbeat(commands.Cog):
"""Sharded heartbeat for AutoShardedBot (includes the optional shards array)."""
def __init__(self, bot: commands.AutoShardedBot):
self.bot = bot
self.session: aiohttp.ClientSession | None = None
async def cog_load(self):
self.session = aiohttp.ClientSession(
timeout=aiohttp.ClientTimeout(total=10)
)
self.heartbeat_loop.start()
async def cog_unload(self):
self.heartbeat_loop.cancel()
if self.session and not self.session.closed:
await self.session.close()
@tasks.loop(seconds=60)
async def heartbeat_loop(self):
token = os.getenv("MONITOR_HEARTBEAT_TOKEN")
if not token or not self.bot.user or not self.session:
return
shards = []
for shard_id, latency in self.bot.latencies:
shard_guilds = sum(
1
for guild in self.bot.guilds
if guild.shard_id == shard_id
)
status = "healthy" if latency >= 0 else "disconnected"
latency_ms = round(latency * 1000) if latency >= 0 else None
shards.append(
{
"shard_id": shard_id,
"status": status,
"latency_ms": latency_ms,
"guild_count": shard_guilds,
}
)
payload = {
"bot_id": str(self.bot.user.id),
"interval_seconds": 60,
"shards": shards,
}
try:
async with self.session.post(
MONITOR_HEARTBEAT_URL,
headers={"Authorization": f"Bearer {token}"},
json=payload,
) as response:
if response.status != 200:
log.warning(
"Monitor heartbeat failed: HTTP %s",
response.status,
)
except Exception:
log.exception("Monitor heartbeat request failed")
@heartbeat_loop.before_loop
async def before_heartbeat(self):
await self.bot.wait_until_ready()
async def setup(bot: commands.AutoShardedBot):
await bot.add_cog(MonitorShardedHeartbeat(bot))
Endpoint Specification & Response Codes
The public heartbeat endpoint accepts POST requests with JSON bodies.
POST https://botmonitor.pages.dev/api/public/heartbeat
Authorization: Bearer <MONITOR_HEARTBEAT_TOKEN>
Content-Type: application/json
| Status | Response Payload | Description |
|---|---|---|
| 200 OK | {"ok": true, "bot_id": "...", "status": "healthy", "timestamp": "..."} | Heartbeat and shard telemetry successfully recorded. |
| 400 Bad Request | {"error": "<validation message>"} | Invalid JSON body, missing bot_id, interval outside 10–300s, or invalid shard fields. |
| 401 Unauthorized | {"error": "Missing heartbeat authentication token"} | Missing or malformed Authorization header or token. |
| 403 Forbidden | {"error": "Heartbeat token is invalid or has been revoked"} | Token hash not found in database, token was revoked, or token belongs to a different bot_id. |
| 429 Too Many Requests | {"error": "Rate limit exceeded. Heartbeat interval should be 30-60s."} | Heartbeat sent faster than the 3-second rate limit threshold. |
Health States
Heartbeat age is at or below 1.5× the configured interval. A token exists and the bot is reporting normally.
Heartbeat age is above 1.5× but at or below 2.5× the configured interval.
Heartbeat age has exceeded 2.5× the configured interval. Zombie status is dashboard telemetry only; it does not create an outage, downtime record, or public outage ping by itself.
