Security & Trust Model

No marketing language here. Just the mechanics.

Authentication: HMAC-SHA256

Every API request must be signed. We don't use bearer tokens or session cookies.

Required Headers

Header Description
X-Api-Key Your integrator API key
X-Timestamp Unix timestamp (seconds)
X-Nonce Unique string per request
X-Signature HMAC-SHA256 signature

Signature Calculation

javascript
// 1. Build the signature base string
signatureBase = method + "\n" +
                path + "\n" +
                timestamp + "\n" +
                nonce + "\n" +
                bodyHash

// 2. Hash the request body (SHA-256, hex-encoded)
bodyHash = sha256(requestBody || "")

// 3. Compute HMAC-SHA256
signature = hmac_sha256(signatureBase, apiSecret)

// Example
GET /v1/escrows/esc_123
X-Timestamp: 1735430400
X-Nonce: unique_abc123

signatureBase = "GET\n/v1/escrows/esc_123\n1735430400\nunique_abc123\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"

Implementation Example (TypeScript)

typescript
import { createHmac, createHash } from 'crypto';

function signRequest(
  method: string,
  path: string,
  body: string | null,
  apiSecret: string
): { timestamp: string; nonce: string; signature: string } {
  const timestamp = Math.floor(Date.now() / 1000).toString();
  const nonce = crypto.randomUUID();

  const bodyHash = createHash('sha256')
    .update(body || '')
    .digest('hex');

  const signatureBase = [method, path, timestamp, nonce, bodyHash].join('\n');

  const signature = createHmac('sha256', apiSecret)
    .update(signatureBase)
    .digest('hex');

  return { timestamp, nonce, signature };
}

Replay Protection

Timestamp Validation

Requests with timestamps older than 300 seconds (5 minutes) are rejected.

javascript
// Server-side check
if (Math.abs(serverTime - requestTimestamp) > 300) {
  return 401; // TIMESTAMP_EXPIRED
}

Nonce Tracking

Each X-Nonce value can only be used once within the timestamp window. Reusing a nonce returns 401 NONCE_REUSED.

Idempotency

All POST requests require an Idempotency-Key header.

Scenario Result
New key + new request Processed normally
Same key + same payload Returns cached response
Same key + different payload 409 IDEMPOTENCY_MISMATCH

Key Requirements

  • Format: UUID v4 recommended
  • Retention: 24 hours
  • Scope: Per integrator
http
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000

Webhook Security

Outbound webhooks are HMAC-signed using the same scheme.

Verifying Webhook Signatures

javascript
// Incoming webhook
POST /your-webhook-endpoint
X-Clearhold-Timestamp: 1735430400
X-Clearhold-Signature: abc123...

// Verify
signatureBase = "POST\n/your-webhook-endpoint\n" + timestamp + "\n" + bodyHash
expectedSignature = hmac_sha256(signatureBase, webhookSecret)

if (!timingSafeEqual(expectedSignature, receivedSignature)) {
  return 401; // Signature mismatch
}

Retry Policy

Attempt Delay
10s (immediate)
21s
32s
44s
58s
616s (final)

After 6 failed attempts, webhooks are dead-lettered. No automatic recovery. You'll need to manually replay from audit log.

Rate Limits

Tier Limit
Standard 100 requests/minute
Pro 1,000 requests/minute
Enterprise Custom

Rate limit headers included in every response:

http
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 1735430460

Security Guarantees

  • ✓ All data encrypted in transit (TLS 1.3)
  • ✓ All data encrypted at rest (AES-256)
  • ✓ Timing-safe signature comparison
  • ✓ No sensitive data in logs
  • ✓ Audit trail for all state transitions