# Authentication, signing, credential storage, and idempotency

## Signature headers

`x-api-signature` must be `v1=<64 lowercase-or-uppercase hex characters>`.

Canonical input:

```text
METHOD\n
PATH_WITH_SORTED_QUERY\n
TIMESTAMP\n
NONCE\n
SHA256(EXACT_RAW_BODY)
```

The server decrypts the API secret in memory, computes HMAC-SHA256, and compares it with `hash_equals`. Timestamp tolerance and replay TTL default to 300 seconds. Successful signatures reserve both nonce and signature fingerprints in Redis/cache.

## Storage

`public_key_hash` is SHA-256 and is used for lookup. `secret_ciphertext` and webhook secrets use Laravel encrypted casts in this starter. In production, replace the application encrypter with a KMS/envelope-encryption implementation and retain `secret_kid` for key version tracking and re-encryption.

## Idempotency

`POST /payments` and `POST /payouts` require `Idempotency-Key`. The `idempotency_records` table scopes keys by merchant, environment, and operation. It stores a hash bound to method, path, sorted query, and exact raw body plus the final response status and JSON body.

- Same key + same request: returns the original response and `Idempotency-Replayed: true`.
- Same key + different request: returns HTTP 409 `IDEMPOTENCY_CONFLICT`.
- Concurrent unfinished duplicate: returns HTTP 409 `IDEMPOTENCY_IN_PROGRESS`.
