HEART Embedded Systems logoHEART

HEART Part 4: Connections#

Reading tracks1 Basic2 Intermediate
Basic: "In one minute", Sections 1, 2, 3.1 to 3.3, 3.5, 4.1 to 4.3, 4.6, 5, 6.6, 7.1 and 13, and 12 for microcontrollers. Intermediate: the rest (3.4, 4.4, 4.5, 6.1 to 6.5, 7.2 to 7.4, 8 to 11). Which track am I?

How to reach a HEART, and how messages travel safely

Author: Milind K. Patil, Syncaissa Systems Inc. Status: Working draft 1, November 2026. Part of the HEART Standards Family. Includes the corrections of Security Advisory HEART-2026-001. Version: 1.0 (draft) Read first: Part 1: Basics and Part 3: Information Model. Track: Basic: "In one minute", Sections 1, 2, 3.1 to 3.3, 3.5, 4.1 to 4.3, 4.6, 5, 6.6, 7.1 and 13, and 12 for microcontrollers. Intermediate: the rest (3.4, 4.4, 4.5, 6.1 to 6.5, 7.2 to 7.4, 8 to 11).


In one minute#

Part 3 says what a HEART says. This part says how it travels.

A HEART can be reached in five ways:

ConnectionUse it for
HTTPAlmost everything: apps, services, devices running Linux
GatewayMany systems sharing one address, and systems that cannot be reached directly
Unix socketPrograms on the same machine
MQTTConnected devices that already use a message broker
Serial lineMicrocontrollers, through a bridge

The rules that matter most:

  1. Port 43278 is HEART's standard port, with 7685 as the fallback.
  2. Discovery needs no login. GET /.well-known/heart-card.json always works, and on a local network systems announce themselves as _heart._tcp.
  3. Everything off the machine is encrypted with TLS, and the device proves who it is.
  4. A login token works for one system only, and only for whoever holds its key.
  5. The same messages and rules apply on every connection. Changing the connection never changes what is allowed.

1. What this part covers#

Think of Part 3 as the letter, and this part as the postal service. The letter says the same thing whether it goes by post, by courier or by hand. But each route has its own rules: addresses, stamps, signatures on delivery.

This part coversOther parts cover
Ports, addresses and how to find a systemThe messages themselves (Part 3)
HTTP: paths, headers, paging, errors, live streamsWho must approve what (Part 6)
Logging in: tokens and scopesWhat data may leave (Part 5)
Encryption and device identityThe meaning of Autonomy Levels (Part 2)
System-to-system messages and webhooks
The HEART Gateway, Unix sockets, MQTT and serial lines

The one rule above all others: every connection carries the same operations and messages, under the same checks. A system MUST NOT be more open on one connection than on another.


2. The connections at a glance#

A system MUST offer at least one of http-server, http-push, unix-socket or mqtt, and list them in its Card's bindings.

BindingWhat it meansTypical system
http-serverThe system answers HTTP requests itselfA service, an app back end, an embedded Linux board
http-pushThe system cannot be reached, so it sends its messages to a HEART Gateway, which serves themA browser app, a phone app, a device behind a home router
unix-socketHTTP over a local socket file, with no network portA program behind a Gateway on the same machine
mqttMessages on an MQTT 5 brokerA fleet of connected sensors
in-processInside one program, such as a page and its Web WorkerA browser app. Listed for discovery only: it does not meet the requirement above.

A microcontroller with only a serial port takes part through a bridge, which serves http-server for it (Section 12).


3. Finding a system#

3.1 The first request#

Every consumer starts the same way:

GET /.well-known/heart-card.json

This MUST work without logging in, and MUST return the public view of the Card. The Card then tells the consumer everything else: the API's address (endpoints.api), how to log in (auth), and other standards the system also speaks, such as MCP, A2A or OpenTelemetry.

The Card MUST come with a signature, in the Heart-Card-Signature header (Section 7.3), at Connected level and above.

3.2 Ports#

NamePortRole
HEART43278The standard port. It spells HEART on a phone keypad.
Fallback7685Used only when 43278 cannot be bound or reached.

Both ports are the same service, with the same rules. The fallback is never a weaker way in.

Systems that face the internet MAY use port 443 instead, behind normal HTTPS infrastructure.

Rules for a system (listener):

  1. Listen on 43278. If that port cannot be bound, use 7685.
  2. When running on 7685, record system.port.fallback in the Rhythm, and show the real port in the Card.
  3. On Linux, reserve 43278 so outgoing connections cannot take it: sysctl net.ipv4.ip_local_reserved_ports=43278.

Rules for a client:

  1. If the address names a port, use only that port.
  2. If not, try 43278, then 7685. A connect timeout of 3 seconds is recommended.
  3. Fall back only if the connection itself fails: refused, unreachable or timed out.
  4. Never fall back after a certificate failure, a login failure or any HTTP reply. Those are answers, not outages, and trying elsewhere could hand your token to an impostor.
  5. Check the same server identity on both ports.

3.3 The heart:// address#

heart:// is a short way to write an address. It is not a new protocol: it always means HTTPS.

AddressMeans
heart://plant-serverhttps://plant-server:43278/.well-known/heart-card.json
heart://plant-server/systems/pump-stationThe Card of pump-station, at the Gateway on plant-server
heart://plant-server/systems/pump-station/pulsehttps://plant-server:43278/heart/v1/systems/pump-station/pulse

The rule: add /heart/v1 before the path, and use port 43278 if none is given. An address ending at a system name means that system's Card.

3.4 Finding systems on a local network#

On a home or factory network, nobody knows the devices' addresses in advance. HEART systems announce themselves with DNS-SD over multicast DNS (mDNS), the method printers and smart speakers already use.

The service type is _heart._tcp, on the HEART port. A TXT record gives a few public facts, so a tool can list devices without contacting each one:

TXT keyMeaningExample
txtversVersion of this record. Always 1.1
idSystem IDcom.example.cool-air.ac-controller
pathWhere the public Card is/.well-known/heart-card.json
levelFeature Setfull
tls1 if the HEART uses TLS1
tplTemplate ID, if built from one (optional)heart/embeddedstandard/c-posix/v1.2611
ktThumbprint of the identity key (optional)NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs
gw1 if this is a HEART Gateway (optional)1

Rules:

  1. A system that serves HEART on a local network SHOULD announce _heart._tcp. A Gateway announces itself, not each system it hosts.
  2. The TXT record MUST contain only what the public Card already shows. It MUST NOT identify a person, and the instance name MUST NOT either.
  3. A system MUST NOT announce a HEART that is not reachable on that network. For example, one that listens only on loopback must stay silent.
  4. Discovery is a hint, never proof. Anyone on a network can announce anything. A client MUST still check the system's certificate and signed Card (Section 7) before trusting what it found. If kt is given, it SHOULD match the Card's identity, and a mismatch is a warning sign.
  5. The owner MUST be able to turn announcements off.

Tools: heart-templates/tools/heart_mdns.py finds HEART systems or announces one (standard library only), and heart-templates/tools/avahi/heart.service announces a Linux device through Avahi.

3.5 Watching a device from anywhere: HEART Hub#

A device that cannot be reached from outside can still be watched: it reports its public Pulse and alerts, outbound only, to a monitor site that its owner registered it with. Each device then has a private page, such as https://heartcortex.ai/device/3f9c0a71b2d4e865, that its owner may share. The rules, for devices and for sites, are in the HEART Hub profile. Anyone may run a monitor site with the open code in heart-monitor/.

A small sensor need not build envelopes: it may send a plain JSON light report (status, alerts by fault code, and figures), and may say how long it will sleep, so its silence is not taken for a Flatline. It may send it over HTTPS, or over MQTT through a small bridge that relays it to the site. See HEART Hub, Sections 5.4 to 5.6 and 7.


4. Talking HTTP#

4.1 The basics#

TopicRule
Base path/heart/v1
Messages about the systemSent as CloudEvents envelopes (Part 3), Content-Type: application/cloudevents+json
Lists{"items": [...], "nextCursor": ...}, as application/json
Requests to the system (invoke, ask, decide)Plain JSON bodies
VersionEvery response MUST carry Heart-Version: 1.0 (or the minor version in use). A request for an unsupported major version gets 400 unsupported-version.
Not offeredAn operation above the system's Feature Set, or in a module it does not claim, MUST answer 501 not-implemented

4.2 The main operations#

The full list of 124 operations is in the OpenAPI file, openapi/heart-api-1.0.yaml. These are the ones most consumers use:

RequestWhat it doesFeature Set
GET /.well-known/heart-card.jsonThe public CardBasic
GET /heart/v1/pulseThe PulseBasic
GET /heart/v1/arrhythmiasList alertsBasic
GET /heart/v1/cardThe Card, as much as you may seeConnected
GET /heart/v1/rhythmHistory, or a live streamConnected
POST /heart/v1/askAsk a question in plain languageConnected
POST /heart/v1/inboxSend a message from another systemConnected
POST /heart/v1/subscriptionsAsk to be sent messages (webhooks)Connected
GET /heart/v1/receiptsWhat data left, and to whomConnected
GET /heart/v1/capabilitiesWhat it can be asked to doConnected
POST /heart/v1/capabilities/{name}/invokeAsk it to do somethingFull
POST /heart/v1/actions/{id}/decisionApprove, deny or cancelFull
POST /heart/v1/tending/stopStop all automatic actionFull

The Pulse always answers. GET /heart/v1/pulse MUST return 200 whenever the HEART can respond at all, even when the system is failing. The bad news is in the body, not in the status code.

4.3 Headers#

HeaderDirectionRule
Heart-VersionResponseRequired
Idempotency-KeyRequestRequired on every POST that is not read, except acknowledge, resolve, silence and an Action's decision; without it, 400 invalid-request. Optional on PUT, PATCH and DELETE, which set or remove a whole value; with a key, a retry of one that waits for approval returns the same Action. Keys belong to one caller and one operation. Sending the same key again returns the same result, and never runs it twice. The system MUST remember keys for at least 24 hours. The same key with a different body gets 422 idempotency-conflict.
AuthorizationRequestThe access token (Section 6)
DPoPRequestProof that the caller holds the token's key (Section 6.3)
Heart-Card-SignatureResponseThe Card's signature (Section 7.3)
Heart-Key-RotationResponseFor 7 days after a key change: the statement, signed by the old key, that names the new one (Section 7.4)
Heart-Receipt-SaltBothThe salt of the Valve receipt, when non-public data goes to a receipted recipient (Part 5)
Heart-Principal, Heart-Clearance, Heart-Scopes, Heart-CallerRequestOnly from a Gateway to a hosted system, signed (Section 9.4)
traceparentBothW3C Trace Context. Systems SHOULD accept and pass it on.
ETag, Cache-ControlResponseThe Card SHOULD carry them
Retry-AfterResponseWith 429 rate-limited

4.4 Paging#

Long lists come a page at a time. The reply has a nextCursor. Send it back as ?cursor=... for the next page. nextCursor: null means the end. A page holds at most 500 items, and 100 by default.

If history was deleted to save space, the page MUST say gap: true, so nobody mistakes missing history for nothing happening.

4.5 Live streams#

GET /heart/v1/rhythm with Accept: text/event-stream keeps the connection open and sends each new event as it happens (Server-Sent Events). Each event has an ID. After a disconnect, reconnect with Last-Event-ID and the stream continues where it stopped, with nothing lost.

4.6 Limits#

Every operation MUST be rate-limited, including discovery and the Pulse. Too many requests get 429 rate-limited with Retry-After.


5. Errors#

Every error uses the same format (RFC 9457), with a stable code that programs can rely on:

{
  "type": "https://heartcortex.ai/problems/insufficient-clearance",
  "title": "Insufficient clearance",
  "status": 403,
  "detail": "Reading rhythm requires internal clearance.",
  "code": "insufficient-clearance"
}
CodeHTTPMeaning
invalid-request400The body or parameters are wrong
unsupported-version400This major version is not spoken here
unauthenticated401No login, or the token is invalid, expired or for another system
token-not-bound401Over a network, the token must be bound to the caller's key
insufficient-clearance403Not allowed to see this, or missing a scope
capability-not-allowed403This kind of caller may not do this
caller-not-allowed403A Proposal names a target closed to AI. Only Proposals get this code
presence-required403A person must prove they are present for this exact action (Part 6)
presence-invalid403The proof of presence did not check out
valve-blocked403The Valve refused to release the data (Part 5)
signature-required403A Connected or Full system must sign the Card it joins with
signature-invalid403The Card's signature does not check out
not-found404No such thing, or you may not know it exists
conflict409Wrong state, such as deciding a finished action
identity-changed409The system joined before with a different identity key
autonomy-stopped409Refused because automatic action is stopped
guard-failed409A guard or interlock refused the command
idempotency-conflict422A retry key was reused with a different body
out-of-range422A value is outside what is allowed
cannot-undo422A trial change was refused because it could not be undone
budget-exceeded429The AI spending limit was reached
rate-limited429Too many requests
internal-error500Something unexpected went wrong
not-implemented501Not offered by this system
native-failed502The device returned an error
system-unreachable503A Gateway could not reach the hosted system
model-unavailable503No AI model could be reached
native-timeout504The device did not answer in time

6. Logging in#

6.1 Tokens#

A caller logs in with an access token from the owner's login service, using OAuth 2.0 (client credentials), or with a client certificate (mutual TLS). A token is a signed note that says who the caller is and what they may do.

What the token saysClaimRule
WhosubThe caller's identity
Which system it is foraudMUST name this system's ID. A system MUST refuse a token for any other system.
What they may seeheart_clearancepublic, internal or private
What kind of callerheart_callerhuman, peer, ai or rule. Set by the login service from how the caller was registered, never chosen by the caller. If missing, the caller counts as peer.
What they may doscopeThe scopes below
Acting for someone elseactIf present, the caller is an AI agent (Section 6.4)
Whose key it is bound tocnf.jktThe fingerprint of the holder's key (Section 6.3)
Who issued itissMUST be the issuer named in the Card's auth.issuer
When it expiresexpAt most 15 minutes after issue. A system MUST refuse an expired token, allowing at most 60 seconds of clock difference.

Tokens are JWTs as RFC 9068 describes. iss, aud, exp, sub, scope, heart_clearance and heart_caller MUST all be present, and cnf when the token is bound to a key; a token missing any gets 401 unauthenticated. The system checks the signature with the issuer's published keys. A token cannot be called back once issued, which is why it lives so briefly. A Full system MAY also ask the issuer about each token (introspection, RFC 7662), for owners who need to cut access at once.

Client certificates. A certificate names its caller in a SAN URI. The owner keeps a table that gives each one its clearance, scopes and caller kind: the grants that name a certificate (Section 6.5). A certificate not in the table counts as peer, clearance public, with no scopes.

6.2 Scopes and clearance#

There are two separate checks on every request:

  • Scope says what a caller may do.
  • Clearance says what a caller may see.
ScopeAllows
heart.readReading anything above the public view, up to the caller's clearance. Without it, a caller gets the public view.
heart.askAsking questions
heart.sendSending messages to the inbox, and joining a Gateway
heart.subscribeManaging webhooks
heart.tendAcknowledging, resolving, silencing and reporting alerts, and stop
heart.proposeDelivering a brain's Proposal. Nothing else, and never together with heart.approve
heart.invokeAsking for actions
heart.approveApproving actions. Never given to AI agents.
heart.reasonCalling a Reasoning Gateway
heart.configureChanging properties, settings, schedules and custom commands
heart.operateDiagnostics, power and software operations
heart.adminPolicies, autonomy, the allowlist, keys, peers and Playbooks
heart.dataConsent and personal data requests
heart.fleetFleet commands and hosted systems at a Gateway
heart.nativeRaw device protocol access

A system with no login at all may still conform at Basic, serving public views only.

6.3 A token is bound to its holder#

A token that anyone can copy and use is like a house key without a lock on the copying machine. So:

  1. Over a network, every token MUST be bound to a key the caller holds, with DPoP (RFC 9449) or a client certificate (RFC 8705). Only connections that never leave the machine (loopback and Unix sockets) are excused. An unbound token from the network gets 401 token-not-bound.
  2. With DPoP, each request carries a fresh DPoP proof: a short note signed with the caller's key, naming the method, the URL, the time and the token. The system MUST check that the key matches cnf.jkt, that the proof is under a minute old, and that it has not been used before.
  3. A token bound to a key and sent without proof of that key MUST be refused.

So a stolen token is useless without the key, and a captured proof cannot be replayed.

6.4 AI agents are never people#

People increasingly connect AI assistants to their accounts. An assistant then holds a token that says human. The rules:

  1. A token issued to a client registered as an agent, or whose act claim (delegation, RFC 8693) names any actor that is not a HEART Gateway marked heart_gateway: true by the issuer, MUST be treated as ai, whatever heart_caller says. A token whose only actors are marked Gateways keeps the subject's heart_caller (API Standard 8.2).
  2. Such a token MUST NOT carry heart.approve.
  3. A person's own token can still be pasted into an agent, so approvals also need proof of presence (Part 6).

6.5 Roles: who may call a system#

An EV charger is shared with its installer, a pump station with the operators on call, a transmitter with a clinic. The owner gives each of them a grant (PUT /heart/v1/access/grants/{principal}, module access), and each grant gives one role:

RoleMay doMay see
ownerEverything but deliver a brain's Proposalprivate
installerRead, tend, invoke, configure and operateinternal
maintainerRead, tend, invoke and operateinternal
operatorRead, ask, subscribe, tend, invoke and approveinternal
viewerRead and askinternal
  1. While a system holds any grant, a caller can do only what both its token and its role allow, and see only what both allow. A caller with no grant counts as a stranger: peer, public, no scopes.
  2. A grant may name a client certificate. The grants are then the certificate table of Section 6.1.
  3. Giving access, or widening it, is destructive: the owner proves presence (Part 6, Section 4.1). Narrowing or removing access is write.
  4. The first grant gives the owner role, and the last owner grant cannot be removed. Nobody can lock the owner out by accident.

6.6 Small devices: the owner key#

A thermostat or a birdhouse has no login service. Below HEART Connected, a system MAY use one owner key instead of tokens (API Standard 8.2):

  1. The device makes the key at first start, with at least 128 random bits, and shows it once, only at the device: its screen, its console, or a card in the box.
  2. It keeps only a hash of the key, and compares a presented key by that hash, in constant time.
  3. The owner sends it as Authorization: Bearer <owner key>, over TLS or within one machine. Whoever holds it is the owner, with clearance private.
  4. The owner can replace the key at the device. The old key stops working at once. Releasing, transferring or resetting the device replaces it too.
  5. The key never leaves the device: not to a HEART Hub, not to a Command Center, not to the maker.
  6. From HEART Connected up, the owner key is not accepted. Tokens bound to their holder are.

7. Encryption and identity#

7.1 Encryption#

All traffic MUST use TLS 1.2 or later, except inside one machine (loopback, Unix sockets, in-process). A system MUST refuse to serve plain HTTP on any network address.

7.2 Every device has an identity#

Encryption only helps if the client can tell the real device from an impostor. A device on a home or factory network has no public name for an ordinary certificate. So:

  1. Identity key. Every system at Connected level or above MUST have its own key pair. The private key never leaves the system, and SHOULD live in secure hardware where there is any.
  2. Maker certificate. A device SHOULD carry a certificate for that key from its maker (IEEE 802.1AR style).
  3. Claiming. The one-time code for taking ownership MUST be unique per device, come in the box (a label or QR code), and never be derived from public data such as a serial number. Claiming SHOULD use SPAKE2+, as Matter does, so the code cannot be captured on the network. The owner records the device's key when claiming.
  4. Checking the server. A client MUST accept a certificate only if it comes from the owner's or maker's authority, or its key matches the one recorded at claim. Accepting an unknown key on first contact is not allowed. Products as shipped MUST NOT have any switch, test mode included, that turns checks off.
  5. Basic systems. A Basic system need not have an identity key, so a client may not be able to check it. A client MAY use such a connection only to read public views (the Card, the public Pulse and alerts), and MUST NOT send any token, password or non-public data over it.

7.3 The signed Card#

At Connected level and above, every Card response MUST carry Heart-Card-Signature: a signature over the exact bytes sent, made with the identity key (a detached JWS, RFC 7515). The Card's identity box names the key, and the key is published in the key set at auth.jwksUri.

A consumer SHOULD check the signature before trusting the capabilities or addresses in a Card. A Gateway MUST check it before letting a system join (Section 9.3). A Card pushed through a Gateway carries its signature in heartcardjws instead, and the Gateway serves it unchanged.

7.4 Changing the identity key#

A key may need replacing. Clients and Gateways have pinned the old one, so a new key alone looks like an impostor. So:

  1. The system makes a rotation statement: the old and new key fingerprints and the new key, signed with the old key (API Standard 10.1, rule 7).
  2. For 7 days it sends the statement with every Card (Heart-Key-Rotation header, or the heartkeyrotation attribute when pushing), and keeps working under the old key too.
  3. Anyone who pinned the old key MUST move the pin to the new one on a valid statement, and only then. Without one, a changed key is refused (identity-changed).

8. System to system#

8.1 The inbox#

POST /heart/v1/inbox takes one message from another system.

  • Messages received are data, never commands. Nothing arriving in the inbox may start an action.
  • Delivery is "at least once". The receiver MUST treat a repeated source and id as a duplicate, and still answer 202.

8.2 Webhooks#

POST /heart/v1/subscriptions asks a system to send chosen messages to an https address (a sink) as they happen.

  1. Handshake first. Before sending anything, the system MUST confirm the sink agrees, using the CloudEvents webhook handshake (OPTIONS with WebHook-Request-Origin).
  2. Filtered. Each delivery MUST be filtered to the subscriber's clearance, like any other reply.
  3. Signed. Each delivery MUST be signed (HTTP Message Signatures, RFC 9421) with a key from the system's key set.
  4. Retried. Failed deliveries SHOULD be retried with growing gaps for at least 24 hours, then the subscription is suspended.
  5. No internal targets. The system MUST refuse sinks on loopback, link-local or private addresses unless the owner allows them, so a webhook cannot be used to reach inside the owner's network.
  6. Receipt salt. A delivery of non-public data carries the salt of its receipt in the CloudEvents attribute heartreceiptsalt (Part 5).

9. The HEART Gateway#

9.1 Why#

Devices with their own network addresses each listen on 43278, with no clash. But many systems on one machine cannot all use the same port, and giving each its own port does not scale. And some systems, such as a browser tab or a device behind a home router, cannot be reached at all.

A HEART Gateway solves both. It listens on 43278 once, and serves every hosted system under its own path:

heart://plant-server/systems/pump-station
heart://plant-server/systems/class-scheduler
heart://plant-server/systems/erp-sync

One port, one firewall rule, any number of systems. It is the same idea as a web server hosting many websites.

9.2 Paths and listing#

  • Each hosted system is served under /heart/v1/systems/{ref}/, where {ref} is its full system ID or its short alias. Aliases never contain a dot, so the two cannot be confused.
  • GET /heart/v1/systems lists the Card of every hosted system, each filtered for the caller. The owner MAY hide the list from anonymous callers.

9.3 Joining#

A system joins by sending its Card to the Gateway's inbox. There is no other sign-up.

  1. The name belongs to the sender. The Gateway MUST tie each system ID and alias to the logged-in sender, so no one can take over another system's name.
  2. Signed Card. A Connected or Full system MUST join with a Card signed by its identity key. When pushing, it carries the signature as an attached JWS, including its public key, in the CloudEvents attribute heartcardjws. The Gateway MUST check the signature, check that the key matches the Card's identity, and refuse anything else (signature-required, signature-invalid).
  3. Pinned key. The Gateway remembers the key a system first joined with. A later join with a different key MUST be refused with identity-changed, unless it comes with a valid rotation statement (Section 7.4), or the owner has removed the system.
  4. The Gateway's own key. When joining, the system reads the Gateway's Card over a checked connection and remembers the Gateway's key. Caller headers (Section 9.4) are checked against that key.
  5. Routed or pushed. A Card that gives endpoints.api (https) or endpoints.socket makes the system routed: the Gateway passes each request to it live. A Card with neither makes it pushed: the Gateway stores and serves what the system sends. The Gateway MUST apply the same address restrictions as for webhooks.

9.4 Passing on the caller#

The Gateway checks the caller's login. The hosted system still needs to know who the caller is, so its own Valve can filter the reply.

  1. The Gateway passes the verified caller in four headers: Heart-Principal (the caller's sub), Heart-Clearance (public, internal or private), Heart-Scopes (the scopes, separated by spaces) and Heart-Caller (human, peer, ai or rule). The exact formats are in API Standard 9.3.
  2. Over a network, the Gateway MUST sign these headers (RFC 9421, label heart), covering the method, the full address, the four headers and the time (created), with its identity key. The system MUST check the signature against the Gateway key it remembered at join, and refuse one older than 60 seconds.
  3. The Gateway MUST NOT pass on the caller's own token. A hosted system chose its own address when it joined, so it could capture the token and use it elsewhere. If a system needs a token, the Gateway gets a new one made for that system only (token exchange, RFC 8693), with no more rights than the caller had.
  4. The Gateway MUST remove these four headers from every incoming request, and a system MUST trust them only when signed by its Gateway, or from its Gateway's verified process on a Unix socket (Section 10).
  5. The Gateway MUST NOT serve one caller's non-public reply to a different caller.

The Valve stays in each system. The Gateway may handle encryption, logins and rate limits in one place, but filtering happens inside each system, before data leaves it. A compromised Gateway therefore sees only what each caller was already allowed to see.

9.5 Pushed systems#

  1. The system's own Valve filters before pushing, so only what was pushed is ever available. It pushes one copy of each message for each clearance level it shares.
  2. The Gateway MUST NOT change or filter pushed messages itself. It gives each caller the copy for the highest level that caller may see, byte for byte, or nothing. It MUST serve the last Pulse unchanged, so consumers can apply the Flatline rule.
  3. ask needs the live system, so the Gateway answers 501 for a pushed system.
  4. A pushed system SHOULD keep a bounded buffer of messages while the Gateway is unreachable, and resend them with their original IDs.

Commands through the channel the device already holds. A device behind a home router or a mobile network cannot be reached from outside, but it can keep asking its Gateway for work. That is its command channel (API Standard 9.3):

  1. The device sends its messages to the Gateway's inbox with Prefer: wait=25. The Gateway holds the answer until a command is waiting, or 25 seconds pass, and then answers with the commands. The device asks again at once.
  2. A caller sends an ordinary HEART request to the device's Gateway path. The Gateway turns it into a command: the request, the caller it checked, an ID, and a lifetime of at most 60 seconds, all signed with the Gateway's identity key.
  3. The device checks the signature against the Gateway key it remembered at join, checks that the command is for it, still fresh, and new to it, and drops anything else without answering. It then runs the request through its own API and gate, exactly as if it had come in from the Gateway, and sends the answer back as a heart.reply. The Gateway gives that answer to the caller.
  4. Nothing about safety moves to the Gateway. The device still checks scope and clearance, filters with its own Valve, needs proof of presence where the gate does, and obeys Stop.

A pushed system with a command channel can be commanded, so it may claim HEART Full. One without a channel cannot be commanded through the Gateway, and stays at Connected.

9.6 The Gateway is a system too#

A Gateway MUST have its own Card and Pulse, and follows this standard like any other system. It is also a shared point of failure, so:

  1. Hosted systems MUST keep looking after themselves when the Gateway is down.
  2. Production deployments SHOULD run more than one Gateway.
  3. A Gateway SHOULD raise a system.flatline alert for a hosted system that goes quiet for three Pulse intervals.

10. Unix sockets#

Programs on the same machine as a Gateway can talk to it through socket files instead of network ports. The protocol is the same HTTP, without TLS.

SocketWho listens
/run/heart/gateway.sockThe Gateway, for systems joining or pushing
/run/heart/systems/{systemId}.sockA routed system, for requests from the Gateway
  1. Sockets MUST NOT be open to every user on the machine.
  2. File permissions are not enough. Both sides MUST check the other process's identity through the operating system (SO_PEERCRED or its equivalent). A system's socket MUST accept the Gateway's caller headers only from the Gateway's own user. Where this check is not available, the headers must be signed, as over a network.
  3. Socket paths are internal, so they never appear in a public Card.

11. MQTT#

For devices that already use an MQTT 5 broker. MQTT uses the broker's own port, not 43278 or 7685. {instance} is - for a system with no instance, and {level} is public, internal or private.

TopicCarriesKept on the broker?
heart/v1/{systemId}/{instance}/card/{level}The CardYes
heart/v1/{systemId}/{instance}/pulse/{level}The PulseYes
heart/v1/{systemId}/{instance}/rhythm/{level}EventsNo
heart/v1/{systemId}/{instance}/arrhythmia/{level}AlertsNo
heart/v1/{systemId}/{instance}/cmd/{operationId}Commands to the deviceNever
heart/v1/{systemId}/{instance}/reply/{clientId}Replies to one clientNo
  • Messages use the CloudEvents MQTT binding, structured JSON. Commands have the CloudEvents type heart.command.{operationId}, with data = {"path", "query", "body"} of the HTTP request. A command sets MQTT 5's Response Topic and Correlation Data; the reply, type heart.reply, has data = {"status", "headers", "body"} of the HTTP response, so every status and problem code means the same as over HTTP.
  • One topic per level. A device publishes one copy of each message for each level it shares, filtered to that level. The broker's access rules MUST let a client read a {level} topic only with that clearance or higher. A device that publishes internal data on a public topic does not conform.
  • A device SHOULD set an MQTT "last will" that publishes a public Pulse with status unknown, so its silence is noticed.
  • A device that only reports to HEART Hub may instead publish its light report on heart/v1/hub/{deviceId}/report, for a bridge to relay over HTTPS. Those topics and their rules are in HEART Hub, Section 5.6.

11.1 Commands must be signed#

The broker's rules say who may publish on a topic. They say nothing about who sent this particular command, what they may do, or whether it is a recording being played again. So every command MUST:

  1. Carry a token for this device (aud = its system ID), in the MQTT user property heart-token.
  2. Be signed with the key that token is bound to, as a compact JWS (ES256 or EdDSA) in the MQTT user property heart-sig. Its payload is {"sys", "op", "sha256", "id", "exp"}: the system ID, the operation, base64url SHA-256 of the exact data bytes, the message ID, and an expiry at most 60 seconds ahead (seconds since 1970). The exact rules are in API Standard 9.5.
  3. Be checked by the device exactly as over HTTP: scope, clearance and the approval gate.
  4. Be refused if it has expired (allowing 60 seconds of clock difference), if any claim does not match the message, or if its ID has been seen before.

A device too small to check signatures MUST NOT take commands over MQTT. It is commanded through a Gateway instead. Replies SHOULD be signed with the device's identity key, in the same form.


12. Small devices: HEART Line v1#

A microcontroller is too small to serve web requests or check signatures. It still has a HEART: it notices its own problems. It reports them as short lines on a serial port, and a bridge on a nearby computer serves HEART for it. This is the delegation pattern of the HEART Core Standard, Section 8.

 microcontroller ──serial line──► bridge ──HTTP──► anyone asking
  notices problems itself          adds time, IDs,   └──► HEART Gateway (optional)
                                   serves HEART Basic

12.1 The lines#

One JSON object per line: UTF-8, at most 1024 bytes including the line end, ending with a newline (\n; a \r before it is ignored). The serial settings are the device's own; 115200 baud, 8 data bits, no parity, 1 stop bit is RECOMMENDED. Every line has t, its type.

LineWhenExample
cardOnce, at start{"t":"card","id":"org.example.my-device","name":"My Device","version":"0.1.0","instance":"unit-1","template":"heart/embeddedstandard/c-baremetal/v1.2611","interval":30}
pulseEvery interval{"t":"pulse","seq":12,"status":"healthy"}
alertWhen a problem starts{"t":"alert","code":"system.main-function-lost","severity":"critical"}
resolvedWhen it ends{"t":"resolved","code":"system.main-function-lost"}
FieldMeaning
intervalSeconds between pulse lines, 1 to 86400
seqCounts up by one per pulse line, starting at 0 after each card line
statushealthy, degraded or failing
codeA fault code (Part 3). A device has at most one open problem per code.
severityinfo, warning, error or critical. Default error.
textOptional plain-language explanation, at most 200 characters

A card line starts a new instance, as after a reboot: open problems from before it are resolved by the bridge, and seq starts again.

12.2 Rules for the device#

  1. The device MUST notice problems and send alert and resolved itself, so it keeps working with no network.
  2. It MUST send a card line at start, and a pulse line at least once per interval.
  3. It MUST NOT accept commands over the serial line in version 1.0. HEART Line v1 carries reports only.

12.3 Rules for the bridge#

  1. The bridge MUST serve nothing for a device until it has received a card line.
  2. It MUST turn each line into the matching Part 3 message, adding the time, a unique ID and the source. An alert becomes an Arrhythmia with that code and severity, a new UUID, detectedAt set to when the line arrived, and text (or the code's meaning from Part 3) as explanation. Each pulse becomes a Pulse with the bridge's own sequence, which keeps rising across device restarts.
  3. It MUST raise an alert only once per open problem, and resolve it on resolved.
  4. If no line arrives for three intervals, the device's status MUST become unknown (the Flatline rule).
  5. It MAY push the messages on to a HEART Gateway, logged in as the device's owner.
  6. The Card it serves keeps the device's template value, so everyone can see what the device runs.
  7. A line that is not valid JSON, is too long, or has an unknown t MUST be ignored and counted, never guessed at. Unknown fields are ignored.

Tested templates for both sides are listed in heart-templates/README.md.


13. Choosing a connection#

Your systemUse
A service or app back end with a network addresshttp-server on 43278, with TLS
Many services on one machineA Gateway on 43278, with each service on a Unix socket
A browser app, phone app or device behind a home routerhttp-push to a Gateway
A fleet of devices already on MQTTmqtt, with signed commands
A microcontroller with a serial portHEART Line v1 and a bridge
A small sensor that only reports to HEART HubA light report over HTTPS, or over MQTT through a bridge (HEART Hub, Sections 5.4 and 5.6)
A service on the internet behind existing HTTPSPort 443, with the same paths

14. Changes needed in other files#

ChangeWhere
When the parts replace the API Standard, Sections 5, 8, 9 and 10.1 move hereHEART-API-Standard-1.0.md
Add serial-line as a recognised delegation link, without making it a Card binding (the bridge serves http-server)schemas/1.0/card.schema.json description
Publish a schema for HEART Line v1 recordsNew schemas/1.0/heart-line.schema.json
Add the heartcardjws, heartreceiptsalt and heartkeyrotation CloudEvents attributes to the envelope schema (done in the API Standard, Section 3.1)schemas/1.0/envelope.schema.json

15. Open items#

ItemWhy it matters
Port registrationPorts 43278 and 7685 are not yet registered with IANA. IANA usually gives one port per service, so 7685 may stay an unregistered convention.
The heart URI scheme and well-known pathNeither is registered yet. Both need a published specification first.
The heart service nameDNS-SD needs the service name heart registered in IANA's service name registry (RFC 6335), with the port.
Compact binary (CBOR and CoAP)JSON may be too heavy for the smallest devices. A CBOR profile, with COSE signatures, is planned.
Commands over HEART LineVersion 1 lines carry reports only. Commands would need signing on devices that can check signatures.
Levels per connectionA system might offer Full on one connection and only Connected through a Gateway. A Card should be able to say so.
Bluetooth and MatterPossible future connections, through the same rule: same messages, same checks.