Uptime checksHTTP, keyword and TLS checks on your addresses, with the evidence of every failure kept.
Cron heartbeatsOne curl line at the end of a job. Silence past the grace window is the alert.
Status pagesA page your customers read, on your own domain, with the incident in plain words.
ExplanationsA written reason with the alert: what failed, what the checks saw, what to look at first.
on every plan Alerts to six channelsImportTeams
APIRead, pause, acknowledge and post updates from your own scripts.
MCP serverAsk your AI assistant what is down, then acknowledge or post an update.
WebhooksSigned JSON when an incident opens, is acknowledged or recovers.
Heartbeat guidescrontab, GitHub Actions, Railway, Node, Go, WordPress and Kubernetes.
DocsShort guides, each one task, with the lines to copy.
GuidesWhy monitors send false alarms, why cron jobs stop, and the fix.
ChangelogThe big things we shipped, newest first.
Trust and securityWhere your data lives, who processes it, how long we keep it.
Referral program soonRecommend Upglory to another team. In the works.
Contact
Docs menu Heartbeats / Node

Monitor a Node job.

One fetch after the work, with a timeout. Five minutes, start to finish.

5 minute read Guide

Before you start

You need a heartbeat in Upglory with its period and grace set to match the schedule that starts the script, whatever starts it. Its page shows the ping address; pass it to the script as UPGLORY_PING_URL.

Settings that match this schedule

  • Period6 hoursThe schedule the script is started on, whatever starts it.
  • Grace15 minutesThe longest run you have seen, plus a little.
  • Channelsemail, TelegramInherited from the workspace default. Change it on this heartbeat if it matters less than the others.

The line

Send the ping as the last thing the job does. If the work throws, the ping is never sent, which is exactly what you want.

node
// after the work, never before it
const PING_URL = process.env.UPGLORY_PING_URL;

async function ping(path = "") {
  await fetch(PING_URL + path, { signal: AbortSignal.timeout(10_000) });
}

await rebuildSitemap();
await ping();

AbortSignal.timeout is the part people leave out: without it a hung ping can outlive the job it was reporting on.

Measure the run time

Ping /start before the work. The ping after it then records how long the job ran, and a slow run is not mistaken for a missed one.

await ping("/start");
await rebuildSitemap();
await ping();

Report a failure

Ping /fail in the catch and throw again: you hear about the failure at once instead of at the end of the grace window, and the job still exits with its error.

try {
  await rebuildSitemap();
  await ping();
} catch (err) {
  await ping("/fail");
  throw err;
}

Test it

Use Send a test ping on the heartbeat's page, or run the script once by hand. Nothing alerts until the first ping arrives, so a job you have not deployed yet never raises an alarm.

What you will see if it stops

Fifteen minutes after the run was due, the heartbeat turns down. If the script pinged /fail instead, the alert arrives immediately and says the job reported a failure rather than going quiet, which are two different problems.

missed rebuild-sitemap
LATE due 12:00, grace ran out 12:15
last ping 06:02, ran 2 min 5 s
Email and Telegram told at 12:15

The other guides

Start free 20 heartbeats on Free, with no card