.NET SDK — offline, freshness and grace
The SDK does not call the server on every check. After activation it holds a signed, cached license and validates it locally against the embedded provider key. This is what lets you gate features on the hot path without a network round trip — and what lets a short connectivity blip pass unnoticed.
The on-disk license cache
Section titled “The on-disk license cache”The cached license lives on disk in a LicenseFileStore, keyed to the device
fingerprint, in a default location under application data. On startup the SDK serves
the cached license first and validates its signature against the pinned provider key,
so the application starts licensed without waiting on the network. On the
fluent-builder path,
UseLicenseStoreBuilder() enables the cache and lets you move it:
.UseLicenseStoreBuilder(store => store .UseLicenseFolderLocation(LicenseFileStore.LicenseFolderLocation.UserDefined, path))At-rest encryption of the cache
Section titled “At-rest encryption of the cache”The cached license is encrypted on disk, on by default. This closes a gap the signature alone doesn’t: a signed license proves it wasn’t forged, but a plain JSON file gives up its contents to anything that can read it — a backup, a synced folder, malware scraping known paths.
The encryption key derives from three inputs: your application’s key material (a
built-in default, or your own via UseProtectionKey), a random salt generated
per file, and the device’s node id. Binding to the device is what matters most:
copy the cache to another machine and it won’t decrypt there — and the signature
still rejects a forged one.
.UseLicenseStoreBuilder(store => store .UseProtectionKey(myAppEmbeddedKeyBytes)) // optional — a built-in default is used otherwiseDecryption failure — wrong key, a different device, a tampered file — is always treated as a cache miss, never a license denial: the SDK falls back to revalidating online rather than locking a legitimate user out because a file didn’t decrypt. A cache written by an SDK version predating this feature is read once as plaintext and silently rewritten encrypted.
Call DisableCacheProtection() on the store builder if the cache file needs to
stay human-readable (debugging, an environment where you handle protection
yourself); the license signature and device binding are unaffected either way.
Lease freshness
Section titled “Lease freshness”Each cached license carries a lease describing how recently it was authoritatively
validated. LeaseStatus captures where it stands:
| Status | Meaning |
|---|---|
Fresh |
Validated within the normal freshness window. Used directly. |
Stale |
Past freshness but still inside the allowed offline window — should revalidate soon. |
StaleLockout |
Past the absolute offline limit — must revalidate before further protected use. |
Unverifiable |
Freshness can’t be determined; refused like StaleLockout — the license must be revalidated before use. |
The background refresh (every LicenseRefreshIntervalSeconds) keeps the lease Fresh in
normal operation. A backend outage shorter than the allowed offline window has no
user-visible effect: the lease may go Stale, but the license keeps working until the
absolute offline limit.
A prolonged outage eventually moves the lease to lockout, at which point the license must
be revalidated before protected features resume — the SDK will not extend an unverified
license indefinitely.
Grace after expiry
Section titled “Grace after expiry”Grace is separate from freshness: it’s the window after a license’s validity ends
during which the product keeps working, giving a customer time to renew. While in grace,
LicenseStatus is GracePeriod and IsOperational stays true, so your existing
feature checks keep passing without special
casing. Read LicenseStatus == LicenseStatus.GracePeriod if you want to show a “renew
now” banner.
Clock-rollback protection
Section titled “Clock-rollback protection”Offline enforcement can’t trust the system clock alone — moving it backwards would revive an expired license. The SDK keeps a monotonic high-water record of observed time, so winding the clock back doesn’t restore expired access. This is automatic; you don’t configure it.
Fully disconnected devices
Section titled “Fully disconnected devices”For air-gapped or intermittently connected installs, add the
Revenusion.MonetizeIt.Client.OfflineStore package. It lets you carry activation across
an air gap: the device produces a signed request, an operator activates it against the
platform, and the device imports the signed response — all verified against the same
provider key, so an offline device is no less protected than an online one.