Event-driven monitors
How Drumbeats watches on-demand jobs — queue workers, webhook handlers, ad-hoc scripts. Requires run_id correlation. No schedule.
An Event-driven monitor watches jobs that run on demand: queue workers, webhook handlers, manual scripts, CI jobs. Drumbeats does not enforce a schedule — it only reacts to the events your job sends.
Use Event-driven when there is no fixed cadence to fall behind on, only individual runs that succeed, fail, or hang.
How the monitor decides health
Each execution sends two events: a start ping when work begins and a success or failure ping when it ends. Drumbeats groups them by run_id (a unique string you generate per run) into a single run record. The monitor's health rolls up:
- Latest run =
success→ monitor stays UP. - Latest run =
failure(or hung pastmax_duration_secondswithout a finish) → incident opens. failure_toleranceconsecutive failures → monitor flips to DOWN.
There is no MISSED state — an Event-driven monitor that no one pings is silent, not alerting.
Configuration
| Field | Required | Example | Description |
|---|---|---|---|
max_duration_seconds | — | 300 | If start is not followed by success / failure inside this window, Drumbeats records the run as hung and opens an incident. |
failure_tolerance | — | 1 | Consecutive failures before flipping the monitor to DOWN. |
name | ✓ | Order processor | Human-readable label shown in alerts and the dashboard. |
The lack of a schedule field is intentional. Event-driven monitors do not catch a service that has stopped pinging entirely — pair them with an Uptime monitor on the worker's healthcheck endpoint if you also need "is the consumer alive?" coverage.
Why run_id is required
Event-driven monitors handle concurrent executions. Without a run_id, ten parallel runs of the same job each send start, then ten send success — Drumbeats cannot tell which success matches which start. Pass a unique run_id on every event and the same run_id survives start → log → success.
Wire pings into your worker
The shape is the same across stacks: generate run_id, send start, run the work, send success or failure with the same run_id.
RUN_ID="job-$(date +%s)-$RANDOM"
API="https://api.drumbeats.io/v1/ping/<monitor-id>"
curl -sf "$API/start?run_id=$RUN_ID"
if process_message; then
curl -sf "$API/success?run_id=$RUN_ID"
else
curl -sf "$API/failure?run_id=$RUN_ID"
fiRUN_ID="job-$(date +%s)-$RANDOM"
API="https://api.drumbeats.io/v1/ping/<monitor-id>"
curl -sf "$API/start?run_id=$RUN_ID"
if process_message; then
curl -sf "$API/success?run_id=$RUN_ID"
else
curl -sf "$API/failure?run_id=$RUN_ID"
fiimport { randomUUID } from "node:crypto";
const API = "https://api.drumbeats.io/v1/ping/<monitor-id>";
async function handleMessage(message: QueueMessage) {
const runId = `msg-${randomUUID()}`;
await fetch(`${API}/start?run_id=${runId}`);
try {
await processMessage(message);
await fetch(`${API}/success?run_id=${runId}`);
} catch (err) {
await fetch(`${API}/failure?run_id=${runId}`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ payload: String(err) }),
});
throw err;
}
}import { randomUUID } from "node:crypto";
const API = "https://api.drumbeats.io/v1/ping/<monitor-id>";
async function handleMessage(message: QueueMessage) {
const runId = `msg-${randomUUID()}`;
await fetch(`${API}/start?run_id=${runId}`);
try {
await processMessage(message);
await fetch(`${API}/success?run_id=${runId}`);
} catch (err) {
await fetch(`${API}/failure?run_id=${runId}`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ payload: String(err) }),
});
throw err;
}
}import uuid, requests
API = "https://api.drumbeats.io/v1/ping/<monitor-id>"
def handle(message):
run_id = f"msg-{uuid.uuid4()}"
requests.get(f"{API}/start", params={"run_id": run_id})
try:
process(message)
requests.get(f"{API}/success", params={"run_id": run_id})
except Exception as exc:
requests.post(
f"{API}/failure",
params={"run_id": run_id},
json={"payload": str(exc)},
)
raiseimport uuid, requests
API = "https://api.drumbeats.io/v1/ping/<monitor-id>"
def handle(message):
run_id = f"msg-{uuid.uuid4()}"
requests.get(f"{API}/start", params={"run_id": run_id})
try:
process(message)
requests.get(f"{API}/success", params={"run_id": run_id})
except Exception as exc:
requests.post(
f"{API}/failure",
params={"run_id": run_id},
json={"payload": str(exc)},
)
raiseCatching hung runs
Drumbeats opens an incident when a start is not followed by success / failure within max_duration_seconds. The check runs server-side, so a worker that has died mid-run still surfaces — you do not need a finalizer to detect the hang.
For long-running jobs with progress signals, send log pings while the work continues:
curl -sf -X POST "$API/log?run_id=$RUN_ID" \
-H "Content-Type: application/json" \
-d '{"payload": "Processed batch 3 of 12"}'curl -sf -X POST "$API/log?run_id=$RUN_ID" \
-H "Content-Type: application/json" \
-d '{"payload": "Processed batch 3 of 12"}'log events are stored on the run and reset the inactivity timer (the equivalent of "still alive"). They count as beats; see Beats and usage.
Common patterns
Webhook handler
from flask import Flask, request
import requests, uuid
app = Flask(__name__)
API = "https://api.drumbeats.io/v1/ping/<monitor-id>"
@app.route("/webhook", methods=["POST"])
def webhook():
run_id = f"wh-{uuid.uuid4()}"
requests.get(f"{API}/start", params={"run_id": run_id})
try:
handle(request.json)
requests.get(f"{API}/success", params={"run_id": run_id})
return {"ok": True}
except Exception as exc:
requests.post(f"{API}/failure", params={"run_id": run_id}, json={"payload": str(exc)})
return {"error": str(exc)}, 500from flask import Flask, request
import requests, uuid
app = Flask(__name__)
API = "https://api.drumbeats.io/v1/ping/<monitor-id>"
@app.route("/webhook", methods=["POST"])
def webhook():
run_id = f"wh-{uuid.uuid4()}"
requests.get(f"{API}/start", params={"run_id": run_id})
try:
handle(request.json)
requests.get(f"{API}/success", params={"run_id": run_id})
return {"ok": True}
except Exception as exc:
requests.post(f"{API}/failure", params={"run_id": run_id}, json={"payload": str(exc)})
return {"error": str(exc)}, 500CI / one-off scripts
For scripts that run inside a CI pipeline, use the pipeline's run ID as run_id so the Drumbeats record links back to the pipeline:
The last call uses the exit-code endpoint: 0 becomes success, anything else becomes failure. See Ping API: exit codes.
Related guides
- Ping API: event-driven pings — endpoint reference with run_id parameters.
- Cron monitors — for jobs with a fixed schedule.
- Incidents — hung-run handling and recovery flow.
- Alternatives — comparison to Cronitor's job monitoring and Healthchecks.io's pings.