Skip to content

Offline (air-gapped) activation

A machine with intermittent connectivity doesn’t need file-based activation — the .NET SDK caches the license on disk and validates it offline for as long as the offline-grace policy allows. This page is for machines that never connect: activation and lease renewal travel as files.

Everything rests on one trust anchor: license artifacts are signed with the provider’s key, and the device verifies them with the public key you shipped — no network required. The public key is safe to ship: it can verify, not mint, so carrying it on the device grants an attacker nothing. What is worth protecting is the cached license itself once it lands on disk — see at-rest encryption, which the offline flow gets automatically since it uses the same LicenseFileStore.

  1. On the device — the SDK writes an activation request file (activation code, fingerprint, application info).
  2. Carry it out — USB stick, one-way transfer, whatever your site allows.
  3. In the portal — an operator uploads the request under Offline activation; the server runs the normal activation pipeline and returns a signed response file.
  4. Carry it back — return the response file to the device.
  5. On the device — the SDK imports the response, verifies the signature and installs the license.
carried by hand — USB, one-way transfer Air-gapped device never networked writes request files, imports signed files, queues usage locally verifies everything with the provider public key Provision Portal Offline activation page issues signed responses, ingests usage & extends the lease idempotent: re-upload never consumes a seat 1 · activation-request.json 2 · signed response — the license 3 · usage-export.zip 4 · signed receipt — fresh lease

Add-on package: Revenusion.MonetizeIt.Client.OfflineStore (alongside Client.LicenseConsumer).

using Revenusion.MonetizeIt.Client.LicenseConsumer.Features.OfflineActivation;
var request = new OfflineActivationRequestBuilder()
.WithActivationCode("ABC123-DEF456-GHI789")
.WithFingerprint(nodeIdProvider)
.WithApplicationInfo(applicationInfoProvider)
.Build();
await OfflineActivationRequestWriter.WriteToFileAsync(request, @"D:\transfer\activation-request.json");

The fingerprint provider is the same one the online flow uses — including per-component hashes, which are included in the request.

In the portal, open Offline activation, upload the request file, and download the signed response. From your own backend, the same operation is POST /api/v1/provision/operational/activations/offline/issue.

Submitting a byte-identical request file twice within a short server-side window returns the same signed bytes; a re-export from the device produces a fresh signature. A stronger guarantee holds at the session level: the same device (fingerprint) re-activating reuses its existing session, so a re-issued response file — however it was produced — can never consume a second seat.

using Revenusion.MonetizeIt.Client.LicenseConsumer.Features.OfflineActivation;
var importer = new OfflineActivationResponseImporterBuilder()
.WithFingerprint(nodeIdProvider) // required: refuses a license issued for another device
.WithLicenseStore(licenseStore)
.WithPublicKeyProvider(publicKeyProvider)
.WithLicenseService(licenseService) // optional: refreshes the in-memory cache too
.Build();
var result = await importer.ImportAsync(@"D:\transfer\activation-response.json");
if (!result.Success) throw new InvalidOperationException(result.Message);

The importer verifies the envelope signature against the pinned public key before touching the license store. From here the application runs as if it had activated online.

Air-gapped machines accumulate usage locally (FileTransactionStore). Periodically, swap files again to upload usage and renew the lease:

  1. Export on the device:

    using Revenusion.MonetizeIt.Client.LicenseConsumer.Services.Store;
    var exporter = new LicenseZipFileExporter(@"D:\transfer\usage-export.zip", licenseStore, transactionStore);
    await exporter.ExportAsync();
  2. Upload in the portal — Offline activation → Ingest transactions — and download the signed receipt. (API: POST /api/v1/provision/operational/usage/offline/ingest.) The server records the usage, extends the lease, and wraps a refreshed license in the receipt.

  3. Import the receipt on the device:

    using Revenusion.MonetizeIt.Client.LicenseConsumer.Features.OfflineActivation;
    var importer = new OfflineTransactionReceiptImporterBuilder()
    .WithFingerprint(nodeIdProvider) // required: refuses a license issued for another device
    .WithTransactionStore(transactionStore)
    .WithPublicKeyProvider(publicKeyProvider)
    .WithLicenseService(licenseService)
    .Build();
    var result = await importer.ImportAsync(@"D:\transfer\usage-receipt.json");

    On success the refreshed license is installed first, then the uploaded transactions are purged from the local log — if anything fails before the purge, the log stays intact for retry.

Repeat the swap on whatever cadence the entitlement’s lease window requires, and an air-gapped fleet stays licensed and metered indefinitely.

  • The whole loop is operator-driven through the portal in this release — there is no end-user self-service for offline activation.
  • The request file is not secret (it contains hashes, not raw device traits), but treat response and receipt files as license material: they’re single-purpose, device-bound, and verified by signature on import.