Skip to content
EntryWick documentation

For whoever runs a self-hosted install, when they want to know what their instance reports and what it.

The license heartbeat (self-hosted, week 21)

Once a day a self-hosted EntryWick tells EntryWick it is still there, on which version, and how many people its licensed events have had — and hears back whether the license is in good standing, whether there is a newer version, and anything EntryWick needs its operator to know. PRICING_ENTITLEMENTS §5, ADR-020.

Who opens it: whoever runs a self-hosted install, when they want to know what their instance reports and what it heard back; an EntryWick staff member looking at a license, or revoking one.

What they came to do: see that the instance reaches EntryWick and what it sends; revoke or reinstate a license.

What must be true when they leave: the operator can see exactly what leaves their server — counts, never people — and knows that a network outage, however long, never stops the app. EntryWick knows each install's version and each licensed event's peak, and nothing else.

What is sent

POST https://license.entrywick.io/v1/heartbeat at 03:45 (UTC, the container's clock), with the license key as the bearer token:

{
  "v": 1,
  "instance_id": "01j9z3k8q2m4n6p8r0s2t4v6w8",
  "version": "1.4.0",
  "channel": "stable",
  "sent_at": "2027-06-01T03:45:00+00:00",
  "peaks": { "01j9z3m1a2b3c4d5e6f7g8h9jk": 812, "01j9z3m9x8y7z6a5b4c3d2e1fg": 64 }
}
  • instance_id — made once, the first time a heartbeat is sent, and kept in storage/app/instance/id on the storage volume. Restarts and updates keep it; a backup restored onto a new server carries it along.
  • version — the image tag (EW_VERSION).
  • peaks — for each event of the organization the key licenses that was measured in the last 45 days, the highest number of active attendees on confirmed registrations it has had (the same number the attendee cap and overage use). Events are named only by their ULID.

Nothing else: no names, emails, phone numbers, answers, event titles or organization names — an automated test checks every field of the body and searches it for the people in the database. Other organizations on the same install are not reported. Nothing is sent on the cloud, without a genuine key, or to an address that is not https.

Send it by hand, and see the outcome: php artisan ew:license:check --heartbeat. Without --heartbeat the command only prints what the last one heard.

What comes back

{ "status": "active", "latest_version": { "stable": "1.4.0", "beta": "1.5.0-beta.1" },
  "notices": [ { "id": "maint-2027-06", "level": "info", "message": "…" } ], "received_at": "…" }

The instance keeps the answer in storage/app/instance/heartbeat.json, with when it last tried and last heard back.

  • Grace and expiry come from the key, not the answer. The key's own dates decide whether it is active, in its 30 days of grace, or expired; a heartbeat cannot expire a key whose dates are good, nor bring back one past them.
  • revoked starts a seven-day countdown from the first answer that says so, then the instance becomes read-only (week 21 step 3). The countdown survives restarts and a cleared cache, and does not start again with each answer. A later answer that is anything but revoked — because the license was reinstated — ends it.
  • Unreachable (no connection, a timeout, an error, an answer that makes no sense): the last answer stands. After 30 days without one the instance shows a warning, and keeps running exactly as before.
  • latest_version tells the instance whether its channel has a newer numbered version; notices are short messages for the operator (at most ten, 500 characters each).

What the instance shows for each of these — the banner, and what read-only blocks — is in license-states.md.

On EntryWick's side (staff)

The license host answers POST /v1/heartbeat (EW_LICENSE_HOST). A key that does not verify, or that the platform never issued, gets 401; a body that is not a heartbeat, 422; more than 12 an hour for one key, 429. Each accepted heartbeat updates the license's last seen time and version and its recorded peaks (the highest ever per event, for invoicing overage by hand). The first instance to report is recorded as the license's instance_id; if a different instance reports with the same key it is noted on the license and in the audit log once — never refused, since a restored or moved install looks the same. The status follows the dates (active, grace, expired) unless revoked; a change is in the audit log. Ordinary heartbeats are not.

The newest versions come from EW_LATEST_STABLE / EW_LATEST_BETA, and notices from EW_LICENSE_NOTICES (a JSON list), on the platform server.

php artisan ew:license:revoke 01j9z3… --reason=non_payment     # or --reason=breach; nothing else is a reason
php artisan ew:license:revoke 01j9z3… --reinstate

Both are audited (license.revoked, license.reinstated, from cli) and refused on a self-hosted instance. The instance hears either at its next heartbeat, within a day.