Skip to main content
The Cloud Connector enables secure, consent-gated upload of HSV snapshots (converted to HSI 1.0 format) to the Synheart Platform.

Core Principles

  1. Consent-Gated: Uploads only occur with explicit user consent
  2. Derived Data Only: Only state representations (HSI 1.0 format), never raw signals
  3. Secure Transport: HTTPS + HMAC-SHA256 authentication with time-windowed nonces
  4. Privacy-Preserving: No raw content, direct identifiers, or raw biosignals (only pseudonymous IDs for routing)
  5. Tenant-Isolated: Data routed to correct tenant namespace
  6. Offline-Resilient: Persistent queue with FIFO eviction (max 100 snapshots)
  7. Rate-Limited: Client-side rate limiting per window type

Architecture


Upload Flow

1. Prerequisites

Before any upload:
  1. User grants cloudUpload consent
  2. SDK obtains tenant credentials from Synheart Platform
  3. SDK generates or retrieves HMAC secret key

2. HSV → HSI 1.0 Conversion

The SDK converts HSV (internal representation) to HSI 1.0 (external format) for upload: Upload Request:
{
  "subject": {
    "subject_type": "pseudonymous_user",
    "subject_id": "user_123"
  },
  "snapshots": [
    {
      "hsi_version": "1.0",
      "observed_at_utc": "2025-12-28T10:30:00Z",
      "computed_at_utc": "2025-12-28T10:30:10Z",
      "producer": {
        "name": "Synheart Core SDK",
        "version": "1.0.0",
        "instance_id": "device_abc_123"
      },
      "window_ids": ["micro"],
      "windows": {
        "micro": {
          "start_utc": "2025-12-28T10:29:30Z",
          "end_utc": "2025-12-28T10:30:00Z",
          "duration_seconds": 30
        }
      },
      "axes": {
        "affect": {
          "readings": {
            "arousal_index": {
              "value": 0.72,
              "confidence": 0.85,
              "window_id": "micro"
            },
            "valence_stability": {
              "value": 0.65,
              "confidence": 0.82,
              "window_id": "micro"
            }
          }
        },
        "engagement": {
          "readings": {
            "engagement_stability": {
              "value": 0.81,
              "confidence": 0.88,
              "window_id": "micro"
            },
            "interaction_cadence": {
              "value": 0.55,
              "confidence": 0.79,
              "window_id": "micro"
            }
          }
        },
        "behavior": {
          "readings": {
            "motion_index": {
              "value": 0.45,
              "confidence": 0.90,
              "window_id": "micro"
            },
            "posture_stability": {
              "value": 0.78,
              "confidence": 0.85,
              "window_id": "micro"
            },
            "screen_active_ratio": {
              "value": 0.90,
              "confidence": 0.95,
              "window_id": "micro"
            },
            "session_fragmentation": {
              "value": 0.22,
              "confidence": 0.88,
              "window_id": "micro"
            }
          }
        }
      },
      "embeddings": [
        {
          "embedding_id": "state_embed_1",
          "dimension": 64,
          "model": "synheart_fusion_v1",
          "window_id": "micro",
          "vector": [0.12, 0.34, -0.56, ..., 0.78]
        }
      ],
      "privacy": {
        "contains_pii": false
      }
    }
  ]
}

3. HMAC Signing

The SDK signs the request with HMAC-SHA256:
String computeSignature({
  required String method,
  required String path,
  required String tenantId,
  required int timestamp,
  required String nonce,
  required String bodyJson,
}) {
  // 1. Compute SHA256 of body
  final bodyHash = sha256.convert(utf8.encode(bodyJson)).toString();

  // 2. Construct signing string (newline-separated)
  final signingString = [
    method.toUpperCase(),  // "POST"
    path,                  // "/v1/ingest/hsi"
    tenantId,              // "tenant_abc_123"
    timestamp.toString(),  // Unix timestamp
    nonce,                 // Time-windowed nonce
    bodyHash,              // SHA256 of JSON body
  ].join('\n');

  // 3. Compute HMAC-SHA256
  final hmac = Hmac(sha256, utf8.encode(hmacSecret));
  final digest = hmac.convert(utf8.encode(signingString));

  return digest.toString(); // Hex-encoded signature
}
Nonce Generation (time-windowed to prevent replay attacks):
String generateNonce() {
  final timestamp = DateTime.now().millisecondsSinceEpoch ~/ 1000;
  final random = Random.secure();
  final randomHex = List.generate(12, (_) => random.nextInt(256))
      .map((b) => b.toRadixString(16).padLeft(2, '0'))
      .join();
  return '${timestamp}_$randomHex'; // Format: <unix_timestamp>_<random_hex>
}
Request Headers:
POST /v1/ingest/hsi HTTP/1.1
Host: api.synheart.com
Content-Type: application/json
X-Synheart-Tenant: tenant_abc_123
X-Synheart-Signature: <hmac_sha256_hex>
X-Synheart-Nonce: 1704067200_a1b2c3d4e5f6
X-Synheart-Timestamp: 1704067200
X-Synheart-SDK-Version: 1.0.0
Signature Components:
  • Payload: JSON-encoded HSI snapshot
  • Secret: Tenant-specific HMAC key (obtained during SDK initialization)
  • Nonce: Time-windowed random nonce (prevents replay attacks)

4. HTTP Request

Endpoint:
POST https://api.synheart.ai/v1/ingest/hsi
Headers:
Content-Type: application/json
X-Synheart-Tenant: app_xyz_prod
X-Synheart-Signature: <HMAC signature>
X-Synheart-Nonce: <time-windowed nonce>
X-Synheart-Timestamp: <Unix timestamp>
X-Synheart-SDK-Version: 1.0.0
Body:
{
  "userId": "anon_user_123",
  "snapshot": { ... }
}

5. Server Validation

The Synheart Platform validates:
  1. HMAC signature: Recomputes HMAC and compares
  2. Nonce freshness: Checks nonce is within 5-minute window
  3. Tenant authorization: Verifies tenant has ingestion capability
  4. Schema validation: Ensures HSI snapshot matches expected format

6. Response

Success (200 OK):
{
  "status": "accepted",
  "snapshotId": "hsi_snapshot_abc123",
  "timestamp": 1704067205
}
Error (400/401/403):
{
  "status": "error",
  "code": "invalid_signature",
  "message": "HMAC signature validation failed"
}

Security

HMAC Authentication

Why HMAC?
  • Prevents tampering with requests
  • Prevents replay attacks (via nonce)
  • Lightweight (no public key infrastructure)
  • Tenant-scoped (each tenant has unique secret)
HMAC Secret Management:
  • Secrets issued by Synheart Platform during app registration
  • Secrets stored securely on-device (Keychain on iOS, KeyStore on Android)
  • Secrets rotated periodically (90-day cycle)

Nonce System

Nonce Requirements:
  • Time-windowed: valid for 5 minutes
  • Unique: generated via secure random
  • Checked server-side: prevents replay attacks
Nonce Format:
<timestamp>_<random_hex>
Example: 1704067200_a3f8c9d2e1b4
Server-Side Validation:
def validate_nonce(nonce, signature_timestamp):
    parts = nonce.split('_')
    nonce_timestamp = int(parts[0])
    current_time = time.time()

    # Check freshness (within 5 minutes)
    if abs(current_time - nonce_timestamp) > 300:
        raise InvalidNonceError("Nonce expired")

    # Check nonce hasn't been used before
    if redis.exists(f"nonce:{nonce}"):
        raise InvalidNonceError("Nonce already used")

    # Mark nonce as used (with 10-minute TTL)
    redis.setex(f"nonce:{nonce}", 600, "1")

Transport Security

  • TLS 1.3: All requests use HTTPS
  • Certificate Pinning: SDK pins Synheart Platform certificates
  • No Sensitive Data: HSI snapshots contain no PII or raw signals

Rate Limiting

Client-Side Rate Limiting

SDK implements local rate limiting:
Window TypeMax Upload Frequency
Micro (30s)Every 30 seconds
Short (5m)Every 2 minutes
Medium (1h)Every 10 minutes
Long (24h)Every 1 hour
Batching:
  • SDK may batch multiple snapshots into a single request
  • Max batch size (by capability tier): 10 (Core), 50 (Extended), 200 (Research)
  • Max request size: 1 MB

Server-Side Rate Limiting

Synheart Platform enforces rate limits per tenant:
TierRequests/MinuteRequests/Hour
Free10200
Developer602,000
Production60020,000
EnterpriseCustomCustom
Rate Limit Response (429):
{
  "status": "error",
  "code": "rate_limit_exceeded",
  "message": "Too many requests",
  "retryAfter": 60
}

Data Retention

Cloud Storage

HSI snapshots uploaded to the cloud are retained:
  • Default: 90 days
  • Extended: 1 year (with user consent)
  • Research: 5 years (research-tier apps only)

User Deletion

Users can delete cloud data:
// Request deletion of all cloud data
await Synheart.deleteCloudData();
Deletion Process:
  1. SDK sends deletion request to Synheart Platform
  2. Platform marks data for deletion
  3. Data purged within 7 days
  4. Confirmation sent to user

Tenant Routing

Tenant Identification

Each app is assigned a unique tenantId:
Format: <app_identifier>_<environment>
Examples:
  - syni_life_prod
  - external_app_xyz_dev
  - research_lab_staging

Routing Logic

Synheart Platform routes HSI snapshots to tenant-specific storage:
api.synheart.ai/v1/ingest/hsi

[Validate HMAC & Nonce]

[Extract tenantId from header]

[Route to tenant namespace]

s3://synheart-hsi-prod/<tenantId>/<userId>/<timestamp>.json
Tenant Isolation:
  • Each tenant has isolated storage namespace
  • Cross-tenant access is forbidden
  • Data queries are tenant-scoped

Capability-Based Upload

Upload endpoints respect capability levels:
CapabilityEndpointAccess
Core/v1/ingest/hsiBasic HSI axes
Extended/v1/ingest/hsiFull 64D embeddings
Research/v1/ingest/hsi-researchFull fusion vectors
Extended Payloads (Extended/Research):
{
  "userId": "anon_user_123",
  "snapshot": {
    "axes": { ... },
    "embedding": {
      "vector": [...],  // Full 64D (Extended)
      "fusionVectors": {...}  // Internal fusion state (Research only)
    }
  }
}

Error Handling

Client-Side Retry Logic

SDK implements exponential backoff for failed uploads:
Future<void> uploadWithRetry(HSISnapshot snapshot) async {
  int attempts = 0;
  int maxAttempts = 3;
  int baseDelay = 1000;  // 1 second

  while (attempts < maxAttempts) {
    try {
      await _upload(snapshot);
      return;  // Success
    } catch (e) {
      attempts++;
      if (attempts >= maxAttempts) {
        // Store locally for later retry
        await _storeForRetry(snapshot);
        throw e;
      }

      // Exponential backoff: 1s, 2s, 4s
      int delay = baseDelay * pow(2, attempts - 1);
      await Future.delayed(Duration(milliseconds: delay));
    }
  }
}

Error Codes

CodeDescriptionClient Action
invalid_signatureHMAC validation failedRegenerate signature
invalid_nonceNonce expired or reusedGenerate new nonce
rate_limit_exceededToo many requestsBackoff and retry
invalid_tenantTenant not foundCheck tenant credentials
schema_validation_failedInvalid HSI formatCheck SDK version

Offline Support

Local Queueing

When network is unavailable, SDK queues HSI snapshots locally:
class UploadQueue {
  List<HSISnapshot> _queue = [];

  Future<void> enqueue(HSISnapshot snapshot) async {
    _queue.add(snapshot);
    await _persistQueue();

    // Limit queue size (max 100 snapshots)
    if (_queue.length > 100) {
      _queue.removeAt(0);  // FIFO
    }
  }

  Future<void> flush() async {
    while (_queue.isNotEmpty && _isOnline()) {
      final snapshot = _queue.first;
      try {
        await _upload(snapshot);
        _queue.removeAt(0);
        await _persistQueue();
      } catch (e) {
        break;  // Stop on failure
      }
    }
  }
}

Network State Monitoring

SDK monitors network state and automatically flushes queue when online:
NetworkConnectivity.onConnectivityChanged.listen((status) {
  if (status == ConnectivityStatus.connected) {
    _uploadQueue.flush();
  }
});

Performance Targets

MetricTargetMeasurement
Upload latency≤ 80ms95th percentile
Request size< 10 KBPer snapshot
Batch size≤ 1 MBPer request
Queue size≤ 100 snapshotsLocal storage
Retry attempts≤ 3Per snapshot

API Reference

Enable Cloud Upload

// Request cloudUpload consent and enable uploads
await Synheart.enableCloud();

// Check upload status
bool isEnabled = await Synheart.isCloudEnabled();

// Disable cloud uploads
await Synheart.disableCloud();

Manual Upload

// Force upload of current HSI snapshot
await Synheart.uploadNow();

// Flush upload queue
await Synheart.flushUploadQueue();

Upload Callbacks

// Listen for upload events
Synheart.onUploadSuccess.listen((event) {
  print('Uploaded snapshot: ${event.snapshotId}');
});

Synheart.onUploadError.listen((error) {
  print('Upload failed: ${error.message}');
});

Testing

Mock Server

For testing, use the Synheart Platform sandbox:
Endpoint: https://sandbox.synheart.ai/v1/ingest/hsi
Tenant: test_tenant_sandbox
Secret: test_hmac_secret_xyz

Unit Tests

test('HMAC signature is valid', () {
  final payload = '{"userId":"test","snapshot":{}}';
  final secret = 'test_secret';
  final nonce = '1704067200_abc123';

  final signature = computeHMAC(payload, secret, nonce);
  expect(signature, isNotEmpty);
});

test('Upload fails without consent', () async {
  await Synheart.revokeConsent('cloudUpload');

  expect(
    () => Synheart.uploadNow(),
    throwsA(isA<ConsentRequiredError>())
  );
});


Last Updated: 2025-12-25 Version: 1.0.0 Author: Israel Goytom