Skip to content
EntryWick documentation

For the person who installs and runs EntryWick on their organization's own server, when they install it,.

License keys (self-hosted, week 21)

A self-hosted EntryWick is the same image and Compose file as the cloud, told apart by one setting and licensed by one key. PRICING_ENTITLEMENTS §5, ADR-019, ADR-020.

Who opens it: the person who installs and runs EntryWick on their organization's own server, when they install it, renew, or change plan; and an EntryWick staff member who issues the key.

What they came to do: put the key EntryWick sent them into the install, and see that it is accepted.

What must be true when they leave: the instance knows which organization it is licensed to, for how many attendees, until when, and on which update channel — without ever having to reach EntryWick to know it. Nothing they do with the key, right or wrong, can stop the app from running or lock anybody out of an event.

On the instance

In .env:

EW_HOSTING=self_hosted
EW_LICENSE_KEY=ew_lic_…

EW_HOSTING is cloud (the default) everywhere EntryWick runs the app itself; on the cloud none of this applies. After changing the key, restart the app containers and run php artisan ew:license:check, which verifies the key, stores the result and prints it — validity: active, the cap, the dates and the channel. It never prints the key.

  • Offline. The key is checked against public keys built into the app. No connection is needed, at boot or ever.
  • When: as the app boots, and every night at 03:08 (before the entitlements sync at 03:10).
  • What it reads as: active through the last day of the key (UTC); grace for the 30 days after; expired after that; not_yet_valid before its first day; missing with no key; invalid for anything that is not a genuine key (mistyped, cut short, changed, or signed by someone else). Each of these is a state the app runs in. What grace, expired, missing and invalid change — a banner, updates, publishing new events — is in license-states.md; check-in never stops.
  • Online, once a day: the heartbeat reports counts only and hears whether the license was revoked (heartbeat.md). It never overrides the key's dates, and the app runs without it.
  • What the key grants. The organization the key names gets the Enterprise plan with the key's attendee cap and the features listed on it; other organizations on the same instance get nothing from it. A key past its dates keeps granting the same. A key that disappears or stops verifying leaves the organization on what the last good key granted — a typo in .env never drops an install to Free in the middle of an event.

Issuing a key (EntryWick staff, on the platform)

php artisan ew:license:issue bigorg --cap=5000 --valid-until=2028-02-15 [--valid-from=2027-02-15] [--channel=beta] \
    [--feature=sso --feature=white_label_full …] [--instance=…]
  • The organization must be on Enterprise (the self_hosted entitlement), or the command refuses.
  • --cap is required: a number, or unlimited. --valid-until is required; dates are calendar days in UTC.
  • Without --feature, every Enterprise feature is on. Naming features switches on only those — list attendee_overage too, or the cap blocks registrations instead of counting overage.
  • The key is printed once. EntryWick keeps its hash and a short prefix (ew_lic_ and five characters), never the key; the issue is in the organization's audit log as license.created, from cli.
  • Refused on a self-hosted instance, and on any server without EW_LICENSE_SIGNING_KEY except a developer's machine, which signs with the development key that only local and testing accept.

The format

ew_lic_ + base64url(payload JSON) + . + base64url(Ed25519 signature), without padding. The signature covers every byte before the dot, ew_lic_ included. The payload:

Field Meaning
v format version, 1
kid which public key verifies it
license the license's ULID on the platform
tenant_ulid the organization it licenses
attendee_cap a whole number, or null for unlimited
valid_from, valid_until YYYY-MM-DD, UTC; active through the whole valid_until day
update_channel stable or beta
features the boolean entitlement keys switched on (PRICING §7)
issued_at ISO 8601, UTC

Signing keys

php artisan ew:license:keygen prod-2027 prints a new pair and stores nothing. The public half is added to App\Domain\Billing\Support\LicenseKeys::PUBLIC_KEYS and committed; the secret half goes into the platform server's .env as EW_LICENSE_SIGNING_KEY with EW_LICENSE_SIGNING_KID, and into the password manager — never into the repository or a self-hosted install. Rotation is a new kid alongside the old one; the old one is removed only when no key signed with it is still in use. dev-2026 is the development key: its seed is published, so it is accepted only where APP_ENV is local or testing.