CertifiEd

Hardware binding

A CertifiEd license can be tied to a specific customer machine. The binding lives on the license itself — the hwBinding mode and the hwFingerprint value — and is checked on every activation. The fingerprint travels inside the signed token (hwfp), so it cannot be forged on the client.

Implementation: HwBindingMode (CertifiEd.Domain/Enums), HwBindingNames (CertifiEd.Application/Licensing); the check lives in ClientLicenseService.CheckBinding, issuance and rebinding in LicenseService.

The three modes

Mode (wire)Fingerprint at issuanceWho may activate
noneforbiddenany machine; hwFingerprint is optional on activate
fixedmandatoryonly the machine with that fingerprint
firstActivationforbiddenany machine, but the first activation pins the fingerprint; from then on it behaves like fixed

The JSON names are exactly none, fixed, firstActivation (parsing is case-insensitive). An unknown value yields license.invalid_hw_binding (400). When hwBinding is omitted the mode is inferred from hwFingerprint for backwards compatibility: present → fixed, absent → none.

When to use which

  • `none` — SaaS-like and floating licenses, containers and autoscaling, where the hardware changes on its own. Constrain the seat count instead: limits.maxSeats in the config.
  • `fixed` — a customer server you already know, because you collected its fingerprint during onboarding. The strictest option: a foreign machine never activates.
  • `firstActivation` — the typical on-prem delivery: the fingerprint is unknown up front, but the license must settle on one machine. The customer installs the product and the first activation claims the node.

The fingerprint

The fingerprint is computed by the client. By default the SDK uses HwFingerprint.Get() — a SHA-256 over the machine name, the OS description, the first stable MAC address and the platform machine id. Your own value goes into CertifiEdClientOptions.HwFingerprint — see the Client SDK.

The server treats the fingerprint as an opaque string: it trims surrounding whitespace and compares byte for byte (StringComparison.Ordinal). Case matters.

1. Issuance

bash
# fixed — the fingerprint is mandatorycurl -s -b cj.txt -X POST $BASE/api/v1/panel/licenses \  -H 'Content-Type: application/json' \  -d '{"companyId":"'"$COID"'","templateId":"'"$TPLID"'",       "config":"{\"features\":[\"core\"],\"limits\":{\"maxSeats\":1}}",       "hwBinding":"fixed","hwFingerprint":"fp-machine-A"}'
# firstActivation — the fingerprint is forbidden, it is captured on first usecurl -s -b cj.txt -X POST $BASE/api/v1/panel/licenses \  -H 'Content-Type: application/json' \  -d '{"companyId":"'"$COID"'","templateId":"'"$TPLID"'",       "config":"{\"features\":[\"core\"]}","hwBinding":"firstActivation"}'

Validation failures:

JSON
{ "code": "license.hw_fingerprint_required",  "message": "A 'fixed' hardware binding requires hwFingerprint.",  "status": 400 }
{ "code": "license.hw_fingerprint_not_allowed",  "message": "A 'firstActivation' binding must not carry hwFingerprint — it is captured on the first activation.",  "status": 400 }

The same rules apply to bulk issuance at POST /api/v1/panel/licenses/bulkhwBinding and hwFingerprint are set for the whole batch.

2. Activation

bash
curl -s -X POST $BASE/api/v1/client/activate \  -H 'Content-Type: application/json' \  -d '{"licenseKey":"'"$KEY"'","hwFingerprint":"fp-machine-A","machineName":"erp-01"}'
  • fixed, or an already-pinned firstActivation: the fingerprint is compared; a mismatch yields activation.fingerprint_mismatch (400).
  • A not-yet-pinned firstActivation: hwFingerprint is mandatory, otherwise activation.fingerprint_required (400). On success it is written to license.hwFingerprint and audited as license.hw_bound.
  • none: nothing is checked.

3. Machine replacement: rebinding

The customer swapped the server. An operator or a company Admin performs the rebind:

bash
curl -s -b cj.txt -X POST $BASE/api/v1/panel/licenses/$LICID/rebind \  -H 'Content-Type: application/json' \  -d '{"hwFingerprint":"fp-machine-B","reason":"motherboard replaced"}'
  1. Rights (Admin on the license's company; operators always qualify) and status are checked.
  2. hwFingerprint and hwBinding are updated.
  3. Every active activation is deactivated — their seats are freed.
  4. The license is re-signed: currentVersion + 1, a fresh signed version carrying the new hwfp in its payload.
  5. A license.rebound webhook event is published and an audit entry of the same name is written — with the old and new fingerprint, both modes, the reason and the number of activations dropped.
LicenseResponse
{  "id": "019f7ef0-b274-7b01-8417-8181c1d2facf",  "licenseKey": "CFED-5WEE-KE7C-FP7A-C89M",  "status": "Active",  "currentVersion": 2,  "isTrial": false,  "hwBinding": "fixed",  "hwFingerprint": "fp-machine-B"}

4. Releasing the binding

bash
curl -s -b cj.txt -X POST $BASE/api/v1/panel/licenses/$LICID/rebind \  -H 'Content-Type: application/json' \  -d '{"hwFingerprint":null,"reason":"released"}'
# fixed -> firstActivation (re-pins on the next activation)# none  -> none            (nothing to release)

hwBinding in the request overrides that inference: {"hwFingerprint":null,"hwBinding":"none"}, for instance, drops hardware control entirely. The fixed → firstActivation transition is exactly what you want when handing a license out into the field: the customer pins it to the new server on first run.

Limitations

  • A revoked (license.rebind_revoked, 409) or expired (license.rebind_expired, 409) license cannot be rebound — reissue it instead.
  • The API will not create a fixed license without a fingerprint: both issuance and rebinding reject hwBinding: "fixed" with no hwFingerprint. A defensive branch on activation still exists and answers activation.not_bound (409).
  • Binding does not cap the seat count — that is the separate limits.maxSeats mechanism inside the signed config. hwBinding: "none" plus maxSeats: 5 is a perfectly valid five-machine floating license.
  • The fingerprint is computed by the client, so this protects against casually copying a license, not against a determined reverse-engineer. The cryptographic guarantee comes from the token signature, not from the binding.
  • A rebind bumps the license version; currentVersion also grows on transfers. There is no cap on the number of rebinds — every one of them shows up in the audit log.

Error diagnostics

CodeHTTPWhereCauseWhat to do
license.hw_fingerprint_required400issue / bulk / rebindfixed without a fingerprintPass hwFingerprint or pick another mode
license.hw_fingerprint_not_allowed400issue / bulk / rebindA fingerprint with none or firstActivationDrop hwFingerprint
license.invalid_hw_binding400issue / bulk / rebindUnknown modeOnly none, fixed, firstActivation
activation.fingerprint_mismatch400activateWrong machineRebind the license and re-issue the .ced
activation.fingerprint_required400activatefirstActivation, the client sent no fingerprintCheck that the SDK computes one and it is not overridden with an empty string
activation.not_bound409activatefixed with no fingerprintOperator: run a rebind with a fingerprint
activation.seat_limit409activatelimits.maxSeats exhaustedFree a seat: deactivate, an activation reset, or reissue with a larger limit
license.rebind_revoked409rebindThe license is revokedReissue
license.rebind_expired409rebindThe license has expiredReissue

Useful places to investigate:

bash
# current mode, fingerprint and versioncurl -s -b cj.txt $BASE/api/v1/panel/licenses/$LICID
# which machines activated, and when they were releasedcurl -s -b cj.txt $BASE/api/v1/panel/licenses/$LICID/activations
# full history: license.rebound / license.hw_bound / activation.rejectedcurl -s -b cj.txt "$BASE/api/v1/panel/audit?entityId=$LICID"

See also

  • API reference — the full endpoint reference, rebind and bulk included.
  • Client SDK — binding from the integrator's point of view.
  • License protocol — where hwfp lives inside the token and the marker.
  • Webhooks — the license.rebound event.