Node — @yetdapay/license-server
Quick summary
This SDK enforces licensed execution on a Node 22 server you operate (BYOS). It verifies signed entitlements and proof-of-possession, then issues one-time capabilities.
Start with the minimal server example and a development store. Use a persistent, access-controlled store before any real deployment.
sdk/license/server/ — the enforcement host for a seller's BYOS (bring-your-own-server) deployment. Node 22, zero runtime dependencies — uses only node:crypto and node:sqlite (engines.node >= 22). Verifies entitlements and DPoP-style proofs, issues and consumes one-time capabilities, and fails closed on store errors.
Setup
import {
createLicenseServer, createMemoryStore, generateKeyBundle,
} from '@yetdapay/license-server';
// or the reference persistent store:
import { createSqliteStore } from '@yetdapay/license-server/stores/sqlite';
const server = createLicenseServer({
store: createSqliteStore({ file: './.data/license.db' }),
keys: { load: async () => JSON.parse(process.env.DEVPAY_LICENSE_KEYS) },
issuer: 'devpay',
audience: 'app:my-product',
entitlementTtlSec: 120,
});2
3
4
5
6
7
8
9
10
11
12
13
Required inputs:
- Signing/verification keys — ES256 P-256 bundle, injected from a secret manager/KMS (
resolveKeys,generateKeyBundle,keyBundleFromEnvhelpers exist). Never commit them. LicenseStore— your persistence contract implementation (below).- Product feature definitions —
products.featuresis the sole entitlement source of truth; license-row features are not trusted.
API surface
import {
createLicenseServer, createWorkerConfig, WORKER_ISSUER, DEFAULTS,
toHttpResponse, requestTargetFromUrl,
signEntitlement, verifyEntitlement,
verifyProof, verifyProofSignature, readEntitlementToken, readHeader,
createMemoryStore, createSqliteStore, // stores
LicenseError, ErrorCode, asLicenseError,
} from '@yetdapay/license-server';2
3
4
5
6
7
8
createLicenseServer(options) → { activate(), authorize(), issueCapability() }:
activate({key, product_id, public_jwk})— validates the JWK (on-curve), gates on license status, atomically binds a device slot tojkt(UNIQUE(lic_id,jkt),count < max_devices) →{entitlement, expires_in, features}.authorize(headers, requestTarget, rawBody)— the guard chain: entitlement sig/iss/exp→ live license re-query → proof sig/htm/htu/iat→jktmatch →jtiatomic consume → body hashh→ feature authorization. Any denial → the mapped error, and the internal reason is never exposed.issueCapability({feature})— 60-second one-time capability; atomic consume (UPDATE … WHERE consumed=0) → reuse →409 CAPABILITY_CONSUMED.
LicenseStore contract
Required methods (see src/index.d.ts):
getProduct · findLicenseByKey · getLicense · bindDevice · countBindings · setLicenseStatus · consumeNonce · createCapability · getCapability · consumeCapability (+ optional pruneNonces)
Shipped adapters:
createMemoryStore(seed?)— in-memory, for tests/dev.createSqliteStore({file})(@yetdapay/license-server/stores/sqlite) — reference implementation onnode:sqlite; the template for D1/Postgres ports.node:sqliteis experimental in Node 22 (prints a warning) — production BYOS should port the adapter rather than ship it.
Any store failure surfaces as INTERNAL — fail closed, never a permission grant on a DB error.
Worker preset
import { createLicenseServer, createWorkerConfig } from '@yetdapay/license-server';
const server = createLicenseServer(createWorkerConfig({ store, keys }));2
Sets issuer: 'devpay-lic' (WORKER_ISSUER) and drops the aud requirement to match the hosted Worker's token format. Other options pass through as overrides.
Error model
LicenseError + ErrorCode map failures to {code,message} + HTTP status — VALIDATION (400), INVALID_PUBLIC_JWK (400), NOT_FOUND (404), PRODUCT_MISMATCH/FORBIDDEN/REVOKED/DEVICE_LIMIT/INVALID_STATUS (403/409), UNAUTHORIZED (401 — one opaque code for every auth check), CAPABILITY_CONSUMED (409), BODY_TOO_LARGE (413), INTERNAL/CONFIG (500). Worker-vs-SDK deltas are documented in the contract matrix §2 — deliberate, not bugs.
TTLs & windows
entitlement 120s · proof iat ±60s skew · capability 60s · nonce TTL 600s — all from DEFAULTS/PROOF_IAT_WINDOW_SEC/CLAIM_CLOCK_SKEW_SEC.