Skip to content

.NET SDK — device fingerprints

Activation binds a seat to a device fingerprint — a stable identifier for the install. The fingerprint is what lets the platform tell “the same machine reconnecting” from “a new machine claiming a seat”, so a restart or redeploy reuses its seat instead of consuming a new one.

Seats are counted per fingerprint. If the fingerprint changes between runs, each run looks like a new device: the entitlement fills up and further activations are refused. A fingerprint that stays constant across process restarts is therefore the single most important property — prefer a value you can guarantee is stable over one derived from volatile hardware.

The SDK resolves it through INodeIdProvider:

  • From configuration — the default provider reads Oclavex:LicenseConsumer:NodeId. Set it to a value that is stable for the install (for example, a per-deployment id you persist).
  • Derived from the device — a machine-derived identifier (machine name and network adapter). Convenient, but on machines with VPNs or virtual adapters the “first” adapter can vary between runs, so it is best treated as a fallback rather than the primary identity.

DeviceIdDeviceFingerprint composes a fingerprint from device traits and hashes it on the device — raw values never leave the machine. Pass it to UseNodeId on the fluent builder, or register it as the INodeIdProvider:

var fingerprint = new DeviceIdDeviceFingerprint
{
FingerprintMethod = DeviceIdDeviceFingerprint.FingerprintMethods.MacAddress
| DeviceIdDeviceFingerprint.FingerprintMethods.MachineName
| DeviceIdDeviceFingerprint.FingerprintMethods.OsVersion
};

The default is MacAddress | MachineName. Available traits: MachineName, OsVersion, MacAddress, UserName.

The selected values are concatenated and hashed with SHA-256, then encoded Base64Url. That digest is the fingerprint; the raw traits stay on the machine. If none of the selected traits yields a value, the machine name is used so the provider always returns something rather than an empty fingerprint.

MacAddress picks one adapter and holds it for the life of the process. The choice ignores loopback, tunnel and dial-up adapters and the usual virtual ones, and does not depend on enumeration order, so a VPN or container adapter coming up mid-session does not change the fingerprint underneath a running install.

For a value you control — a tenant id, a provisioned device id, a stored GUID — register a scoped INodeIdProvider before AddMonetizeItLicenseConsumer:

builder.Services.AddScoped<INodeIdProvider>(_ =>
StaticNodeId.Create(deploymentId));

In a multi-tenant host, resolve the fingerprint from the current tenant so each tenant’s seats are counted independently — pair this with the per-tenant ILicenseKeyProvider from install and configure.

Short fingerprints for manual / air-gapped entry

Section titled “Short fingerprints for manual / air-gapped entry”

A hashed fingerprint is normally moved as a file or an API call, not typed by hand. When the only channel available is a person reading a value off one screen and entering it on another — a fully air-gapped machine, a value read aloud over the phone — a 40+ character hash is impractical. StaticNodeId.CreateShort produces a short, grouped alternative instead:

builder.Services.AddScoped<INodeIdProvider>(_ =>
StaticNodeId.CreateShort(deploymentId));

The result looks like 7K4M-QX2P-9J — an 8-character payload grouped for readability plus a 2-character checksum, so a mistyped fingerprint is rejected rather than silently activating the wrong device.

If you build your own entry box for that value, check it with TryCanonicalize and send back what it gives you:

if (!ShortFingerprintSigner.Instance.TryCanonicalize(typed, out var fingerprint))
return "That is not a valid device fingerprint.";

It accepts any case and any separators, and forgives the look-alike substitutions people most often make copying a code by hand (O/0, I/L/1) — the same tolerance an activation code has. What it returns is the exact value CreateShort produced. Send that, not what was typed: a bare fingerprint is matched by exact equality — send the typed text and your check passes, but the server never matches the device.

Beyond the composite hash, the provider also surfaces each trait as its own one-way hash (Components), keyed MachineName, MacAddress, OsVersion and UserName. Sending components lets the server recognise a device whose fingerprint drifted, such as a swapped network card or an OS upgrade, instead of treating it as a new device and taking another seat.

How much drift is tolerated is the entitlement’s choice, not the client’s:

Strategy A returning device is recognised when
MatchAll the whole fingerprint is identical. This is the default.
MatchAny at least one component still matches
MatchTwo at least two components still match
MatchMost a majority of components still match

Two things follow from the strategy living on the server. The tolerant strategies apply only when the client sends components, so a client that reports a bare fingerprint is always matched exactly, whatever the entitlement says. And sending components never loosens anything on its own: an entitlement left on MatchAll keeps exact matching. Both halves have to agree before drift is forgiven, which is why this is safe to adopt gradually.

Sending components is additive, so existing clients keep working unchanged.

In the portal, open an entitlement and go to the Policies tab. The setting is Fingerprint component matching.

An activation code can carry rules about the fingerprint, which surface as these errors:

  • “A device fingerprint is required to activate this license.” — the code requires the device to identify itself and the request carried no fingerprint. Check that your INodeIdProvider is returning a value.
  • “This activation code is locked to a different device.” — the code is bound to one machine’s fingerprint and this is not that machine. If it should be, the bound value on the code needs updating; a fingerprint that changed on its own points at an unstable provider, which why stability matters covers.

Both are described from the vendor’s side in Locking a code to one device.

Where hardware traits are meaningless (containers, autoscaling), derive a stable logical fingerprint instead:

using Revenusion.MonetizeIt.Client.LicenseConsumer;
builder.Services.AddScoped<INodeIdProvider>(_ =>
StaticNodeId.Create(customerIdentifier));

Same identifier, same fingerprint — one seat per customer environment across restarts and instances.