Device integration
A device authenticates by signing every request with its private key — there is no login step and no stored session. Hiveron uses a deliberately strict subset of RFC 9421 (the "IoTNet v2" signature profile). The older base64 bearer-token scheme is no longer accepted.
The request signature
Every call to an /iot/* endpoint carries these three headers:
Content-Digest: sha-256=:BASE64_SHA256_OF_EXACT_BODY:
Signature-Input: sig1=("@method" "@target-uri" "content-digest");created=EPOCH;expires=EPOCH+300;nonce="32_LOWERCASE_HEX";keyid="device:ID:VERSION";alg="ecdsa-p256-sha256";tag="iotnet-v2"
Signature: sig1=:BASE64_64_BYTE_R_THEN_S:keyid is device:<id>:<key_version> (both shown in the console). The signature is raw big-endian r‖s (32 bytes each), base64-encoded — not ASN.1 DER. GET requests still send the digest of an empty body.
What gets signed
The signature is computed over this exact, newline-separated base (no trailing newline). Component and parameter order is exact; append "idempotency-key" when that header is present, then "range" when a Range header is present:
"@method": POST
"@target-uri": https://api.hiveron.net/iot/telemetry
"content-digest": sha-256=:BASE64_DIGEST:
"idempotency-key": 3f8a1c72-...-uuid
"@signature-params": ("@method" "@target-uri" "content-digest" "idempotency-key");created=EPOCH;expires=EPOCH+300;nonce="32_LOWERCASE_HEX";keyid="device:ID:VERSION";alg="ecdsa-p256-sha256";tag="iotnet-v2"Sign the UTF-8 base with ECDSA P-256 / SHA-256. The @target-uri is the public HTTPS URL plus the exact path and query — Host/forwarded headers can't select another origin, and redirects aren't supported.
How the server verifies it
- The signature is checked against the device's registered public key at the given key version.
- A fresh random 128-bit nonce is required per attempt; nonces are recorded and cannot be replayed (even across restarts).
createdmay be at most 30 seconds in the future; the signature expires within 5 minutes.- The device's key version and active state are checked in the same transaction as the operation.
The device needs a reasonably accurate clock. GET /time ({ "unix_ms": N }) is advisory — synchronize from trusted infrastructure, not solely from it.
The device loop
Devices poll: check for firmware, post telemetry, and drain queued commands. Commands are a peek / acknowledge queue — reading one does not consume it; acking does. Telemetry retries must reuse the same Idempotency-Key and identical body (a different body under the same key returns 409).
# The device SIGNS EVERY request (fresh nonce + timestamp each time).
# 1) firmware
GET /iot/fw_check # -> { fw_id, size, sha256 } (all null if none)
if fw_id changed:
GET /iot/fw_download # signed; raw bytes -> flash & reboot
# 2) telemetry (UUID Idempotency-Key required)
POST /iot/telemetry
Idempotency-Key: <uuid>
{ "content": "<reading>" } # -> 204 (keep the same UUID + body on retries)
# 3) commands (peek / acknowledge mailbox)
n = GET /iot/command_check # -> { count }
while n > 0:
cmd = GET /iot/command # -> { id, content } (peek oldest, or 204)
execute(cmd.content)
POST /iot/command_ack { "id": cmd.id } # -> 204 (409 if expired)
n -= 1Once the loop is running, the device's own page in the console shows its readings arriving and whether it is on the latest firmware:

Ready-made C helper
You don't have to implement the signing yourself. We provide a dependency-light C reference — create_request_headers() — that produces the three headers above. It bundles micro-ecc for P-256 (ESP32-friendly); you supply platform randomness, a trusted clock, and TLS validation. Using it is one call:
#include "device_token.h"
/* Provisioned on the device; the console shows dev_id and key_version. */
static const uint64_t dev_id = 1;
static const uint64_t key_version = 1;
static const uint8_t priv[32] = { /* 32-byte P-256 private key */ };
void post_telemetry(void) {
const uint8_t body[] = "{\"content\":\"21.4C\"}";
/* Your platform supplies a trusted clock, a fresh 32-hex-char nonce,
and a UUID idempotency key. */
device_headers h;
if (create_request_headers(
dev_id, key_version, priv,
now_epoch_seconds(), nonce_hex,
"POST", "https://api.hiveron.net/iot/telemetry",
body, sizeof(body) - 1,
idempotency_uuid, NULL, &h) != 0) {
return; /* invalid input */
}
/* Send these three as request headers over your TLS client: */
/* Content-Digest: h.content_digest */
/* Signature-Input: h.signature_input */
/* Signature: h.signature */
}