Docs

SDK-Dokumentation

In 5 Minuten von der Registrierung zur ersten signierten Lizenz.

Was ist xcess?

xcess ist eine License-as-a-Service-Plattform für Software-Vendors. Du integrierst unser SDK in deine Anwendung, dein Backend ruft unsere REST-API auf, um Lizenzen zu erzeugen, und unsere Plattform übernimmt Crypto, Heartbeat-Verifikation und Audit-Trail.

Architektur-Diagramme

Mermaid-Diagramme zu System, License-Lifecycle, Heartbeat, Webhook-Zustellung und Schlüssel-Rotation.

Diagramme öffnen

Vendor-Migration

Schritt-für-Schritt-Guide für die Migration von eigener JWT-Signierung auf xcess.ch.

Migration starten

Quickstart

  1. Vendor-Konto registrieren (kostenlose 14-Tage-Trial).
  2. API-Token aus dem Sign-Up-Dialog sicher speichern (wird nur einmal angezeigt).
  3. Erste Lizenz via REST-API oder SDK erstellen (siehe unten).
  4. JWT in deiner Anwendung mit dem veröffentlichten Public Key verifizieren – offline, ohne Netzwerk-Roundtrip pro Start.

REST-API Reference

Lizenz erstellen

Endpoint

POST https://xcess.ch/api/v1/vendor/licenses

Headers

Authorization: Bearer xv_live_<your-token>
Content-Type: application/json

Request Body

{
  "productCode": "acme-pro",
  "productName": "Acme Professional Edition",
  "customerCode": "customer-001",
  "customerName": "Acme Customer GmbH",
  "customerEmail": "[email protected]",
  "validUntil": "2027-04-25T00:00:00Z",
  "seats": 25,
  "metadata": { "plan": "premium" }
}

Response (201)

{
  "licenseNumber": "L-ACME-000123",
  "jwt": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Imt2LWFjbWUtMjAyNiJ9...",
  "customer": { "code": "customer-001", "name": "Acme Customer GmbH" },
  "validUntil": "2027-04-25T00:00:00Z"
}

Public Key (PEM) herunterladen

Liefert nur den aktuell aktiven Schlüssel als PEM. Geeignet für einfache Verifikation ohne Schlüssel-Rotation.

GET https://xcess.ch/api/v1/vendor/<your-vendor-code>/public-key

Alle gültigen Schlüssel (JWKS-ähnlich)

Liefert ALLE aktuell akzeptierten Schlüssel inkl. zurückgezogener (innerhalb der Übergangsphase). Empfohlen für produktive Integrationen mit Schlüssel-Rotation.

GET https://xcess.ch/api/v1/vendor/<your-vendor-code>/keys

Response (201)

{
  "vendor": { "code": "acme", "name": "Acme GmbH" },
  "keys": [
    {
      "kid": "kv-acme-2026-04",
      "algorithm": "RS256",
      "isActive": true,
      "retiresAt": null,
      "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkq...",
      "createdAt": "2026-04-01T08:00:00Z"
    }
  ],
  "count": 1
}

SDK für JavaScript / TypeScript

Das offizielle JavaScript/TypeScript SDK kapselt JWT-Verifikation, JWKS-Caching und Heartbeat-Übermittlung. SDKs für Python, C++ und Java folgen.

Installation

# npm
npm install @xcess/sdk

# yarn
yarn add @xcess/sdk

# pnpm
pnpm add @xcess/sdk

Lizenz verifizieren

Holt automatisch den passenden Public Key über die JWKS-URL, prüft Signatur, Issuer, Ablaufdatum und gibt das geparste Payload zurück.

import { verifyLicense } from '@xcess/sdk';

const result = await verifyLicense({
  jwt: process.env.LICENSE_JWT!,
  vendorCode: 'acme',
});

if (!result.valid) {
  throw new Error(`License invalid: ${result.reason}`);
}

console.log('License OK', result.payload);

Heartbeat senden

Aktive Nutzung an xcess melden – sichtbar im Reseller-Dashboard und im Audit-Log. Optional, aber empfohlen für Compliance & Anti-Tampering.

import { sendHeartbeat } from '@xcess/sdk';

await sendHeartbeat({
  jwt: process.env.LICENSE_JWT!,
  metadata: {
    appVersion: '1.4.0',
    activeUsers: 12,
    hostname: 'app-prod-01',
  },
});

Ohne SDK: jose direkt verwenden

Falls du kein SDK einbinden möchtest, kannst du jose oder eine andere JWT-Library direkt nutzen.

import { jwtVerify, importSPKI } from 'jose';

const pubKeyPem = await fetch(
  'https://xcess.ch/api/v1/vendor/acme/public-key',
)
  .then((r) => r.json())
  .then((j) => j.publicKeyPem);

const pubKey = await importSPKI(pubKeyPem, 'RS256');
const { payload } = await jwtVerify(jwt, pubKey, {
  issuer: 'xcess.ch',
  algorithms: ['RS256'],
});

Hardware-bindung

Hardware-Bindung verknüpft Lizenzen mit konkreten Geräten. Bei jedem Heartbeat sendet das SDK einen Fingerabdruck der Hardware. xcess registriert das Gerät in der Lizenz und kann anhand der konfigurierten Modus-Regeln neue Aktivierungen ablehnen.

Bindungsmodi

  • NONE — Hardware-Daten werden ignoriert. Lizenzen sind beliebig oft aktivierbar.
  • SOFT — Geräte werden registriert und verfolgt, aber keine Limits durchgesetzt.
  • STRICT — Limit (maxDevices) wird hart durchgesetzt. Heartbeats werden mit HTTP 409 abgelehnt, sobald das Limit erreicht ist.

Fingerabdruck berechnen

Der Fingerabdruck ist ein deterministischer SHA-256-Hash über stabile Hardware-Merkmale (z.B. Hostname + MAC + CPU). Das SDK liefert eine Helper-Funktion mit, alternativ kannst du einen eigenen Hash berechnen.

import { computeDeviceFingerprint } from '@xcess/sdk';

const fingerprint = await computeDeviceFingerprint([
  process.env.HOSTNAME ?? 'unknown',
  process.env.MACHINE_ID ?? '',
]);

console.log('Device fingerprint:', fingerprint);

SDK-Beispiel mit Fingerabdruck

Das untenstehende Beispiel berechnet den Fingerabdruck über die Hostname-Identität und sendet ihn beim Heartbeat mit. Bei STRICT-Lizenzen wird der erste Aufruf das Gerät registrieren, weitere Aufrufe halten es lebendig.

import { sendHeartbeat, computeDeviceFingerprint } from '@xcess/sdk';

const fingerprint = await computeDeviceFingerprint([
  process.env.HOSTNAME ?? 'unknown',
]);

await sendHeartbeat({
  jwt: process.env.LICENSE_JWT!,
  fingerprint,
  deviceLabel: 'production-app-01',
  deviceMetadata: {
    appVersion: '1.4.0',
    region: 'eu-central',
  },
});

Endpoint POST https://xcess.ch/api/heartbeat

Authorization: Bearer <license-jwt>
Content-Type: application/json

{
  "fingerprint": "abc123...",
  "device_label": "production-app-01",
  "device_metadata": { "appVersion": "1.4.0" }
}

Floating / Concurrent Lizenzen

Floating-Lizenzen erlauben N gleichzeitige Sitzungen pro Lizenz. Beim Start fordert das SDK eine Sitzung an (acquire) und erhält ein Sitzungs-Token. Es muss in regelmässigen Abständen erneuert (refresh) und beim Beenden freigegeben werden (release). Ist das Limit erreicht, lehnt der Server mit HTTP 409 ab.

Concurrency-Modi

  • NONE — keine Sitzungsverwaltung. Heartbeat-only Lizenz.
  • STRICT — maxConcurrentSessions wird hart durchgesetzt. Sessions laufen automatisch ab (sessionTimeoutSeconds) und werden serverseitig aufgeräumt.

Sitzung manuell verwalten

Die einfachste Variante: acquire/refresh/release direkt auf dem Client. Bei einem 409 muss die App den Anwender informieren oder warten.

import { Xcess } from '@xcess/sdk';

const xcess = new Xcess({});

const session = await xcess.acquireSession({
  licenseJwt: process.env.LICENSE_JWT!,
  fingerprint: 'abc123...',
  label: 'production-app-01',
});

// ... do work ...

setInterval(() => {
  xcess.refreshSession({
    licenseJwt: process.env.LICENSE_JWT!,
    sessionToken: session.sessionToken,
  });
}, (session.sessionTimeoutSeconds * 1000) / 2);

// On shutdown:
await xcess.releaseSession({
  licenseJwt: process.env.LICENSE_JWT!,
  sessionToken: session.sessionToken,
});

Automatische Erneuerung mit LicenseSessionLease

Der Helper LicenseSessionLease erneuert die Sitzung automatisch in der halben TTL und gibt sie beim stop() frei. Errors werden über onError gemeldet, ein endgültiger Verlust (404/410) über onLost.

import { Xcess, LicenseSessionLease } from '@xcess/sdk';

const xcess = new Xcess({});

const lease = new LicenseSessionLease({
  client: xcess,
  licenseJwt: process.env.LICENSE_JWT!,
  fingerprint: 'abc123...',
  label: 'production-app-01',
  onRenewed: ({ sessionId, expiresAt }) => {
    console.log('Session renewed:', sessionId, 'until', expiresAt);
  },
  onLost: (err) => {
    console.error('Session lost:', err);
    // Application should stop / disable features here.
  },
});

await lease.start(); // throws on 409 concurrency_limit_reached

// ... app runs ...

await lease.stop();

mTLS / Client-Zertifikate

Jeder Vendor erhält automatisch eine eigene selbst-signierte Root-CA. Mit dem License-JWT (Bearer) können SDKs ein X.509-Client-Zertifikat ausstellen, das ihre Lizenz an einen TLS-Handshake bindet. Der private Schlüssel wird einmalig zurückgegeben — niemals serverseitig persistiert.

Zertifikat ausstellen

POST /api/v1/license/cert/issue mit Bearer-License-JWT. Antwort enthält certificatePem, privateKeyPem und caCertificatePem.

import { Xcess } from '@xcess/sdk';

const xcess = new Xcess({});
const result = await xcess.issueClientCertificate({
  licenseJwt: process.env.LICENSE_JWT!,
  label: 'production-app-01',
});

console.log('Certificate PEM:', result.certificatePem);
console.log('Private key PEM (store securely!):', result.privateKeyPem);
console.log('Vendor CA PEM:', result.caCertificatePem);
console.log('Fingerprint:', result.fingerprintSha256);

Widerruf & CRL

Vendor-Admins können Zertifikate über die UI oder das Vendor API widerrufen. Eine vom Vendor bereitgestellte CRL ist unter /api/v1/vendor/<code>/crl verfügbar (Cache 5 min).

# Download vendor root CA
curl https://xcess.ch/api/v1/vendor/<code>/ca \
  -o vendor-ca.pem

# Download CRL (cache 5 minutes)
curl https://xcess.ch/api/v1/vendor/<code>/crl \
  -o vendor-crl.pem

# Verify a client cert
openssl verify -CAfile vendor-ca.pem -CRLfile vendor-crl.pem \
  -crl_check client.pem

Schlüssel-Rotation

Schlüssel-Rotation läuft transparent für deine Anwendung – sofern dein SDK oder Code die JWKS-URL nutzt anstatt einen einzelnen Public Key fest einzubacken.

  1. Im Reseller-Dashboard unter "Signaturschlüssel" einen neuen Schlüssel erzeugen. Der bisherige Schlüssel bleibt aktiv – beide laufen parallel.
  2. Neue Lizenzen werden automatisch mit dem neuesten aktiven Schlüssel signiert. Bestehende JWTs bleiben gültig.
  3. Wenn alle wichtigen Lizenzen erneuert sind, alten Schlüssel zurückziehen. Das setzt eine 90-Tage-Übergangsphase, in der bestehende JWTs weiter validiert werden.
  4. Nach Ablauf der Übergangsphase verschwindet der zurückgezogene Schlüssel automatisch aus der JWKS-Antwort.

Damit dein Backend nicht bei jedem Verify einen Netzwerk-Call macht, cachet das SDK die JWKS-Antwort. Cache-Dauer ist konfigurierbar:

import { verifyLicense, configureKeyCache } from '@xcess/sdk';

// Cache JWKS responses for 10 minutes (default: 60s).
configureKeyCache({ ttlSeconds: 600 });

await verifyLicense({ jwt, vendorCode: 'acme' });

Signaturalgorithmen

xcess unterstützt drei Standard-JOSE-Algorithmen für die Lizenzsignatur. Die Wahl ist pro Schlüssel — du kannst mehrere Algorithmen parallel betreiben und sukzessive migrieren.

RS256 (RSA 2048)

Bewährter Standard, breite SDK-Kompatibilität, betreibbar mit FIPS-140-2-validierten Modulen. Signaturen sind grösser (~256 Byte), aber von praktisch jeder JWT-Library verifizierbar.

ES256 (ECDSA P-256)

Elliptische-Kurven-Variante. Ca. 5x schnellere Schlüsselerzeugung, kompakte Signaturen (~64 Byte) und Public Keys (~91 Byte). Empfohlen für eingebettete Geräte und mobile Apps.

EdDSA (Ed25519)

Modernste Variante. Deterministische Signaturen (gleicher Input ⇒ gleiche Signatur, kein RNG-Risiko), kleinste Schlüssel (~32 Byte) und sehr kleine Signaturen (~64 Byte). Keine FIPS-140-2-Modulvalidierung verfügbar.

Error-Codes

Alle API-Fehler liefern ein einheitliches JSON-Format mit `error` (kurzer Code), optional `message` (Mensch-lesbar) und `code` (interner Maschinen-Code).

HTTP-StatusError-CodeBeschreibung
400invalid_jsonRequest-Body konnte nicht als JSON geparst werden.
400invalid_inputRequest-Body verletzt das Schema (siehe `details`).
401unauthorizedAPI-Key fehlt oder ist ungültig.
403forbiddenAPI-Key besitzt nicht den erforderlichen Scope.
404not_foundAngefordertes Objekt existiert nicht.
429rate_limitedRate-Limit überschritten — Header `Retry-After` beachten.
500internal_errorUnerwarteter Server-Fehler — Logs prüfen oder Support kontaktieren.

Rate-Limits

Rate-Limiting wird pro Client-IP angewandt (Sliding Window, 60 Sekunden). Limit-Header werden bei jeder Antwort gesetzt: `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`. Bei Überschreitung kommt `429 rate_limited` mit `Retry-After`-Header (in Sekunden).

EndpointLimit / Minute
POST /api/v1/vendor/licenses60
POST /api/heartbeat120
GET /api/v1/vendor/{code}/jwks · /keys120
GET /api/healthunbegrenzt

Webhooks

Echtzeitbenachrichtigungen über Lizenzereignisse – ideal für CRM-Integration, Slack-Alerts oder Buchhaltungssysteme. Jede Auslieferung wird mit HMAC-SHA256 signiert.

Signatur-Verifizierung

Jeder Webhook-Aufruf trägt einen X-Xcess-Signature-Header. Die Signatur wird über den Zeitstempel und den Roh-Body gebildet – verwerfen Sie alle Anfragen, deren Zeitstempel älter als 5 Minuten ist (Replay-Schutz).

// Headers on every webhook delivery
X-Xcess-Signature: t=1735689600,v1=<hex_hmac_sha256>
X-Xcess-Event: license.activated
X-Xcess-Delivery: dlv_xxxxxxxx

// Verification (Node.js)
import crypto from 'crypto';
function verify(rawBody, header, secret) {
  const [tPart, sigPart] = header.split(',');
  const t = tPart.split('=')[1];
  const v1 = sigPart.split('=')[1];
  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${t}.${rawBody}`)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(v1, 'hex'),
    Buffer.from(expected, 'hex'),
  );
}

Event-Typen

  • license.created
  • license.activated
  • license.suspended
  • license.revoked
  • license.expired
  • license.heartbeat.missed

Retry & Dead-Letter

Bei nicht-2xx-Antworten wird die Auslieferung mit Exponential-Backoff wiederholt. Nach dem letzten Versuch landet der Event in der Dead-Letter-Queue und ist 30 Tage über das Reseller-Dashboard abrufbar.

Retry schedule (max 5 attempts):
  Attempt 1:    immediate
  Attempt 2:   +30s
  Attempt 3:   +5m
  Attempt 4:   +30m
  Attempt 5:   +2h
  Attempt 6:   +12h → dead-letter

Beispiel-Payload

{
  "id": "evt_01HG3K8MXYZ",
  "type": "license.activated",
  "occurredAt": "2026-04-26T08:14:32.123Z",
  "delivery": "dlv_8f3d2a1b",
  "data": {
    "licenseId": "lic_abc123",
    "vendorCode": "acme",
    "customerCode": "atos",
    "fingerprint": "sha256:e3b0c…",
    "activatedAt": "2026-04-26T08:14:30.000Z"
  }
}

Feature-Flags

Feature-Flags sind dynamisch auswertbare Schalter, die in Ihrer Anwendung Funktionen ein- oder ausblenden. Werte werden zur Laufzeit aus dem Lizenz-JWT geladen.

Scope-Hierarchie

Flags können auf drei Ebenen gesetzt werden. Es gilt strikte Priorität: License-Override schlägt Customer-Override schlägt Product-Default.

Scope priority (highest wins):
  1. LICENSE   → specific license override
  2. CUSTOMER  → customer-wide override
  3. PRODUCT   → product default

Effective value:
  effective(flag) =
    licenseOverride?.value
    ?? customerOverride?.value
    ?? productDefault.value

Wert-Typen

  • BOOLEAN — An/Aus-Schalter (z. B. Modul-Aktivierung)
  • NUMBER — numerische Limits (z. B. Sitzplätze, Mandanten)
  • STRING — String-Werte (z. B. Theme-Namen, Region)
  • JSON — strukturierte Konfiguration

SDK-Beispiel

import { fetchFlags } from '@xcess/sdk';

const flags = await fetchFlags({
  licenseJwt: storedJwt,
  vendorCode: 'acme',
});

if (flags.boolean('module.advanced_export', false)) {
  // unlock UI feature
}

const seats = flags.number('limits.max_seats', 25);
const theme = flags.string('ui.theme', 'default');
const cfg = flags.json('integrations.slack', {});

License-Bundles

Ein License-Bundle bündelt JWT, Vendor-CA und CRL-Snapshot in einer signierten ZIP-Datei (.xcesslic). Ideal für Air-Gapped-Deployments und Offline-Verifizierung.

Bundle-Format

Standardisiertes ZIP-Layout mit manifest.json als Index. Alle Dateien sind via SHA-256-Hash referenziert.

license.xcesslic   (ZIP-Container, .xcesslic suffix)
├─ manifest.json    (Bundle-Metadaten)
├─ license.jwt      (Lizenz-Token, RS256/ES256/EdDSA)
├─ vendor-ca.pem    (Vendor-Root-CA, optional)
└─ vendor-crl.pem   (CRL-Snapshot, optional)

manifest.json-Schema

{
  "version": 1,
  "issuer": "xcess.ch",
  "vendorCode": "acme",
  "customerCode": "atos",
  "licenseId": "lic_abc123",
  "issuedAt": "2026-04-26T08:00:00Z",
  "expiresAt": "2027-04-26T08:00:00Z",
  "algorithms": ["RS256"],
  "files": {
    "license.jwt": "sha256:b94d27…",
    "vendor-ca.pem": "sha256:e3b0c4…"
  }
}

SDK-Verifizierung

import { verifyLicenseBundle } from '@xcess/sdk';
import { readFileSync } from 'node:fs';

const bundle = readFileSync('./license.xcesslic');
const result = await verifyLicenseBundle(bundle, {
  expectedVendorCode: 'acme',
  expectedCustomerCode: 'atos',
});

if (!result.valid) {
  throw new Error(`License invalid: ${result.reason}`);
}

console.log('License id:', result.claims.licenseId);
console.log('Expires at:', result.claims.exp);

Sicherheit & Best Practices

  • API-Tokens nie im Client-Code oder mobilen Apps einbetten. Ausschliesslich Server-zu-Server.
  • JWT-Verifikation immer offline in deinem Backend. Niemals dem Client vertrauen.
  • Trial-Period läuft nach 14 Tagen automatisch aus. Subscription rechtzeitig aktivieren.
  • Audit-Logs in deinem xcess-Dashboard einsehen – jeder Schlüssel-Wechsel und jede neue Lizenz ist nachvollziehbar.