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 öffnenVendor-Migration
Schritt-für-Schritt-Guide für die Migration von eigener JWT-Signierung auf xcess.ch.
Migration startenQuickstart
- Vendor-Konto registrieren (kostenlose 14-Tage-Trial).
- API-Token aus dem Sign-Up-Dialog sicher speichern (wird nur einmal angezeigt).
- Erste Lizenz via REST-API oder SDK erstellen (siehe unten).
- 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/licensesHeaders
Authorization: Bearer xv_live_<your-token>
Content-Type: application/jsonRequest 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-keyAlle 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>/keysResponse (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/sdkLizenz 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.pemSchlü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.
- Im Reseller-Dashboard unter "Signaturschlüssel" einen neuen Schlüssel erzeugen. Der bisherige Schlüssel bleibt aktiv – beide laufen parallel.
- Neue Lizenzen werden automatisch mit dem neuesten aktiven Schlüssel signiert. Bestehende JWTs bleiben gültig.
- Wenn alle wichtigen Lizenzen erneuert sind, alten Schlüssel zurückziehen. Das setzt eine 90-Tage-Übergangsphase, in der bestehende JWTs weiter validiert werden.
- 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-Status | Error-Code | Beschreibung |
|---|---|---|
| 400 | invalid_json | Request-Body konnte nicht als JSON geparst werden. |
| 400 | invalid_input | Request-Body verletzt das Schema (siehe `details`). |
| 401 | unauthorized | API-Key fehlt oder ist ungültig. |
| 403 | forbidden | API-Key besitzt nicht den erforderlichen Scope. |
| 404 | not_found | Angefordertes Objekt existiert nicht. |
| 429 | rate_limited | Rate-Limit überschritten — Header `Retry-After` beachten. |
| 500 | internal_error | Unerwarteter 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).
| Endpoint | Limit / Minute |
|---|---|
| POST /api/v1/vendor/licenses | 60 |
| POST /api/heartbeat | 120 |
| GET /api/v1/vendor/{code}/jwks · /keys | 120 |
| GET /api/health | unbegrenzt |
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.createdlicense.activatedlicense.suspendedlicense.revokedlicense.expiredlicense.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-letterBeispiel-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.valueWert-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.
