> ## Documentation Index
> Fetch the complete documentation index at: https://docs.synheart.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Request Signing

> Message construction, the six signed headers, and server-side verification per RFC-AUTH-MOBILE-0001 §8

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

```text theme={null}
signRequest(
  appId: String,
  method: String,
  path: String,
  bodyBytes: ByteArray,
) -> SignedHeaders
```

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

## Message construction (normative)

The signed message MUST be:

```text theme={null}
method          = uppercase HTTP method, ASCII   (e.g. "POST")
path            = request path, ASCII             (e.g. "/ingest/v1/hsi")
                  no query string
timestamp_str   = Unix seconds, ASCII decimal     (e.g. "1709312345")
raw_body_bytes  = exact HTTP request body bytes on the wire
                  (empty bytes for GET / no body)
```

The bytes signed:

```text theme={null}
message = method || "\n" || path || "\n" || timestamp_str || "\n" || raw_body_bytes
signature = ECDSA-P256-SIGN(private_key, SHA256(message))
```

<Note>
  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.
</Note>

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

| Header                   | Value               | Notes                                                |
| ------------------------ | ------------------- | ---------------------------------------------------- |
| `X-App-ID`               | string              | The `app_id` provisioned in the dashboard.           |
| `X-Device-ID`            | UUID                | Issued at registration.                              |
| `X-Synheart-Signature`   | `Base64(ASN.1 DER)` | ECDSA-P256 over `SHA256(message)`.                   |
| `X-Synheart-Timestamp`   | Unix seconds        | Decimal ASCII, e.g. `"1709312345"`.                  |
| `X-Synheart-Nonce`       | UUID v4             | Unique per request, never reused.                    |
| `X-Synheart-Sig-Version` | `"1"`               | Signature scheme version. Future evolution may bump. |

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

```dart theme={null}
class SignedHeaders {
  final String appId;
  final String deviceId;
  final String signature;
  final String timestamp;
  final String nonce;
  final String signatureVersion;  // default "1"

  Map<String, String> toMap();
}
```

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.

| Header                            | Used by                                                                                    |
| --------------------------------- | ------------------------------------------------------------------------------------------ |
| `X-Synheart-Signature` (this SDK) | Direct API calls from your app to Synheart endpoints.                                      |
| `X-Synheart-Proof` (runtime)      | HSI ingest from the Synheart runtime. See [Cloud Protocol](/synheart-core/cloud-protocol). |

## 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

| Allowed (prod)                 | Never (prod)                                        |
| ------------------------------ | --------------------------------------------------- |
| `app_id`                       | Raw signatures                                      |
| `device_id` (truncated/hashed) | Raw request bodies                                  |
| Error codes                    | Attestation proof payloads                          |
| Latency metrics                | Public key material (debug builds only with opt-in) |
| Rotation events                | Clock offset values                                 |
| State transitions              |                                                     |

## Related

* [Registration](/synheart-auth/registration) — how the public key reaches the server.
* [Errors](/synheart-auth/errors) — `CLOCK_SKEW`, `NONCE_REPLAY`, `KEY_INVALIDATED` recovery.
* [Cloud Protocol](/synheart-core/cloud-protocol) — `X-Synheart-Proof` for HSI ingest.
