Skip to main content
The heartbeat client ships in @peaqos/peaq-os-sdk 0.5.0+ (JS/TS) and peaq-os-sdk 0.5.0+ (Python) under the monetization namespace. It talks to the dedicated peaqOS heartbeat service; the peaq-hosted instance is https://heartbeat.peaq.xyz, and the base URL stays configuration, never a constant.
A machine that has opted into monetization reports that it is online by pushing a signed to the heartbeat service at a regular interval. A valid heartbeat keeps the machine online; when heartbeats stop, the server marks it offline. The client does not enforce the opt-in state itself: start it only on an opted-in machine (check with getMonetization / get_monetization), and stop it when the machine opts out.

Push, not poll

The machine is a client of the heartbeat service: it pushes a signed heartbeat out. It does not host an endpoint that others poll, and it never serves its own availability.

The 180-second TTL and the interval bound

A valid heartbeat marks the machine online for a fixed presence TTL of 180 seconds. Miss that window and the server flips the machine to offline. To stay online, the machine must heartbeat comfortably more often than the TTL, so the client caps the configured interval: start rejects an interval that is zero, negative, or above the maximum. The exact interval within that bound is caller-supplied configuration. Offline is server-derived. The client never self-reports offline. Stopping the client simply stops heartbeating; the server observes the absence and lets presence lapse. Operator-initiated maintenance is expressed the same way: stop the client.

Request-signing auth

There is no bearer credential. The server authenticates each heartbeat by verifying the signature against the publicKey in the payload.
  • The machine identity private key is held only by your signer implementation. The client passes it a canonical message and receives a hex signature; the client never handles or logs the raw key.
  • publicKey, signature, machineId, and sentAt are not secret and are safe to log.
The signature covers exactly this canonical message, byte for byte (two lines, LF-joined, no trailing newline):
Build it with buildCanonicalMessage / build_canonical_message, the single place this format is defined.

Quick start

Each tick sends exactly { machineId, publicKey, sentAt, signature } and parses { accepted, online, lastHeartbeatAt }, surfaced through the optional result callback.

Fire-and-forget

A failed push, whether a network error or an accepted: false response, is logged and retried on the next tick. It never throws out of the timer and never blocks the machine’s other operations. Every parsed response reaches the result callback, including rejections. A hung push cannot stall the loop either. In JS/TS the whole tick (signing plus push) is time-bounded to 30 seconds, or the interval if shorter. In Python the bound is per request: the transport’s 10-second HTTP timeout, with signing cancelled cooperatively rather than forced. A server that accepts the connection but never responds is timed out, logged, and retried on the next tick like any other failure. stop() is the one exception: it cancels the timer, aborts any in-flight request (including one still signing), and is treated as expected cancellation, not a failure. No error log, no result callback, no retry.

Presence query

checkPresence / check_presence is a standalone one-shot query with no scheduler and no signing. Pass exactly one of machineId or publicKey:
When the machine is offline or unknown, online is false and publicKey / lastHeartbeatAt are null.

Presence only

The heartbeat payload is a pure presence signal: it carries no status field and no queue depth. Presence is binary under this API: online (a valid heartbeat within 180 seconds) or offline (TTL expired). Utilisation states such as busy are not represented, and maintenance is expressed by stopping the client. Do not add extra fields to the payload.

API summary