// DOCUMENTATION ← HOME

DOCS

CronBB is a dead-simple cron job heartbeat monitor. One curl in your crontab. If the job stops running, you get a webhook alert within 2 minutes. No agents, no SDK, no dashboard to babysit.

INTRODUCTION

Cron jobs fail silently all the time — a script gets a bad path after a server update, the cron daemon dies after a reboot, a dependency breaks. You don't notice for days. CronBB solves this with the dead man's switch pattern: your job sends a ping after every successful run. If the ping stops arriving on schedule, CronBB fires a webhook to your Slack or Discord.

Dead man's switch: Instead of checking for failures directly, CronBB checks for the absence of success. Your job proves it ran. Silence is the alarm.

CronBB runs entirely on Cloudflare Workers + KV — zero servers to maintain, zero databases to back up. Everything fits in the free tier.

QUICK START

Get up and running in 60 seconds.

1. Create an account

BASH
# POST your email, get an API key back
curl -X POST https://cronbb.vzbb.site/api/signup \
  -H "Content-Type: application/json" \
  -d '{"email":"[email protected]"}'

# Response:
{"api_key": "cronbb_k8x2m..."}

2. Create a monitor

BASH
curl -X POST https://cronbb.vzbb.site/api/monitors \
  -H "Authorization: Bearer cronbb_k8x2m..." \
  -H "Content-Type: application/json" \
  -d '{"name":"db-backup","interval_minutes":60,"webhook_url":"https://hooks.slack.com/..."}'

# Response:
{"monitor": {"id":"a3f8x1","ping_url":"https://cronbb.vzbb.site/ping/a3f8x1", ...}}

3. Add ping to crontab

CRONTAB
# Append && curl ... to your existing cron command
0 * * * * /usr/local/bin/backup.sh && curl -fsS --retry 3 https://cronbb.vzbb.site/ping/a3f8x1
The && operator is critical — it means the curl only runs if the script succeeds (exit code 0). If your script fails, the ping never fires, and CronBB alerts you.

Or use the CLI

BASH
npm install -g cronbb-monitor

cronbb signup [email protected]
cronbb create db-backup 60 --webhook https://hooks.slack.com/...
cronbb list

HOW IT WORKS

A Cloudflare Worker cron trigger runs every 2 minutes and checks all monitors. For each monitor, it computes: has a ping arrived within the expected window (interval + grace period)?

StateConditionAction
upPing arrived within interval + graceNothing
waitingNo ping received yet (new monitor)Nothing
downPing overdueFire webhook alert
recoveredWas down, ping arrived againFire recovery webhook
pausedManually pausedNo alerts fired

Storage is Cloudflare KV — a globally distributed key-value store. No relational database, no persistent server, no TCP connections to maintain.

AUTHENTICATION

All authenticated endpoints require a Bearer token in the Authorization header. You get your API key when you sign up.

HTTP HEADER
Authorization: Bearer cronbb_k8x2m...
Keep your API key secret. It grants full access to your monitors. Don't commit it to version control — use environment variables or CI secrets.

SIGNUP / LOGIN

POST/api/signup

Creates a new account and returns an API key. Idempotent — calling with the same email returns the existing key.

Request body

FieldTypeDescription
emailrequiredstringValid email address. Used for account identification only.

Response

JSON
{
  "api_key": "cronbb_k8x2m...",
  "message": "Account created."
}
POST/api/login

Retrieves an existing API key by email. Returns 404 if the account doesn't exist.

JSON — REQUEST
{ "email": "[email protected]" }
GET/api/me

Returns account info, plan details, and current usage limits.

JSON — RESPONSE
{
  "email":   "[email protected]",
  "plan":    "free",
  "limits": { "monitors": 3, "interval_min": 2 },
  "created_at": 1700000000000
}

MONITORS

POST/api/monitorsauth required

Creates a new monitor. Returns the full monitor object including the ping URL.

Request body

FieldTypeDescription
namerequiredstringIdentifier for the job (e.g., "db-backup").
interval_minutesrequirednumberExpected time between pings, in minutes. Range: 1–43200.
webhook_urloptionalstringURL to POST alert payloads to. Supports Slack, Discord, or any https URL.
grace_minutesoptionalnumberExtra buffer before alerting. Useful for jobs that sometimes run a bit late. Default: 0.
tagsoptionalstring[]Up to 5 labels for organization (e.g., ["production","db"]).
JSON — RESPONSE 201
{
  "monitor": {
    "id":               "a3f8x1",
    "name":             "db-backup",
    "status":           "waiting",
    "interval_minutes": 60,
    "grace_minutes":    0,
    "ping_url":         "https://cronbb.vzbb.site/ping/a3f8x1",
    "ping_count":       0,
    "last_ping":        null,
    "paused":           false,
    "webhook_url":      "https://hooks.slack.com/...",
    "created_at":       1700000000000
  }
}
GET/api/monitorsauth required

Returns all monitors for the current account.

JSON — RESPONSE
{
  "monitors": [ /* array of monitor objects */ ],
  "count": 2,
  "limit": 3,
  "plan":  "free"
}
GET/api/monitors/:idauth required

Returns a single monitor with the last 20 ping events.

JSON — RESPONSE
{
  "monitor": { /* monitor object */ },
  "history": [
    { "ts": 1700000000000, "recovered": false },
    /* ... up to 20 entries, newest first */
  ]
}
PATCH/api/monitors/:idauth required

Updates one or more fields of a monitor without recreating it. All fields optional.

FieldType
namestring
interval_minutesnumber (1–43200)
webhook_urlstring
grace_minutesnumber (0–60)
tagsstring[]
DELETE/api/monitors/:idauth required

Permanently removes the monitor and all its history.

PING

GET/ping/:idno auth

Sends a heartbeat for the monitor. Call this at the end of each successful cron run. No authentication required — the URL itself is the secret.

BASH
# Append to existing cron command
0 3 * * * /usr/bin/backup.sh && curl -fsS --retry 3 https://cronbb.vzbb.site/ping/YOUR_ID

# --retry 3 ensures transient network issues don't false-alarm
# -f makes curl fail on non-2xx (so you know if the API is down)
# -s silences progress output
# -S shows errors even with -s

Response

JSON
{
  "ok":           true,
  "status":       "up",
  "ping_count":   42,
  "recovered":   false,
  "next_expected": "2026-03-01T04:00:00.000Z"
}

PAUSE / RESUME

Use pause during deployments or maintenance windows to prevent false alarms.

POST/api/monitors/:id/pause
POST/api/monitors/:id/resume
BASH
# Pause before deployment
curl -X POST https://cronbb.vzbb.site/api/monitors/a3f8x1/pause \
  -H "Authorization: Bearer cronbb_k8x2m..."

# Resume after deployment completes
curl -X POST https://cronbb.vzbb.site/api/monitors/a3f8x1/resume \
  -H "Authorization: Bearer cronbb_k8x2m..."

CLI — INSTALLATION

BASH
npm install -g cronbb-monitor

# Verify
cronbb --version

Requires Node.js ≥ 14. The CLI stores your API key in ~/.cronbb/config.json.

CLI — COMMANDS

CommandDescription
cronbb signup <email>Create account, save API key locally.
cronbb login <email>Retrieve and save existing API key.
cronbb whoamiShow account info and plan limits.
cronbb create <name> <mins>Create a monitor.
cronbb list [--json]List all monitors with status.
cronbb status <id|name>Detailed view + ping history.
cronbb edit <id> [flags]Update monitor fields.
cronbb pause <id>Pause a monitor.
cronbb resume <id>Resume a paused monitor.
cronbb rm <id>Delete a monitor.
cronbb ping <id>Manually send a heartbeat.
cronbb crontab <id>Print ready-to-paste crontab snippets.
cronbb healthCheck API status.

Create flags

BASH
cronbb create db-backup 60 \
  --webhook https://hooks.slack.com/... \
  --grace   10 \
  --tags    production,db

WEBHOOKS — SLACK

CronBB auto-detects Slack URLs and sends rich Block Kit messages with color, monitor name, and context.

Step-by-step

  1. Go to api.slack.com/apps and click Create New App → From scratch
  2. Give it a name (e.g. "CronBB") and pick your workspace → Create App
  3. In the left sidebar click Incoming Webhooks → toggle it On
  4. Scroll down → click Add New Webhook to Workspace
  5. Choose the channel that should receive alerts (e.g. #alerts) → Allow
  6. Copy the webhook URL — it looks like: https://hooks.slack.com/services/T.../B.../xxx
  7. Paste it as webhook_url when creating or editing a monitor
BASH — ADD SLACK TO A MONITOR
# At create time:
cronbb create db-backup 60 --webhook https://hooks.slack.com/services/T.../B.../xxx

# Or add to existing monitor:
cronbb edit YOUR_MONITOR_ID --webhook https://hooks.slack.com/services/T.../B.../xxx

What the Slack message looks like

🔴 Monitor Down: db-backup
Last ping was 73m ago (expected every 60m).
ID: a3f8x1 · Interval: 60m · CronBB
🟢 Recovered: db-backup
Recovered after being down for 13m.
ID: a3f8x1 · Interval: 60m · CronBB

WEBHOOKS — DISCORD

CronBB auto-detects Discord URLs and sends rich Embeds with color-coded status (🔴 red for down, 🟢 green for recovery).

Step-by-step

  1. Open Discord, right-click the channel you want alerts in → Edit Channel
  2. Go to Integrations → Webhooks → New Webhook
  3. Give it a name (e.g. "CronBB") and optionally set an avatar → Save Changes
  4. Click Copy Webhook URL — it looks like: https://discord.com/api/webhooks/123456.../xxx
  5. Paste it as webhook_url when creating or editing a monitor
BASH — ADD DISCORD TO A MONITOR
# At create time:
cronbb create db-backup 60 --webhook https://discord.com/api/webhooks/123.../xxx

# Or add to existing monitor:
cronbb edit YOUR_MONITOR_ID --webhook https://discord.com/api/webhooks/123.../xxx

What the Discord embed looks like

🔴 Down: db-backup
Last ping was 73m ago (expected every 60m).
Monitor ID: a3f8x1 Interval: 60m
CronBB · 2026-01-15T03:00:00.000Z
🟢 Recovered: db-backup
Recovered after being down for 13m.
CronBB · 2026-01-15T03:13:00.000Z

Tip: use a dedicated channel

Create a #cronbb-alerts channel in your server so alerts don't mix with conversation. You can also mention a role by editing the channel topic to include @oncall.

WEBHOOKS — GENERIC

Any https:// URL works. CronBB will POST a JSON payload.

BASH — EXAMPLE: TEST WITH WEBHOOK.SITE
# 1. Visit https://webhook.site and copy your unique URL
# 2. Use it as webhook_url when creating a monitor
cronbb create test-job 1 --webhook https://webhook.site/YOUR-UUID

WEBHOOK PAYLOAD REFERENCE

Down alert

JSON
{
  "status":           "down",
  "monitor_id":       "a3f8x1",
  "monitor_name":     "db-backup",
  "interval_minutes": 60,
  "message":          "Last ping was 73m ago (expected every 60m).",
  "down_since":       1700000000000,
  "timestamp":        1700004380000
}

Recovery alert

JSON
{
  "status":           "recovered",
  "monitor_id":       "a3f8x1",
  "monitor_name":     "db-backup",
  "interval_minutes": 60,
  "message":          "Recovered after being down for 13m.",
  "timestamp":        1700005200000
}

GRACE PERIODS

Some jobs run a few minutes late by design (heavy DB queries, slow network, etc.). A grace period adds a buffer before CronBB considers a monitor down.

Alert fires when: now > last_ping + interval_minutes + grace_minutes
BASH
# Job runs every hour but sometimes takes up to 10 extra minutes
cronbb create slow-report 60 --grace 10

# Alert will fire only if ping is more than 70 minutes overdue

DEPLOYING YOUR INSTANCE

CronBB runs on Cloudflare Workers. Deploy your own instance in under 5 minutes.

Requirements

1 — Authenticate Wrangler

BASH
wrangler login

2 — Create a KV namespace

BASH
wrangler kv:namespace create KV
# Copy the returned id = "..." into wrangler.toml → [[kv_namespaces]]

3 — Add Stripe secrets

BASH
wrangler secret put STRIPE_SECRET_KEY
# paste: sk_live_...

wrangler secret put STRIPE_PRO_PRICE_ID
# paste: price_...

wrangler secret put STRIPE_WEBHOOK_SECRET
# paste: whsec_...

4 — Deploy

BASH
wrangler deploy

# Live at: https://cronbb.YOUR_SUBDOMAIN.workers.dev
# Custom domain: add a CNAME in Cloudflare DNS → Workers route

wrangler.toml reference

TOML
name               = "cronbb"
main               = "src/worker.js"
compatibility_date = "2024-09-23"

# Runs every minute — plan logic (free: min 2m, pro: min 1m) is enforced in code
[triggers]
crons = ["* * * * *"]

[[kv_namespaces]]
binding    = "KV"
id         = "YOUR_KV_NAMESPACE_ID"
Free tier limits: Cloudflare Workers free tier includes 100,000 requests/day, unlimited cron invocations, and 100,000 KV reads/day — more than enough for personal or small-team use.

ERROR CODES

StatusCode / messageMeaning
400Various validation messagesInvalid request body (missing field, wrong type, out-of-range value).
401"Authentication required"Missing or invalid Authorization header.
403"Monitor limit reached"You've hit your plan's monitor limit. Upgrade or delete an existing monitor.
404"Monitor not found"Monitor ID doesn't exist or belongs to another account.
429"Too many requests"Rate limit hit on signup/login. Try again in a minute.

All error responses have the shape: {"error": "message here"}