You ship a SaaS. One of your cron jobs processes nightly reports — invoices, analytics summaries, data cleanup. It runs at 2am. Most nights it works. Two weeks in, it silently fails. No error email. No logs surface. Users expect their reports Wednesday morning. Nothing arrives. You don't know until a customer emails: "Where's my invoice?" Now you're debugging at 9am with an angry customer watching.
This is the silent-failure catastrophe. Your app isn't down (uptime looks green). No exceptions hit Sentry. The job just didn't run. Scheduled jobs are the most dangerous thing in production because they're invisible — you can't test them in staging without mocking time, and when they fail, nobody screams until hours later.
The fix: two-part monitoring. (1) Uptime checks on key API endpoints (so you know if your site is actually responding). (2) Heartbeat pings from inside cron jobs (so you know if scheduled work ran). Together they catch 99% of production nightmares before customers do.
Why Silent Cron Failures Cost the Most
Most production failures surface fast. API is down, users can't log in, Sentry screams. Someone gets paged in 30 seconds. Cron failures don't work that way. The job doesn't run, but your app doesn't know. No exception, no alert, nothing. Hours pass. Users file support tickets. You investigate logs and find the job was never even enqueued. Time lost: 4 hours. Customer frustration: maximum. Your credibility: damaged.
The core problem: cron jobs run outside your normal error handling. If a cron crashes, where does the error go? Database logs? A sidecar service? Nowhere? You can't assume. You have to explicitly instrument it. That's where heartbeats come in — you ping a monitoring service every time the job finishes. If you stop pinging, the monitor alerts you. Dead-simple pattern, prevents most disasters.
Three Monitoring Patterns
Pattern 1: Endpoint Uptime Checks
Every 30 seconds, BetterStack pings your API from multiple locations (US, EU, Asia). Your endpoint responds with HTTP 200. Check passes. If 3 checks in a row fail, BetterStack sends you a Slack alert: "API is down." This is tablestakes for any SaaS. Your site could be on fire and you'd know in 90 seconds.
Setup: Add an endpoint that returns `{ status: 'ok' }`. Point BetterStack at it. Done. Cost: included in any BetterStack plan.
Pattern 2: Heartbeat from Cron (Dead-Man's Switch)
Your cron job pings a heartbeat URL once per run. "I just finished billing sync." BetterStack tracks the last ping timestamp. If the timestamp stops advancing (e.g., job was supposed to run every 24h but it's now 30h since last ping), BetterStack assumes the job is dead and alerts you. This is the pattern that catches silent failures.
Setup: Run a single HTTP request at the end of your cron logic. If that request fails, the job knows it failed to report. Immediate alert.
Pattern 3: Synthetic Transactions
Beyond "is the API up?", run a real transaction: create an account, add an item, submit payment, verify confirmation email arrives. BetterStack can be configured to do this every 10 minutes. If any step fails, you're alerted. This catches bugs that only appear under load or specific data states. Expensive, but critical for payment flows.
Setup: Write a script that mimics a user journey. Upload it to BetterStack. It runs on their infrastructure.
BetterStack Uptime + Heartbeat Setup
Step 1: Create an Uptime Check
Log into BetterStack → Uptime → New Monitor. Enter your API endpoint: `https://your-api.com/health`. Set frequency to "every 30 seconds". Enable Slack alerts. Save. Within 2 minutes, the first check runs and you'll see green or red.
Step 2: Create a Heartbeat Monitor
Log into BetterStack → Uptime → New Monitor → Heartbeat. You get a unique URL that looks like `https://uptime.betterstack.com/api/v1/heartbeat/abc123xyz`. This is your heartbeat endpoint. Copy it.
Step 3: Ping the Heartbeat from Your Cron
// Inside your nightly billing sync cron job
import { Database } from '@supabase/supabase-js';
export async function billingSync() {
try {
// ... your actual billing logic here ...
const results = await syncAllInvoices();
// Ping the heartbeat to say "I'm alive"
await fetch('https://uptime.betterstack.com/api/v1/heartbeat/abc123xyz', {
method: 'POST',
});
return { success: true, count: results.length };
} catch (error) {
console.error('Billing sync failed:', error);
// If the job fails, don't ping the heartbeat
// BetterStack will notice the silence and alert
throw error;
}
}
That's it. Every time your cron completes successfully, it pings the heartbeat. If the job crashes, the ping never happens. BetterStack waits (you set this — typically 25h for a 24h job to account for clock drift). If 25h passes without a ping, it alerts: "Billing sync heartbeat missed."
Step 4: Supabase pg_cron Integration
If you're using Supabase, your cron lives in `pg_cron`. You can't directly call Node.js from SQL, but you can call an HTTP endpoint. Use a Supabase Edge Function as a wrapper:
-- In Supabase, create a cron job that calls your Edge Function
select cron.schedule('billing-sync', '0 2 * * *', 'select http_post(
''https://your-project.supabase.co/functions/v1/billing-sync'',
jsonb_build_object(),
''Bearer your-anon-key''
)');
The Edge Function handles the sync and pings the heartbeat. Done.
Real Cost Breakdown
BetterStack pricing: $49/month for 50 monitors (uptime checks + heartbeats). At that price, you get:
- 10 uptime endpoints (check every 30s from 10 global locations)
- 20 heartbeat monitors (cron jobs, background workers, etc.)
- Slack + email alerting
- Public status page (optional, shows customers you care about transparency)
- Incident timeline (for post-mortems)
Alternative: Pingdom charges $15/month per monitor (so 10 endpoints = $150/month). PagerDuty is $49/month but requires integration setup. BetterStack wins on price and simplicity.
Six FAQs
Q: What if the heartbeat ping itself fails (network error)?
Wrap it in retry logic. Ping once, wait 5s, ping again if it failed. Two failures in a row = log it, don't throw (the job succeeded locally, the alert failed). This prevents false positives from flaky network.
Q: Should I ping the heartbeat on failure too?
No. Silence = failure. If you ping even when failed, BetterStack thinks the job ran. Instead, catch the error, log it to BetterStack (via your error monitoring), and let the heartbeat silence trigger the alert.
Q: How often should I check uptime?
Every 30s is standard. For critical services, 10s. For low-criticality, 5 minutes is fine. More frequent = more cost. Balance with your SLA.
Q: Can I use a simple HTTP GET instead of POST for the heartbeat?
Yes. BetterStack doesn't care about the method. Use GET or POST, whatever's easier in your cron environment.
Q: What's the "grace period" on heartbeats?
You set it when creating the monitor. For a 24h job, set grace to 25h (give it 1h buffer for clock drift or temporary delays). For a 5-minute job, set 6 minutes. Too loose and you miss real failures; too tight and you get false alarms.
Q: Do I need both uptime checks AND heartbeats?
Yes. Uptime checks tell you if your API is responding. Heartbeats tell you if your background jobs are running. A cron can fail while your API stays up. You need both signals.
The Aidxn Pattern
Every Velocity X SaaS gets uptime + heartbeat from day one. You monitor: (1) health endpoint, (2) payment processing cron, (3) nightly reconciliation, (4) email queue processor, (5) analytics sync. That's 5 monitors on the $49/month plan. Cost: negligible. Value: knowing your income-critical jobs actually run.
Configure Slack to thread all alerts into a #ops channel. Friday 4pm and your analytics sync missed — you know before customers email. You've got the whole weekend to debug instead of scrambling Monday morning.
Ship a SaaS with uptime gaps and you'll spend months re-earning customer trust. Catch failures at the monitor and you'll never know there was a problem. The invisible fix is always the best fix.
Running a SaaS with unstable cron or unsure monitoring strategy? Aidxn Design audits observability stacks and wires heartbeat monitoring or learn more about error tracking from our monitoring comparison.