cronhq
Sign inStart free
Reliable cron · in 60 seconds

Cron jobs thatactually run.

Point Cronhq at a webhook. We fire it on schedule, retry on failure, and page you when something breaks — and again the moment it recovers.

POST /v1/jobs
shell
curl -X POST https://api.cronhq.xyz/v1/jobs \
  -H "Authorization: Bearer chq_••••" \
  -d '{
    "name": "nightly-rollup",
    "schedule": "0 2 * * *",
    "webhook_url": "https://api.you.com/cron/rollup"
  }'
Executions· live
tail -f
endpointstatusms
  • 12:00:00.012POSTapi.acme.io/cron/rollup200142
  • 12:00:00.347POSTmetrics.app/refresh200081
  • 12:00:01.005POSTbilling.io/invoices50430s
  • 12:00:01.412POSTapi.acme.io/cron/digest200092
  • 12:00:02.118GETstats.dev/health-roll200037
  • 12:00:02.901POSTapi.acme.io/cron/cleanup200204
  • 12:00:03.480recoverybilling.io/invoices ok200412

Throughput

312/min

P95 latency

187ms

Success

99.94%

↳ exec feed · mock data, real shape

// 01Capabilities

// shipped this cycle — the stuff that keeps you here

$ npm i -g cronhq# or: npx cronhq --help

01 · Signed webhooks

  timestamp.body
        │
   hmac-sha256 ── secret
        │
        ▼
  v1=20d7b6d0e4f1…

Verify it's really us.

Every request carries an X-Cronhq-Signature header — HMAC-SHA256 over timestamp.body — plus an X-Cronhq-Timestamp. Each job gets its own secret. Rotate it anytime, no downtime.

verify webhooks →

02 · Heartbeat monitors

  ping  ✓  ✓  ✓  ✕
                 └─ DOWN
        window missed ─┘

A dead-man's switch for your crons.

Ping a URL on every run. Miss the window — period plus grace — and we flip the monitor DOWN and page you once. It's cron's inverse: proof of absence, not presence.

add a monitor →

03 · Cron-as-code

$ npx cronhq jobs tail nightly-rollup

success200412ms

Your schedules, in version control.

npx cronhq sync reconciles a cronhq.yaml in your repo — creates, updates, prunes. npx cronhq tail live-streams executions straight to your terminal.

read the CLI docs →

04 · ⌘K + natural language

  "every weekday at 9am"
             │
             ▼
       0 9 * * 1-5

Type it in English.

Hit ⌘K anywhere in the dashboard for the command palette. Type "every weekday at 9am" and get a valid cron expression back, with a plain-English preview so you never guess wrong.

try the dashboard →
// 02Quick Start
  1. 01step

    Get your API key

    Sign up with your email and we'll send a one-time link — click it and a key is minted for you: a 36-character string starting with chq_. Already have one? Sign in with what you've got.

  2. 02step

    Create a job

    Pick a schedule (every 5 min, daily at 9am, weekdays only…) and the URL we should call. One request, two seconds, done.

    POST /v1/jobs
    {
      "name": "nightly-rollup",
      "schedule": "0 2 * * *",
      "webhook_url": "https://api.you.com/cron/rollup",
      "timezone": "America/New_York"
    }
  3. 03step

    Watch it run

    Every execution is logged with status, duration, HTTP code, and the response body. Newest first. Searchable. Yours forever.

    12:02:00api.you.com/cron/rollup200142
    12:07:00api.you.com/cron/rollup200098
    12:12:00api.you.com/cron/rollup50330s
  4. 04step

    Get alerted

    Wire email or Slack. We page on the third failure in an hour (not every failure), and again the moment things recover.

    alert · recoverynow

    nightly-rollup recovered after 3 failures. Last 6 runs: all 2xx.

// 03Engineering Notes

01 · Exactly once

  ┌─────────┐    ┌─────────┐
  │ worker A │   │ worker B │
  └────┬────┘    └────┬────┘
       └──── LOCK ────┘
              ▼
        ┌─────────┐
        │ job 0x4 │
        └─────────┘

A Postgres lock claims each run.

Two workers can never fire the same scheduled execution. Crashed mid-job? Another picks up after the lock expires.

02 · Retries built in

  try   ✕──┐
  +60s  ✕──┤
  +120s ✕──┤
  +240s ✓──┘
        ▼
        ok

Backoff, not boilerplate.

Max retries + delay per job. The terminal status and last error always land in your history — so the postmortem writes itself.

03 · Alerts with dedup

  ✕  ✕  ✕  ━━ alert
  ✕  ✕  ✕  (silent)
  ✕  ✕  ✓  ━━ recovery
           (silent)

Page once. Recover once.

Failure alert fires on the third bad run in an hour, not the first. Recovery alert fires on the first success after a streak.

// global feed312 / min
12:00:00.012POSTapi.acme.io/cron/rollup200142ms
12:00:00.347POSTmetrics.app/refresh200081ms
12:00:01.005POSTbilling.io/invoices50430sms
12:00:01.412POSTapi.acme.io/cron/digest200092ms
12:00:02.118GETstats.dev/health-roll200037ms
12:00:02.901POSTapi.acme.io/cron/cleanup200204ms
12:00:03.227POSTqueue.dev/drain200066ms
12:00:03.812POSTapi.acme.io/cron/sync200118ms
12:00:04.001PUTcache.io/warm200024ms
12:00:00.012POSTapi.acme.io/cron/rollup200142ms
12:00:00.347POSTmetrics.app/refresh200081ms
12:00:01.005POSTbilling.io/invoices50430sms
12:00:01.412POSTapi.acme.io/cron/digest200092ms
12:00:02.118GETstats.dev/health-roll200037ms
12:00:02.901POSTapi.acme.io/cron/cleanup200204ms
12:00:03.227POSTqueue.dev/drain200066ms
12:00:03.812POSTapi.acme.io/cron/sync200118ms
12:00:04.001PUTcache.io/warm200024ms
// readyready when you are

stop wondering ifyour job fired.

60 seconds to your first scheduled webhook. No SDK, no agent, no daemon to install.

Start freeSign in↳ bring your chq_ key