Concepts
How the license model works
This is the mental model behind Simple License Server. If you understand these concepts, the endpoint behavior is straightforward.
System flow in plain language
- You define a slug (policy template) for a product or plan.
- Your trusted backend calls
POST /generatewith that slug. - The server creates a license key with expiration and seat limits resolved at that moment.
- The client app activates the license with a fingerprint, consuming a seat.
- The client periodically validates the same license+fingerprint pair.
- Seats can be released with
POST /deactivate; full access can be blocked withPOST /revoke.
What is a slug?
A slug is a policy anchor for new licenses. It does not represent a customer; it represents how licenses should behave when they are created.
- Defines
max_activations(seat limit). - Defines expiration policy (
forever,duration, orfixed_date). - Is resolved at generation time, then stored on the license record.
- Changing a slug later affects future licenses, not already-issued ones.
By default, the server creates a default slug with one seat and no expiration.
Want a deeper view of naming rules and policy snapshots? See Slug concepts.
How seats work
Seats are tracked as active activation rows per fingerprint. A seat is in use when that fingerprint has an activation with no deactivated_at value.
/activatewith a new fingerprint creates a seat if the license still has capacity.- Calling
/activateagain for an already-active fingerprint does not consume another seat. /deactivatereleases that fingerprint seat immediately.- Revocation is different:
/revokeblocks the whole license, not one seat.
License API vs Management API
- License API:
/generate,/revoke,/activate,/validate,/deactivate. - Management API:
/management/slugs,/management/api-keys, and/management/webhooksroutes for operator tooling and frontend-backed workflows. - Never call key-protected routes directly from browser code.
- For payment webhooks, use provider event id as
Idempotency-Keyon/generate.
Outbound webhooks and idempotency
Outbound webhooks are asynchronous delivery jobs. Endpoint configuration lives in Management API, and delivery is triggered by License API events.
- Delivery retries can happen, so receivers should dedupe using
Idempotency-Key: sls-webhook-<delivery_id>. - Additional headers include
X-Webhook-Delivery-IdandX-Webhook-Event. - Payload shape includes top-level
id,type,attempt,occurred_at, and adataobject with event-specific fields. - Current event types:
license.generated,license.activated,license.deactivated,license.validated,license.validation_failed,license.revoked.
For full contract details (headers, envelope, and event payload schemas), see Webhook concepts.
Glossary
| Concept | Meaning |
|---|---|
| Slug | Policy template for newly generated licenses. Controls max activations and expiration policy for licenses created under it. |
| License | Issued key tied to a slug. Stores status, metadata, created time, and resolved expires_at. |
| Seat | One active fingerprint binding for a license. Seats are represented by active rows in activations. |
| Fingerprint | Device/app-instance identifier sent by your client to activate, validate, and deactivate. |
| License API | License lifecycle endpoints. /generate and /revoke require a generated server API key; runtime routes are called by your app. |
| Management API | Operator/internal endpoints under /management/* that require a management bootstrap key. |
| Runtime API | Client-facing endpoints (/activate, /validate, /deactivate) used by your shipped app. |
| Idempotency-Key | Header used on /generate to make webhook retries safe and avoid duplicate license issuance. |
| Revocation | License-level action that sets status to revoked and blocks future activate/validate calls. |