SDKey
How each feature works
A deeper look at edge caching, the sealed session protocol, Private inventory, Bearer tooling, and the client SDK contract — not just what SDKey offers, but how the pieces fit together.
Edge network: D1 truth, KV cache
SDKey’s API is a Cloudflare Worker. Durable license rows live in D1; hot reads go through Workers KV so a validate near the user does not round-trip a single origin for every hit.
You do not run a license VM, reverse proxy, or regional failover stack. Clients talk to the edge; your game or desktop binary stays out of the critical path for “is this key good?”
How it works
Validate hits the Worker closest to the client
After session init, sealed validate lands on Cloudflare’s network. The Worker loads the short-lived session from KV (
sess:{appId}:{sessionId}, TTL ~15 minutes), opens the AES-GCM envelope, then looks up the license by hash.License cache is read-through
Hot path:
KV_CACHE.get(lic:{keyHash}). On miss, the Worker queries D1, thenputs the payload with a 300s TTL. Cached reads aim for sub-10ms; durable storage sits behind every miss.Mutations keep the cache coherent
Create, revoke, ban, HWID bind, or expiry changes write through D1 and refresh or delete the KV entry so the next validate sees the new status where clients actually hit the edge.
What stays out of your ops list
No license origin to patch, scale, or fail over — uptime follows Cloudflare’s global footprint.
D1 remains the source of truth; KV is a cache, never the only copy of inventory.
Rate limits and security logs still run on the Worker so abuse controls stay at the edge too.
Sealed session protocol
Clients never send a bare license key as the only protection. They open a short-lived session, verify the server’s Ed25519 identity, derive a shared AES key, seal the validate payload, and must verify the sealed response before trusting success.
The same byte layouts apply in every official SDK: canonical JSON, fixed crypto order, ±60s clock-skew rejection. Skipping verify defeats anti-spoof protection.
How it works
Embed build-time constants
Ship
API_BASE_URL,APP_ID, exactAPP_VERSION, and the app’s Ed25519APP_PUBLIC_KEY. The private signing key never leaves the server — clients only verify.POST /api/v1/session/init
Send
appId, a 32-byte client nonce, and exactclientVersion. The Worker returnssessionId, server nonce, HKDF salt, timestamp, andsignatureB64.Verify Ed25519 over canonical JSON of
{ appId, hkdfSaltB64, serverNonceB64, sessionId, timestamp, v }, then derive a 32-byte AES key: HKDF-SHA256 with IKM = clientNonce ‖ serverNonce, info =sdkey-session-v1‖ appId.POST /api/v1/licenses/validate (sealed)
Inner plaintext:
licenseKey, optionalhwid, nonce, timestamp. Outer HTTPS body: session id + AES-256-GCM IV, ciphertext, and tag.Response is sealed too and carries an Ed25519 signature. Mandatory client order: open the envelope → verify signature with the embedded public key → check session id and clock skew → only then honor
success.HWID lock on first success
When HWID is sent and the app has HWID lock enabled (default), the first successful validate binds the device. Later mismatches return
HWID_MISMATCH. Omit HWID to skip lock, mismatch, and HWID-ban checks. There is no separate activate or heartbeat endpoint — re-validate with a fresh nonce when you need another check.
Failure modes worth knowing
Version gate: clientVersion must match app settings or you get APP_OUTDATED (plaintext on init).
Sealed business failures often return HTTP 200 with a sealed body — read code / message inside the plaintext, not a top-level HTTP error alone.
Common sealed codes include LICENSE_NOT_FOUND, EXPIRED, BANNED, HWID_MISMATCH, REPLAY, CLOCK_SKEW, SESSION_EXPIRED.
Private account mode
Default mode stores server-minted keys and shows plaintext once at create. Private mode flips the threat model for inventory at rest: keys are minted in your browser, then registered blind — the server keeps hashes and a short prefix only.
That hardens database dumps and stolen API-key material. It does not stop a compromised client or a patched binary that skips verify. GDPR roles for end-user data are covered in the Privacy Policy.
How it works
Switch modes in Settings (password re-auth)
Private mode is an account-level setting. Existing Private licenses stay hash-only after you switch; default mode remains available when you want server-minted keys shown once.
Mint locally, register blind
The dashboard (or tooling API blind-create path) generates the key client-side, hashes it, and sends the hash + short prefix to the API. Plaintext never lands in D1, KV, or server logs for those keys.
Optional encrypted vault notes
Notes you attach for your own inventory unlock only in the browser. We store salt and a check blob — not your passphrase — so vault unlock stays client-local.
Validate still works the same for end users
End-user clients still seal
licenseKeyon validate. The server hashes the submitted key and matches the stored hash — Private mode changes inventory storage, not the sealed wire protocol.
What Private mode is not
Not DRM that survives a cracked binary — clients must still verify sealed responses.
Not a substitute for good key distribution — if you paste plaintext keys into Discord, Private mode cannot help.
Not required — keep default mode when you prefer server mint + one-time plaintext display.
Tooling API (Bearer developer keys)
Developer API keys look like sdk_live_…. They are full-account credentials (no scopes): the same surfaces as the dashboard, without a browser cookie. Built for CI, release bots, and store sync.
They do not authenticate client license validation or end-user register / login / upgrade — those stay on the public client paths.
How it works
Create once in Settings → API keys
Plaintext is shown only at creation. Afterward you see a prefix, name, and
last_used_at. The server stores a hash only — revoke anytime from the dashboard or API.Call with Authorization: Bearer
Send
Authorization: Bearer sdk_live_…on every tooling request. Dashboard sessions remain cookie-auth and separate, so CI never needs a cookie jar.Automate the same inventory the UI exposes
Create / list / revoke licenses (including blind create in Private mode), set subscription tiers, ban by license / HWID / IP, reset HWID, manage users, patch app settings, and read logs — without standing up your own auth service.
Typical CI shapes
Mint keys on release: POST /api/v1/licenses/create with duration and optional subscriptionTier.
Revoke a leaked key or ban a device: POST /licenses/:id/ban with scope license | hwid | hwid_and_ip.
Bump the version gate from a pipeline: PATCH /apps/:id/settings with the new version string clients must match.
Official client examples
Drop-in samples under SDKeyDev implement the same protocol document: embed the app public key, open a session, seal validate, verify before trust — across scripting, systems, and native desktop or game stacks.
The in-repo @sdkey/sdk TypeScript client covers session init, sealed validate (optional HWID), and end-user register / login / upgrade if you prefer not to hand-roll Web Crypto.
What every official client must do
Same wire contract
Canonical JSON (sorted keys, no insignificant whitespace), Ed25519 verify, HKDF session key, AES-256-GCM seal, and the mandatory decrypt → verify → skew check order before reading
success.Pick a language you already ship
TypeScript (reference), Python, Ruby, Perl, PHP, Java, Go, Rust, C++, and C# samples live as separate repos so you can clone only what you need and wire HWID into your binary.
Separate path for end-user accounts
Register / login / upgrade are public plaintext JSON endpoints (not sealed). They still require exact
clientVersionand honor IP/HWID bans — useful when your product has usernames in addition to license keys.
Start integrating
Follow license control for session init and sealed validate, API keys for Private mode and CI automation, clone an example from SDKeyDev, or use the full reference when you want every endpoint. The dashboard is ready when you are — register an app, mint a key, and hit the edge.