Skip to content

Publishing heartbeats

A beat is one POST to your monitor’s publish URL. Send one each time your job runs.

New here? Start with Publish your first heartbeat.

Terminal window
curl --request POST \
--url https://ingest.signalsitter.com/nightly-backup \
--header "Authorization: Bearer ${SIGNALSITTER_PUBLISH_CREDENTIAL}"

The URL is an ingest host plus the monitor’s ID. Copy it from the monitor in the dashboard, or from publishUrl when you create a monitor through the API. Use it as given.

Region Ingest host
Unrestricted https://ingest.signalsitter.com
European Union https://eu.ingest.signalsitter.com

A monitor’s region is fixed when it’s created. Keep publishing an existing monitor to its own URL.

Authenticate with Authorization: Bearer <credential>. The credential’s level decides what a beat can do:

  • manage: records the beat and applies any configuration headers. Sending to a new ID creates the monitor.
  • write or readwrite: records the beat and ignores configuration headers. The response lists them in configuration.ignoredFields.
  • read: refused with 403.

The body is optional. Send one of:

  • nothing;
  • a single JSON number, such as 42;
  • a flat JSON object with 1 to 16 numeric measurements, plus a few scalar metadata fields.
Terminal window
curl --request POST \
--url https://ingest.signalsitter.com/nightly-backup \
--header "Authorization: Bearer ${SIGNALSITTER_PUBLISH_CREDENTIAL}" \
--header "Content-Type: application/json" \
--data '{"files":42,"source":"nightly","complete":true}'

Rules for the body:

  • Any body needs Content-Type: application/json.
  • Measurement names use lowercase letters, numbers and underscores, and start with a letter.
  • Numbers must be finite JSON numbers. "42" as a string is refused, not converted.
  • The body can be at most 16 KiB.

Send Content-Encoding: gzip to compress the body. The 16 KiB limit applies to the decompressed body. Any other encoding is refused with 415.

With a manage credential, headers on a beat change the monitor’s configuration. The change is saved with the beat.

Header Sets Values
Signalsitter-Name Display name Text
Signalsitter-Interval Expected time between beats Seconds, 60 to one year
Signalsitter-Grace Extra time before a beat counts as missing Seconds, 0 to 7 days. Default 60. Values under 60 become 60
Signalsitter-Heartbeat Turns missing-beat alerts off disabled. Send Signalsitter-Interval to turn them back on
Signalsitter-Callback Where incidents are sent none, default, alias:<name>, or a public https:// URL
Signalsitter-Metric-Type How a bare-number body is read gauge, count, rate, duration, counter, size
Signalsitter-Min / Signalsitter-Max Limits on a bare-number body. A value outside them opens an incident Finite numbers
Terminal window
curl --request POST \
--url https://ingest.signalsitter.com/nightly-backup \
--header "Authorization: Bearer ${SIGNALSITTER_PUBLISH_CREDENTIAL}" \
--header "Signalsitter-Name: Nightly backup" \
--header "Signalsitter-Interval: 86400" \
--header "Signalsitter-Grace: 1800"

Your plan may set a longer minimum interval. See Plans and limits.

For anything the common headers don’t cover, send Signalsitter-Config: a versioned JSON patch, encoded as unpadded base64url. It takes the same patch as the update configuration operation. If a common header and Signalsitter-Config set the same field, the values must agree.

Older learned-detection settings, such as Signalsitter-Sensitivity and Signalsitter-Mode, are still accepted for compatibility. This guide doesn’t cover them.

Set it. Send Signalsitter-Interval, or set an interval when you create the monitor. Each deadline counts from the latest beat.

Let it learn. Create the monitor without an interval. It watches your beats and sets the interval itself once five beats in a row arrive at a steady pace, each gap within 20% of the typical gap. Grace is then half the interval, at least 60 seconds, unless you set one.

While it’s learning:

  • Silence raises nothing. There’s no deadline yet.
  • Only beats sent with a manage credential count.
  • Each response shows progress:
{
"heartbeatExpectation": { "state": "automatic", "version": 1 },
"heartbeatLearning": { "status": "collecting", "samples": 3, "requiredSamples": 5 }
}

status is collecting, inconsistent (beats too irregular, still trying) or unsupported_cadence (the measured pace is outside 60 seconds to one year). Accept values you don’t recognise, as more may be added.

If your job runs irregularly, set the interval. A source that never settles into a steady pace is never watched.

To learn again, set the heartbeat expectation back to { "state": "automatic", "version": 1 } in the dashboard, the API or Signalsitter-Config.

An object measurement without a type is a gauge.

Type Accepts
gauge Any finite number, including negatives
count, counter Non-negative whole numbers
rate, duration, size Non-negative numbers

A counter is a running total. Its first beat sets the starting point, and each later beat is measured as the increase. A drop counts as a reset. unit is a display label only. Nothing is converted.

Rules are fixed limits on a measurement. A breach opens an incident and sends your callback.

For a bare-number body, set the limits with headers:

Terminal window
curl --request POST \
--url https://ingest.signalsitter.com/queue-worker \
--header "Authorization: Bearer ${SIGNALSITTER_PUBLISH_CREDENTIAL}" \
--header "Content-Type: application/json" \
--header "Signalsitter-Min: 0" \
--header "Signalsitter-Max: 100" \
--data '42'
Rule Fires when
minimum / maximum The value is below or above the limit. Limits are inclusive
zeroExpectation The value isn’t zero ("zero") or is zero ("nonzero")
maximumAbsoluteDelta The value moved more than this since the last beat
maximumPercentageDelta The value moved more than this percentage since the last beat
ratio This measurement divided by another one is outside minimum / maximum

To set rules on named measurements in an object body, or any rule beyond a minimum and maximum, use update configuration, the rule workbench in the dashboard, or Signalsitter-Config on a beat. Set them with a rule’s "ruleAction": { "mode": "active", "version": 1 } to make it open incidents; without it, the rule is checked and shown but never alerts.

This beat alerts when fewer than one file is backed up:

Terminal window
CONFIG='{"version":1,"patch":{"measurements":{"files":{"minimum":1,"ruleAction":{"mode":"active","version":1}}}}}'
CONFIG_HEADER=$(printf '%s' "$CONFIG" | base64 | tr -d '\n=' | tr '+/' '-_')
curl --request POST \
--url https://ingest.signalsitter.com/nightly-backup \
--header "Authorization: Bearer ${SIGNALSITTER_PUBLISH_CREDENTIAL}" \
--header "Content-Type: application/json" \
--header "Signalsitter-Config: ${CONFIG_HEADER}" \
--data '{"files":0}'

Good to know:

  • A monitor can have up to 32 rules.
  • By default, one breaching beat opens a critical incident. Set rulePolicy to wait for several beats or a length of time first.
  • To pause one rule, list its kind in the measurement’s disabledRules. To remove one, set it to null, for example { "measurements": { "files": { "maximum": null } } }.
  • Try a beat against your rules without recording it with test a beat.

200 means the beat and any configuration change are saved. A trimmed example:

{
"accepted": true,
"replayed": false,
"monitorKey": "nightly-backup",
"observation": {
"id": "obs_01234567-89ab-4cde-8fab-0123456789ab",
"status": "on_time"
},
"configuration": {
"status": "applied",
"appliedFields": ["grace_seconds", "interval_seconds"],
"ignoredFields": [],
"warnings": []
},
"nextDeadline": "2026-07-28T12:05:30.000Z"
}

observation.status is on_time, late (after interval plus grace), learning, or not_expected when missing-beat alerts are off.

Errors use application/problem+json with a request ID to quote to support.

Status Meaning What to do
400 Bad monitor ID, header, configuration or body Fix the request. Don’t retry it unchanged
401 Credential missing or invalid Check the Authorization header and the credential
403 Credential can’t publish, or publishing is refused for your account Use a write, readwrite or manage credential, or read the problem detail
409 Idempotency key reused with different content Use a new key for a new beat
410 Monitor deleted Deleted monitors can’t come back. Create a new monitor with a new ID
413 Body or headers too large Keep the body to 16 KiB or less, and shorten long headers
415 Body isn’t JSON, or uses an encoding other than gzip Send JSON, uncompressed or gzip
429 Plan limit reached, or beats faster than the cadence allows Read the problem’s limit context. See below for cadence
503 Temporary problem on our side Retry the same request

Retry 503 with the same request. A 200 with "replayed": true means the first attempt had already been saved.

You can publish up to twice per interval, and no more than once every 48 seconds. Ten beats can arrive back to back before that spacing applies. A 5-minute monitor accepts one beat every 2 minutes 30 seconds.

Faster beats get 429 with a Retry-After and the beat_cadence limit. Ignore them or retry after Retry-After. Either way nothing is lost, and the monitor stays on time.