Payloads

Attach context to any Drumbeats ping — error messages, stdout, structured data, metrics. Payload sizes, parameter precedence, and storage rules.

Payloads are the optional body attached to a Drumbeats ping. They carry error messages, stdout, structured data, or metrics — anything that helps an on-call engineer triage a failed run without leaving the dashboard. Only POST requests can carry payloads; GET pings are limited to query-string parameters (run_id, exit-code event).

Request parameters

The Ping API accepts three optional parameters on every event. All three can be sent on the same POST.

FieldWhereSize limitDescription
run_idQuery string or POST body255 charsCorrelates pings from one execution. Required for Event-driven monitors.
payloadPOST bodyPlan dependent (see below)Stdout, error text, or any string. The dashboard renders this on the run timeline.
duration_msPOST bodyManually reported duration. Optional; Drumbeats also computes duration from start → finish timing when both pings share a run_id.

Payload size limits per plan:

  • Free: 100 KB per ping
  • Pro: 1 MB per ping
  • Business: 2 MB per ping

Payloads up to 10 KB live in the primary database and load instantly in the dashboard. Larger payloads spill to object storage and may take a moment to render. Beats are metered from the first byte regardless of storage tier.

Sending a payload

The patterns below all use the recommended POST + JSON shape. The start / success / failure URL suffix is unchanged — only the request body and method differ.

GET — run_id only
curl "https://api.drumbeats.io/v1/ping/<monitor-id>/success?run_id=job-123"
curl "https://api.drumbeats.io/v1/ping/<monitor-id>/success?run_id=job-123"
POST — run_id + payload
curl -X POST https://api.drumbeats.io/v1/ping/<monitor-id>/success \
  -H "Content-Type: application/json" \
  -d '{
    "run_id": "backup-2026-05-11",
    "payload": "Backed up 3 databases (2.1 GB) in 4m32s"
  }'
curl -X POST https://api.drumbeats.io/v1/ping/<monitor-id>/success \
  -H "Content-Type: application/json" \
  -d '{
    "run_id": "backup-2026-05-11",
    "payload": "Backed up 3 databases (2.1 GB) in 4m32s"
  }'
POST — run_id + duration
START=$(date +%s%3N)
/usr/bin/backup.sh
END=$(date +%s%3N)

curl -X POST https://api.drumbeats.io/v1/ping/<monitor-id>/success \
  -H "Content-Type: application/json" \
  -d "{\"run_id\":\"backup-$(date +%Y%m%d)\",\"duration_ms\":$((END-START))}"
START=$(date +%s%3N)
/usr/bin/backup.sh
END=$(date +%s%3N)

curl -X POST https://api.drumbeats.io/v1/ping/<monitor-id>/success \
  -H "Content-Type: application/json" \
  -d "{\"run_id\":\"backup-$(date +%Y%m%d)\",\"duration_ms\":$((END-START))}"
POST — pipe stdout as raw text
python job.py 2>&1 | curl -X POST https://api.drumbeats.io/v1/ping/<monitor-id>/success \
  -H "Content-Type: text/plain" \
  --data-binary @-
python job.py 2>&1 | curl -X POST https://api.drumbeats.io/v1/ping/<monitor-id>/success \
  -H "Content-Type: text/plain" \
  --data-binary @-
POST — capture output then send
OUTPUT=$(python job.py 2>&1)
EXIT=$?
EVENT=$([ $EXIT -eq 0 ] && echo "success" || echo "failure")

curl -X POST "https://api.drumbeats.io/v1/ping/<monitor-id>/$EVENT" \
  -H "Content-Type: application/json" \
  -d "{\"payload\":$(echo "$OUTPUT" | python3 -c 'import json,sys;print(json.dumps(sys.stdin.read()))'),\"run_id\":\"job-$$\"}"
OUTPUT=$(python job.py 2>&1)
EXIT=$?
EVENT=$([ $EXIT -eq 0 ] && echo "success" || echo "failure")

curl -X POST "https://api.drumbeats.io/v1/ping/<monitor-id>/$EVENT" \
  -H "Content-Type: application/json" \
  -d "{\"payload\":$(echo "$OUTPUT" | python3 -c 'import json,sys;print(json.dumps(sys.stdin.read()))'),\"run_id\":\"job-$$\"}"

Payload resolution rules

When Drumbeats receives a POST, it stores the payload using this precedence:

  1. Explicit payload field inside a JSON body → stored as-is.
  2. Entire raw body (when no explicit payload field) → the whole body is stored as the payload. This is how the plain-text / --data-binary @- form works.
  3. No body → no payload stored. The ping still counts as 1 Beat.

This means you can POST a flat JSON object and the dashboard will render it verbatim:

bash
curl -X POST https://api.drumbeats.io/v1/ping/<monitor-id>/log \
  -H "Content-Type: application/json" \
  -d '{"processed": 1240, "skipped": 3, "duration_ms": 1820}'
curl -X POST https://api.drumbeats.io/v1/ping/<monitor-id>/log \
  -H "Content-Type: application/json" \
  -d '{"processed": 1240, "skipped": 3, "duration_ms": 1820}'

Progress logs

The log event records intermediate state without changing monitor status. Each log ping costs 1 Beat and shows up on the run timeline next to the start and finish pings (when they share a run_id).

bash
API="https://api.drumbeats.io/v1/ping/<monitor-id>"
RUN_ID="import-$(date +%s)"

curl -sf "$API/start?run_id=$RUN_ID"

python import.py --phase=extract
curl -sf -X POST "$API/log?run_id=$RUN_ID" \
  -H "Content-Type: application/json" \
  -d '{"payload":"Extract: 5000 rows fetched"}'

python import.py --phase=transform
curl -sf -X POST "$API/log?run_id=$RUN_ID" \
  -H "Content-Type: application/json" \
  -d '{"payload":"Transform: 4998 rows processed, 2 skipped"}'

python import.py --phase=load
curl -sf "$API/success?run_id=$RUN_ID"
API="https://api.drumbeats.io/v1/ping/<monitor-id>"
RUN_ID="import-$(date +%s)"

curl -sf "$API/start?run_id=$RUN_ID"

python import.py --phase=extract
curl -sf -X POST "$API/log?run_id=$RUN_ID" \
  -H "Content-Type: application/json" \
  -d '{"payload":"Extract: 5000 rows fetched"}'

python import.py --phase=transform
curl -sf -X POST "$API/log?run_id=$RUN_ID" \
  -H "Content-Type: application/json" \
  -d '{"payload":"Transform: 4998 rows processed, 2 skipped"}'

python import.py --phase=load
curl -sf "$API/success?run_id=$RUN_ID"

Use log for phases whose state actually helps debugging. Routine "step finished" notes add Beats with little payoff — see Beats & usage for the accounting.

Multi-environment with slug URLs

The project-slug URL form lets the same script run against multiple Drumbeats projects (staging vs production) without code changes. The monitor's slug stays constant; only the project ID changes per environment.

bash
# .env
DRUMBEATS_PROJECT="proj_abc123"
DRUMBEATS_SLUG="nightly-sync"

API="https://api.drumbeats.io/v1/s-ping/$DRUMBEATS_PROJECT/$DRUMBEATS_SLUG"
RUN_ID="sync-$(date +%s)"

curl -sf "$API/start?run_id=$RUN_ID"
python sync.py
curl -X POST "$API/success?run_id=$RUN_ID" \
  -H "Content-Type: application/json" \
  -d '{"payload":"Synced 5000 records"}'
# .env
DRUMBEATS_PROJECT="proj_abc123"
DRUMBEATS_SLUG="nightly-sync"

API="https://api.drumbeats.io/v1/s-ping/$DRUMBEATS_PROJECT/$DRUMBEATS_SLUG"
RUN_ID="sync-$(date +%s)"

curl -sf "$API/start?run_id=$RUN_ID"
python sync.py
curl -X POST "$API/success?run_id=$RUN_ID" \
  -H "Content-Type: application/json" \
  -d '{"payload":"Synced 5000 records"}'

Swap DRUMBEATS_PROJECT between staging and production — same slug, different project, same script.

Tips