YOUR SCHEDULED JOBS ARE FAILING SILENTLY: MONITORING WEBHOOK DELIVERIES
Your scheduled jobs are failing silently: monitoring webhook deliveries
A cron job that fails at 3am doesn't page anyone. Give scheduled webhook deliveries retries, a full audit trail, and hooks your monitoring stack — Sentry, Datadog — can see.
Ask a candid backend engineer whether last night’s scheduled jobs ran, and you’ll often get a shrug. Classic cron is the least observable thing in most stacks: it appends to a syslog nobody reads, exits silently when your script has a bug, and has no concept of “failure” beyond a nonzero exit code nobody is subscribed to.
Scheduled webhooks change the situation structurally — because an HTTP call leaves a much richer trail than a shell script.
The delivery record is the audit trail
Every scheduled delivery in Recurrence Cloud is recorded in the activity trail:
- Timestamp of every attempt (first fire and every retry)
- HTTP status code your receiver returned
- Response body, truncated for inspection
- Duration / latency of the call
- Retry status — pending, exhausted, or resolved
“Did the 3am sync run?” becomes a query, not an archaeology dig. You can view the activity per webhook URL via the dashboard or pull it via the API:
curl https://api.recurrence.cloud/v1/webhooks/<id>/activity \
-H "Authorization: Bearer $API_TOKEN"
Retries with receipts
A delivery that gets a non-2xx (or times out) is retried on a backoff schedule until it succeeds or exhausts its attempts. Crucially, the record of each attempt persists — so a job that ultimately succeeded on the third try still leaves evidence in the trail that the first two attempts failed with a 500 and a connection reset.
That distinction matters for humans (“was this just a blip?” — now you know) and for alerting: a single transient failure and a chronic 20-minute outage look identical in a health dashboard but nothing alike in an activity trail.
Wire the delivery stream into your existing metrics stack — integrations for tools like Sentry and Datadog let scheduled failures land where your team already works:
- Failure-rate spikes per webhook URL
- p95 latency of receiver endpoints by schedule
- Retry exhaustion counts as the “job never completed” metric
Receiver-side discipline
The trail is only as truthful as your status codes. Three rules:
- Acknowledge only completed work. Return
202for accepted-and-queued,500for genuine failure — never a blanket200at the top of the handler. - Idempotency keys. Retries will happen; make redeliveries safe by keying on the event identity.
- Fail loudly inside, quietly outside. Catch, log with context (schedule name, subject, event id), and return a real error — so the trail captures what your code knew.
A minimal honest handler:
app.post("/jobs", async (c) => {
const claims = await verifyToken(c.req); // 401 on bad signature
try {
await processBatch(claims.event);
return c.json({ ok: true });
} catch (err) {
logger.error({ err, subject: claims.event.subject }, "job failed");
return c.json({ ok: false }, 500); // schedules a retry
}
});
A monitoring checklist for scheduled work
- Every schedule has a named owner and appears in the activity trail
- Alerts fire on retry exhaustion, not on first failure
- Delivery latency is tracked per webhook URL (receivers degrading slowly is a classic pre-outage signal)
- A runbook links from each schedule: what its payload means, what to do when it fails
Reliability is the payoff of the pattern; verify the inbound side and batch intelligently to keep the volume manageable. For the full tour, see the scheduled webhook guide.