Point your existing systems at Lumra over a signed webhook or the events API. Every event is sealed the instant it arrives, and when you enroll your device or platform keys, Lumra verifies each source's own signature so the record comes back source-signed. Each event is chained and returned with everything needed to prove it later. No new hardware, no media uploads.
Point your existing webhook feed at Lumra and get sealed, independently verifiable records back. No reshaping, no signing code, no new hardware. Whatever JSON your platform already sends is sealed as-is; Lumra reads a timestamp and device id out of it for you. This path is cloud-signed at intake; source-signing (your device's own signature) is the upgrade in the section below.
# Point your feed at /v1/ingest/raw with just your key. The whole body is the # event; the "raw" adapter seals any shape. Here, a native TTN uplink sent as-is: curl -X POST https://api.privinet.net/v1/ingest/raw \ -H "Authorization: Bearer pk_live_your_key" \ -H "Content-Type: application/json" \ -d '{"end_device_ids":{"device_id":"reefer-8842"},"received_at":"2026-07-10T01:00:00Z","uplink_message":{"decoded_payload":{"temperature":11.5}}}' # -> the record is sealed and independently verifiable { "event_id": "493c7708-...", "proof_status": "cloud_signed" }
https://api.privinet.net/v1/ingest/raw and add a header Authorization: Bearer <your key>. Enable the uplink message. That is the whole setup: every uplink now seals automatically, with no payload formatter and no code. The same pattern works for Samsara, Motive, Geotab, a VMS, or your own backend.Want to check a payload before you send it for real? Dry-run it. You get back the exact record and hash Lumra would seal, and nothing is stored or billed:
# Validate any payload; nothing is recorded or metered. curl -X POST https://api.privinet.net/v1/events/dry-run \ -H "Authorization: Bearer pk_live_your_key" \ -H "Content-Type: application/json" \ -d '{"adapter":"raw","payload":{"end_device_ids":{"device_id":"reefer-8842"}, ...}}' { "ok": true, "would_seal": { "event_type": "raw.event", "source_id": "reefer-8842", "normalized_payload_sha256": "a5510938..." } }
Post an event and get the full result back in one call. Authenticate with your partner key in the Authorization header.
Point your platform's webhook at Lumra and sign each delivery with your webhook secret. Retries are safe: a repeated event is recognized, not duplicated.
Authorization: Bearer <key>. The secret signs your webhook bodies. Re-issuing credentials replaces both immediately: only the newest pair works. The samples use the live pilot host. Get credentials instantly with self-serve signup; the interactive docs at /docs have an Authorize button that takes your key.curl -X POST https://api.privinet.net/v1/demo/metalert-geofence-exit -H "Content-Length: 0"curl https://api.privinet.net/v1/demo/sample-proofpackcurl -X POST https://api.privinet.net/v1/webhooks/metalert -H "X-Signature: sha256=wrong" -d "{}"); a body over 1 MB returns 413./v1/demo/* calls above are a free sandbox: they create no record on your account and never bill you, so explore freely. A ProofPack for one of your own incidents (GET /v1/events/{id}/proofpack) is the assembled, court-ready deliverable you request when a dispute lands; it is a paid item, and every 60-day pilot includes one free. Reading your events and verifying any record is always free.# Memory-care example: a resident wandering (geofence exit). The "metalert" # adapter normalizes this to a gps.* event, which runs the wandering-risk # analyzer. Risk rises with: a geofence EXIT (not enter), night local time # (derived from timestamp + longitude), low battery, active movement, and no # caregiver acknowledgment. Generic event_types get a signed record but no # memory-care risk interpretation, so send these fields to get it. curl -X POST https://api.privinet.net/v1/events \ -H "Authorization: Bearer pk_live_your_key" \ -H "Content-Type: application/json" \ -d '{ "adapter": "metalert", "payload": { "external_event_id": "SMCG-ELP-2026-07-07-001", "device_id": "resident-wearable-3B", "event_type": "geofence_exit", "timestamp": "2026-07-07T09:14:00Z", "lat": 34.0522, "lng": -118.2437, "battery": 17, "speed": 1.6, "zone_id": "home", "zone_name": "Home", "caregiver_acknowledged": false } }' # Response (trimmed): a signed, chained record you can read back by id. { "event_id": "164b999f-...", "status": "reported", "analysis_summary": { "classification": "high_risk_wandering", "risk_score": 1.0, "urgency": "critical", "escalation_required": true, "recommended_action": "Notify the primary caregiver immediately..." }, "proof_status": "cloud_signed", "proof_id": "b2f1c3a4-...", "report_id": "9d8e7f6a-..." } # Idempotent by design, on ANY adapter: retry with the SAME external_event_id in # the payload, OR send an "Idempotency-Key: <your-id>" header, and you get the # SAME event back ("deduped": true), never a duplicate. Safe to retry on timeout.
Endpoint Does Auth POST /v1/events Ingest one event through the full pipeline, return the signed result Bearer key GET /v1/events/{id} Read an event back Bearer key GET /v1/events/{id}/analysis Risk score, escalation decision, explanation factors Bearer key GET /v1/events/{id}/proof The signed proof record Bearer key POST /v1/events/{id}/verify Recompute the hashes and re-check the signature Bearer key GET /v1/events/{id}/proofpack The court-ready ProofPack export for that incident Bearer key POST /v1/webhooks/{partner} Signed inbound receiver (see below) HMAC signature
Compute an HMAC-SHA256 of the exact request body using your webhook secret, and send it as X-Signature: sha256=<hex>. A missing or wrong signature is rejected. A repeated external_event_id returns the original result, so retries never double-record.
# Sign and deliver, in a few lines. BODY='{"adapter":"metalert","payload":{"external_event_id":"evt-1", ...}}' SIG="sha256=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$WEBHOOK_SECRET" | awk '{print $2}')" curl -X POST https://api.privinet.net/v1/webhooks/metalert \ -H "X-Signature: $SIG" \ -H "Content-Type: application/json" \ -d "$BODY" # First delivery -> creates the record. Retried delivery -> { "deduped": true, "event_id": "164b999f-..." }
By default Lumra seals every event at intake. To have the record also carry your device or platform's own signature, enroll that key's public half once, then sign each event at the source. Lumra verifies the signature against your enrolled key and the record comes back edge_signed_verified, over the normal webhook or events API, with no query parameter. Enrolling a key you do not control gains nothing: only real signatures from it verify. The private seed never leaves your machine; only the public key is enrolled and published.
# 1. Enroll your device or platform public key (once). curl -X POST https://api.privinet.net/v1/portal/source-keys \ -H "Authorization: Bearer pk_live_your_key" \ -H "Content-Type: application/json" \ -d '{"key_id":"reefer-8842","public_key_hex":"<your ed25519 public key hex>","signer_type":"edge_device_key","label":"reefer logger"}'
Sign the SHA-256 of the canonical payload at the source. The canonicalization must match Lumra byte-for-byte: sorted keys at every level, compact separators, UTF-8, and no ASCII-escaping of unicode.
# 2. Sign each event at the source (Python; a Node version is identical in shape). import hashlib, json from nacl import signing def canonical(payload): return json.dumps(payload, sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode() sk = signing.SigningKey(bytes.fromhex(SEED_HEX)) # your secret 32-byte seed digest = hashlib.sha256(canonical(payload)).hexdigest() sig = sk.sign(digest.encode()).signature.hex() incoming_proof = { "signature": sig, "signer_type": "edge_device_key", "key_id": "reefer-8842", "algorithm": "ed25519", "signed_payload_hash": digest, }
Send it on the events API or a webhook exactly as before, with the incoming_proof object alongside the payload. The response confirms your source signature was verified:
# 3. Source-signed over the normal path, no query parameter. { "incoming_proof_status": "edge_signed_verified", "proof_status": "platform_secondary_signature" }
To bind related events into one cross-vendor incident, set the same incident_label on each event. Place it inside payload, or at the top level of the request next to adapter; either works. Then fetch the fused, time-ordered readout at GET /v1/incidents/<label>.
No engineering on the device itself? Drop a small signer in front of your existing feed instead: it canonicalizes, signs, and forwards, so your events become source-signed with no change to the device. Records from several vendors, each source-signed with its own key, can be bound into one cross-vendor incident: see the multi-source Incident ProofPack demo.
Three rungs, fastest first, and all three verify identically. Point your feed at /v1/ingest/raw for sealed records with no code. Run the signing proxy for source-signed records with no change to your devices. Or sign in your own service with the SDK.
Byte-compatible Python and Node reference implementations. Generate a key, enroll it once, sign each event in-process. Includes a runnable TTN example. Apache-2.0, open source.
A small service that sits in front of your existing feed: it canonicalizes, signs with your key, and forwards, so events become source-signed with no change to the device. A helper, not required for cloud-signed raw ingest.
The adapter field selects how your raw payload is normalized into the common event shape. More adapters are added per partner need.
Every record can be re-checked independently: recompute the hashes from the data, then check the signature against the published key. The endpoint below does it server-side; a ProofPack does it in a portable file with instructions and an FRE 902(13)/(14) certification template for a qualified person to sign. Verification is always free; the ProofPack export is the paid, court-ready artifact.
# Re-check a record. Any change to a sealed value returns tamper_detected. curl -X POST https://api.privinet.net/v1/events/{id}/verify \ -H "Authorization: Bearer pk_live_your_key" { "verification_status": "cloud_signed", "detail": "hashes recomputed, signature valid" }
Sixty days, up to twenty-five devices, free. We issue your key and webhook secret, and you are sending real signed events the same afternoon.
Start your free pilot