Know the moment your cron job dies.
One HTTP ping per run. Miss a beat and we email, Telegram, Slack, Discord,
or webhook you within seconds. Built for PHP shops, but every language
that can curl works the same.
$ 0 * * * * /usr/bin/php /app/bin/console hourly:rollup && \
curl -fsS -m 10 --retry 5 https://cronheart.com/ping/<uuid>
Three steps. No agents, no daemons.
Add one curl to your cron line. We watch the schedule and page you when something dies.
Curl on success
Your job pings /ping/<uuid> after a successful run. POST stdout if you want it captured.
We watch the clock
Every 30 seconds the scanner compares "next expected" against now. Past the grace window we mark you late.
Alerts fan out
Email, Telegram, Slack, Discord, signed webhook — whatever you wired up to the project, in parallel.
Everything an SRE expects.
Nothing they don't.
Drop-in HTTP API
One POST /ping/<uuid> per run, with optional /start and /fail for duration and error tracking. Captures up to 10KB of stdout per run.
Cron, interval, simple
Validate any cron expression with a live "next 5 runs" preview. Or use plain intervals — "every 6 hours" — when you don't need the full grammar.
Multi-channel alerts
Email, Telegram, Slack, Discord, signed webhooks. Anti-flap dedupe so a bad run doesn't page you ten times before the recovery clears it.
Status badges
Drop a shields.io-style SVG into your README to show the world your nightly-rollup hasn't flatlined since 2024.
Timezones, done right
Schedules stored in UTC, displayed in your timezone, DST-safe. We've put the corner cases through unit tests so you don't have to.
Drop-in HTTP ping
One curl per run from your cron, systemd timer, Symfony Scheduler, or Laravel Scheduler. Five-minute setup, no SDK required.
Wire it up in any language
If it can curl, it can ping Cronheart.
UUID=your-monitor-uuid
BASE=https://cronheart.com/ping/$UUID
curl -fsS -m 10 --retry 5 "$BASE/start"
if /usr/bin/php bin/console hourly:rollup; then
curl -fsS -m 10 --retry 5 "$BASE/success"
else
curl -fsS -m 10 --retry 5 "$BASE/fail"
fi
$base = "https://cronheart.com/ping/{$uuid}";
file_get_contents("{$base}/start");
try {
$app->run();
file_get_contents("{$base}/success");
} catch (\Throwable $e) {
$ctx = ['http' => ['method' => 'POST', 'content' => $e->getMessage()]];
file_get_contents("{$base}/fail", false, stream_context_create($ctx));
throw $e;
}
import requests, sys
base = f"https://cronheart.com/ping/{uuid}"
requests.get(f"{base}/start", timeout=5)
try:
run_my_job()
requests.get(f"{base}/success", timeout=5)
except Exception as e:
requests.post(f"{base}/fail", data=str(e), timeout=5)
sys.exit(1)
const base = `https://cronheart.com/ping/${uuid}`;
await fetch(`${base}/start`);
try {
await runJob();
await fetch(`${base}/success`);
} catch (err) {
await fetch(`${base}/fail`, { method: 'POST', body: String(err) });
throw err;
}
Simple, predictable pricing
Free for 20 monitors · $5/mo for 50 · $19/mo for 200 · $49/mo for 1000.
No card on the free tier. Same product on every tier — we only meter monitor count.
Stop discovering broken crons from angry users.
Two minutes to your first monitor, zero dependencies installed on your servers.