Architektur

Architektur-Diagramme

Visualisierung der wichtigsten Flows: System-Topologie, License-Lifecycle, Heartbeat-Zyklus, Webhook-Zustellung und Schlüssel-Rotation.

System-Topologie

Wer spricht mit wem: Vendor-API, Customer-Verifikation, JWKS-Endpunkt, Webhook-Auslieferung, Persistenz.

graph TD V["Vendor App<br/>Bearer xv_live_*"] -->|"POST /api/v1/vendor/licenses"| API["xcess.ch API<br/>Next.js + Prisma"] API -->|"RS256, ES256 or EdDSA sign"| KP["Signing Key Provider<br/>Local or future HSM"] API -->|"Persist"| DB[("PostgreSQL<br/>eu-central-2")] API -->|"Audit and Heartbeat"| DB V -.->|"Hand off license JWT"| C["Customer App<br/>any language"] C -->|"GET JWKS, cached 1h"| JWKS["JWKS Endpoint"] JWKS --> API C -->|"Offline verify, jose or xcess SDK"| LV["Local JWT verification"] API -->|"Webhook events, HMAC-SHA256"| W["Vendor Webhook<br/>Endpoint"] API -->|"Heartbeat"| HB["POST /api/heartbeat"] classDef brand fill:#1e1b4b,stroke:#1e1b4b,color:#fff; classDef accent fill:#06b6d4,stroke:#06b6d4,color:#fff; classDef store fill:#f1f5f9,stroke:#94a3b8; class API,KP brand; class JWKS,W accent; class DB store;
High-level Architektur — Vendor signiert serverseitig, Customer verifiziert offline.

License-Lifecycle

Vom POST des Vendors bis zur Offline-Verifikation beim Customer.

sequenceDiagram autonumber actor V as Vendor participant X as xcess.ch API participant DB as Postgres actor C as Customer V->>X: POST /api/v1/vendor/licenses with Bearer xv_live_* X->>X: Validate input + per-tier quota X->>DB: Insert License + Customer X->>X: Sign JWT with RS256, ES256 or EdDSA X-->>V: 201 returns licenseJwt + licenseNumber V-->>C: Distribute JWT via file or API C->>X: GET /.well-known/jwks/code, cached 1h X-->>C: returns keys array C->>C: Verify JWT offline using xcess SDK or jose C->>X: POST /api/heartbeat optional X->>DB: record Heartbeat X-->>C: returns license status
License-Lifecycle — Issue, Distribute, Verify.

Heartbeat-Zyklus

Optionale Online-Bestätigung mit automatischem Backoff bei Rate-Limits.

sequenceDiagram autonumber participant SDK as xcess SDK participant App as Customer App participant X as xcess.ch API App->>SDK: scheduleHeartbeats with interval loop every interval, default 5 min SDK->>X: POST /api/heartbeat with licenseId and fingerprint alt success X-->>SDK: 200 ok with license status else rate-limited X-->>SDK: 429 with Retry-After SDK->>SDK: backoff + retry else license revoked or expired X-->>SDK: 200 with status REVOKED SDK-->>App: emit license.revoked end end
Heartbeat-Loop — robust gegenüber Netzunterbrüchen.

Webhook-Zustellung

HMAC-signierte Events mit exponentiellem Retry und Auto-Disable bei Dauerfehlern.

sequenceDiagram autonumber participant X as xcess.ch API participant Q as Delivery Queue participant W as Vendor Webhook X->>Q: enqueue WebhookDelivery, eg LICENSE_CREATED Q->>W: POST with X-Xcess-Signature header t,v1 hmac-sha256 alt 2xx response W-->>Q: 200 OK Q-->>X: status SUCCEEDED else 4xx, 5xx or timeout W-->>Q: error Q->>Q: schedule retry 30s, 5m, 30m, 2h, 12h loop up to 6 attempts Q->>W: retry POST end Q-->>X: status FAILED, auto-disable after 10 consecutive fails end
Webhook-Zustellung mit Retry-Plan: 30s, 5m, 30m, 2h, 12h.

Schlüssel-Rotation

90 Tage Karenzzeit: Alter und neuer Schlüssel sind im JWKS, bis Customer-Caches abgelaufen sind.

sequenceDiagram autonumber actor R as Reseller Admin participant X as xcess.ch API participant DB as Postgres actor C as Customer SDK Note over R,X: New key joins JWKS immediately, old key stays active for grace period R->>X: POST /api/reseller/signing-keys with algorithm X->>DB: Insert new SigningKey isActive=true X->>DB: Mark old SigningKey isActive=false, retiresAt = now + 90 days X-->>R: 201 returns kid and publicKeyPem Note over X,C: For 90 days, JWKS returns BOTH keys C->>X: GET /.well-known/jwks/code X-->>C: returns keys new and old retired C->>C: Verify by kid, matches either key Note over X: After retiresAt, old key drops out of JWKS
Schlüssel-Rotation — keine Downtime, weil JWKS beide Schlüssel enthält.