Skip to content

Device integration

A device authenticates to the /iot/* endpoints by minting a short-lived token, signed with its private key. There is no login step and no stored session — every request carries a fresh, self-contained token.

The token format

The token is a base64-encoded 76-byte buffer:

base64(  dev_id (4 bytes, big-endian int32)
       ‖ unix_ms (8 bytes, big-endian uint64)
       ‖ ECDSA P-256 / SHA-256 signature (64 bytes, raw r ‖ s) )

The signature is computed over the first 12 bytes (the dev_id and unix_ms) using the device's private key. Send it as Authorization: Bearer <token>.

How the server verifies it

  • The signature is checked against the public key stored on the device record.
  • The timestamp must not be in the future.
  • The timestamp must be strictly newer than the last accepted one for that device — this is the replay protection, so a captured token can't be reused.

Because of the timestamp check, the device needs a trusted clock: fetch it from the public GET /time endpoint ({ "unix_ms": N }) right before signing.

Example: sign a token and post telemetry (Python)

python
import base64, struct, requests
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import ec, utils

DEV_ID = 4
PRIVATE_KEY_HEX = "cdc3551f..."   # 32-byte P-256 private scalar (hex)
API = "https://api.hiveron.net"

def fetch_unix_ms() -> int:
    # The trusted clock. On real hardware this is your only HTTP dependency.
    return requests.get(f"{API}/time").json()["unix_ms"]

def create_auth_token() -> str:
    ts = fetch_unix_ms()
    header = struct.pack(">iq", DEV_ID, ts)      # 4-byte BE int + 8-byte BE long
    priv = ec.derive_private_key(int(PRIVATE_KEY_HEX, 16), ec.SECP256R1())
    der = priv.sign(header, ec.ECDSA(hashes.SHA256()))
    r, s = utils.decode_dss_signature(der)
    sig = r.to_bytes(32, "big") + s.to_bytes(32, "big")   # raw r ‖ s (64 bytes)
    return base64.b64encode(header + sig).decode()

token = create_auth_token()
requests.post(f"{API}/iot/telemetry",
              headers={"Authorization": f"Bearer {token}"},
              json={"content": '{"temp_c": 21.4}'})

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.

pseudocode
# The device is a mailbox client. A typical loop:
token = create_auth_token()          # fresh token per request

# 1) firmware
fw = GET  /iot/fw_check              # -> { "fw_id": <id or -1> }
if fw_id changed:
    binary = GET /iot/fw_download    # raw bytes -> flash & reboot

# 2) telemetry
POST /iot/telemetry { "content": "<reading>" }   # -> 204

# 3) commands
n = GET /iot/command_check           # -> { "count": n }
while n > 0:
    cmd = GET /iot/command           # -> { "id", "content" }  (peek oldest)
    execute(cmd.content)
    POST /iot/command_ack { "id": cmd.id }        # -> 204, removes it
    n -= 1

Once the loop is running, the device's own page in the console shows its readings arriving and whether it is on the latest firmware:

app.hiveron.net/admin/devices/4
A device page: the firmware card with an “update available” badge, the keys card, the telemetry chart, and the recent-readings list.

Ready-made helpers

You don't have to implement the crypto yourself. We provide a ready-made create_auth_token() helper in three languages — only the GET /time call is left as a platform stub for you to wire up:

  • C — dependency-free, bundles micro-ecc for P-256 (ESP32-friendly).
  • C++ — Arduino-friendly.
  • Python — uses the cryptography package.

Download the helpers below. The archive contains the C, C++, and Python sources, the bundled micro-ecc library, and a README with build instructions.