SpikyintegrationOpen panel
Back

Integration

Wire a product to Spiky in three calls.

Authentication

Every product request carries the product API key in x-api-key. Panel routes use an operator session instead.

POST/api/v1/activatebind a machine
POST/api/v1/validatesigned verdict
POST/api/v1/heartbeatreport the fleet
POST/api/v1/deactivatefree a slot
GET/api/v1/public-keypin the authority

Activate

Call once per machine. The response is a signed entitlement; store the license key and fingerprint locally.

curl -X POST https://license.vertalt.my.id/api/v1/activate \
  -H "content-type: application/json" \
  -H "x-api-key: spky_live_..." \
  -d '{
    "licenseKey": "SPKY-XXXX-XXXX-XXXX-XXXX-XXXX",
    "fingerprint": "sha256-of-machine-hwid",
    "serverId": "srv-eu-01",
    "serverName": "eu-01.xcord.example",
    "version": "1.75"
  }'
{
  "ok": true,
  "data": {
    "allowRuntime": true,
    "reason": "active",
    "payload": { "v": 1, "productSlug": "xcord", "plugins": [...] },
    "signature": "…base64url…",
    "keyId": "90e1c8fd60d7610e",
    "nextCheckIn": 60
  }
}

Validate

Call on a 60-second schedule. Generate a fresh nonce per call. Verify the signature before trusting allowRuntime.

POST /api/v1/validate
{
  "licenseKey": "SPKY-...",
  "fingerprint": "sha256-of-machine-hwid",
  "nonce": "n_01J8Z9K2",
  "serverId": "srv-eu-01"
}

A nonce can be used once. Spiky stores it in Redis for 180 seconds and rejects replays with409 nonce_reused. Every denial is still signed, so the client can verify allowRuntime:false instead of trusting an HTTP error.

Verify a response

Canonicalize the payload (sorted keys, no whitespace), then verify with the public key. The SDK in sdk/spiky-client.js does this for you.

import { SpikyClient } from './sdk/spiky-client.js'

const client = new SpikyClient({
  baseUrl: 'https://license.vertalt.my.id',
  apiKey: process.env.SPIKY_API_KEY,
  licenseKey: process.env.SPIKY_LICENSE_KEY,
  fingerprint: machineFingerprint(),
  serverId: 'srv-eu-01'
})

const verdict = await client.validate()
if (!verdict.allowRuntime) {
  stopService(verdict.reason) // license_suspended | product_killed | ...
}

Heartbeat

Report hostname, version, and installed plugins. Unlicensed servers still appear in the fleet.

await client.heartbeat({
  serverName: 'eu-01.xcord.example',
  version: '1.75',
  region: 'eu-central',
  plugins: [
    { slug: 'anti-cheat', version: '1.2.0' },
    { slug: 'permissions', version: '3.0.1' }
  ],
  metrics: { players: 42, uptime: 86400 }
})

Public key

Pin this key in the client. It is public and rotates only with an explicit signing-seed change.

Ed25519key 90e1c8fd60d7610eSPKI DER · base64

Operational notes

Store the license key and fingerprint locally; never send the raw HWID unhashed to logs.
Validate every 60 seconds and heartbeat every 30 seconds.
Treat allowRuntime:false as a stop signal; surface the reason to the operator.
Pin the public key; never fetch it from an untrusted origin at startup.
A product kill switch also blocks activation, not only validation.
API keys are product-scoped. Do not reuse one key across products.

Client SDK

The JavaScript client is in sdk/spiky-client.js. The Java integration example is in examples/xcord-java/.

ed25519 verify nonce per call