Skip to main content
After registration, every outbound request to the Synheart cloud must be signed by the device’s hardware key. This is the SDK-side reference for RFC-AUTH-MOBILE-0001 §8.

Public API

Dart, Kotlin, and Swift expose the same shape. The SDK generates the timestamp and nonce internally.

Message construction (normative)

The signed message MUST be:
The bytes signed:
For Synheart Core’s cloud ingest, the runtime strips the /ingest/ prefix from POSTs to /ingest/v1/... so signatures cover /v1/.... If you build clients that talk to those endpoints from outside Core, mirror this prefix-stripping rule.
The signature is encoded as Base64(ASN.1 DER). The runtime native bridge accepts compact r||s (64 bytes) from its sign_bytes callback and the SDK wraps that in DER for the wire header.

Six required headers

The Dart SignedHeaders.toMap() returns exactly these six keys.

Nonce policy

  • UUID v4. Generated fresh per request; never reused across requests for the same (app_id, device_id).
  • Server enforces replay protection on write endpoints (POST, PUT, PATCH, DELETE) within the 300-second freshness window.
  • Replay protection is RECOMMENDED for read endpoints.
  • Server stores nonces with a 300-second TTL keyed by (device_id, nonce).

Timestamp policy

  • Server-side freshness window: 300 seconds.
  • SDK clock may drift; correctClockSkew(serverTimestamp) learns an offset from CLOCK_SKEW errors and applies it on subsequent signs.
  • The SDK persists clock_offset_ms in local storage.

Server-side verification (RFC §8.4)

Backends that verify these signatures follow:
  1. Extract the six headers; reject if any required header is missing.
  2. Reject if abs(server_time - X-Synheart-Timestamp) > 300.
  3. For write endpoints, reject if (device_id, nonce) has been seen in the freshness window.
  4. Reconstruct the message: method + "\n" + path + "\n" + timestamp + "\n" + body_bytes.
  5. Look up the public key by (app_id, device_id).
  6. Verify ECDSA P-256 signature over SHA256(message).
  7. Store the nonce with a 300-second TTL (write endpoints).

SignedHeaders type

Kotlin and Swift mirror this.

Sig-Version

X-Synheart-Sig-Version is "1". The header is versioned so the signing scheme can evolve without breaking deployed clients.

Wiring through Synheart Core

When synheart-core consumes synheart-auth:
  1. Core constructs a DeviceAuthProvider over SynheartAuth.
  2. Core registers the auth provider’s crypto callbacks with the Synheart runtime.
  3. The runtime’s request-signing path builds X-Synheart-Proof (a compact JWS bound to the request URL+method) for HSI ingest. This is a different header from X-Synheart-Signature — both are hardware-backed by the same ECDSA P-256 key but cover different surfaces.
  4. For non-runtime traffic, hosts call SynheartAuth.signRequest(...) directly and attach the six headers themselves.

What is not signed

  • Query string components (signing is over path only). Servers that include sensitive parameters in query strings are responsible for separate validation.
  • Headers other than the six listed above. The body and the path/method are the only signed surface.

Logging policy

  • Registration — how the public key reaches the server.
  • ErrorsCLOCK_SKEW, NONCE_REPLAY, KEY_INVALIDATED recovery.
  • Cloud ProtocolX-Synheart-Proof for HSI ingest.