<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[PermitCore Blog]]></title><description><![CDATA[PermitCore Blog]]></description><link>https://permitcore.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Wed, 04 Nov 2026 15:46:26 GMT</lastBuildDate><atom:link href="https://permitcore.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Offline Grace Mode: Keeping Your App Working Without a Connection]]></title><description><![CDATA[Signed cache tokens, a sensible grace period, and what the SDK does when the license server is unreachable.]]></description><link>https://permitcore.hashnode.dev/offline-grace-mode</link><guid isPermaLink="true">https://permitcore.hashnode.dev/offline-grace-mode</guid><category><![CDATA[licensing]]></category><category><![CDATA[C#]]></category><category><![CDATA[offline first]]></category><category><![CDATA[SaaS]]></category><category><![CDATA[Security]]></category><dc:creator><![CDATA[PermitCore]]></dc:creator><pubDate>Wed, 04 Nov 2026 09:41:39 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6adc6d80a6d3c5bbf6194933/ec188a8e-dfcb-436f-b7ed-992e70e2a072.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>Originally published on the <a href="https://permitcore.dev/blog/offline-grace-mode">PermitCore blog</a>.</em></p>
<p>Your customer's laptop goes into airplane mode, their office firewall blocks outbound requests, or the license API has a rough five minutes. A paying customer should still be able to open your app and get on with their work. That's what offline grace mode is for: signed cache tokens that keep a licensed app running through short disconnections.</p>
<h2>How it works</h2>
<p>Every successful <code>validate()</code> or <code>activate()</code> call, PermitCore signs a small payload (license status, remaining activations, feature flags, expiry) with the tenant's own ECDSA P-256 private key and returns it alongside the normal response. The SDK caches that signed token locally. The next time the network is unreachable, the SDK checks the cached token's signature instead of failing outright.</p>
<p>That is very different from a plain <code>{"isValid":true}</code> file anyone can edit: a valid signature requires the tenant's private signing key. The private key stays on your side, never inside the customer app, and the app verifies tokens with the matching trusted public key.</p>
<h2>Choosing the grace period</h2>
<p>You set the number of offline grace days per license, in the same form you use to generate the key. The default is 21 days: long enough to cover a holiday or a slow IT department, short enough that a machine that stays offline for good eventually asks to reconnect.</p>
<ul>
<li>Set it to a shorter window if you want tighter periodic check-ins.</li>
<li>Set it to <code>0</code> to disable offline mode for a license, for example when your product phones home by design (metered billing).</li>
<li>For Store subscription licenses, the signed offline deadline is the earlier of your configured offline window and paid-through plus payment grace. Payment grace defaults to 14 days and can be set in Store settings or overridden per license. After a renewal, the next reconnect refreshes the token.</li>
</ul>
<h2>What the SDK does with it</h2>
<pre><code class="language-csharp">// SDK 2.1.1. trustedTenantPublicKey is vendor-approved app configuration.
using var client = new PermitCoreClient(new PermitCoreOptions {
    BaseUrl = apiUrl,
    TrustedPublicKeys = new[] { trustedTenantPublicKey },
    CacheDirectory = appCacheDirectory,
    DeviceId = stableDeviceId
});
var result = await client.ValidateAsync(key, expectedProductId: productId);
if (!result.IsValid) { /* Ask the customer to reconnect or resolve the license problem. */ }
if (result.IsOffline) { /* Show the offline status to the customer. */ }
</code></pre>
<p>This logic already ships in all 8 official PermitCore SDKs (.NET, Node.js, Python, Java, C++, PHP, Go and Unity), so you only write signature verification yourself if you integrate over the raw REST API.</p>
<h2>Make it a layered setup</h2>
<p>Offline grace is availability protection for honest customers. It works best next to the other license controls: keep the grace window sensible for your audience and combine it with a devices-per-license cap, so each key stays tied to the machines your customer actually owns. Server-side signals and the signed cache then back each other up.</p>
<h2>Keep reading</h2>
<ul>
<li>Offline grace sits inside a bigger picture of outage behaviour: <a href="https://permitcore.dev/blog/what-happens-if-a-license-server-goes-down">What happens if a license server goes down</a></li>
<li>Step-by-step offline setup: <a href="https://permitcore.dev/docs/offline-licensing">PermitCore offline licensing docs</a></li>
<li>Floating licenses and offline checkouts are covered in <a href="https://permitcore.dev/blog/node-locked-vs-floating-licenses">Node-locked vs. floating licenses</a></li>
</ul>
<p>PermitCore has a permanent Free plan, so you can try signed offline tokens end to end without a card. We're a young startup and still producing content; if you want a topic covered, reach us via Contact Us on permitcore.dev.</p>
<p>How long do you let your customers run offline before asking them to reconnect: days, weeks, or something tied to the plan? I'd love to hear what's worked for you. 🙂</p>
]]></content:encoded></item><item><title><![CDATA[Activating a License on a Device: How Hardware IDs Work]]></title><description><![CDATA[What actually happens when an app calls activate(): hardware fingerprinting, activation limits, and reactivation.]]></description><link>https://permitcore.hashnode.dev/device-activation-hwid</link><guid isPermaLink="true">https://permitcore.hashnode.dev/device-activation-hwid</guid><category><![CDATA[licensing]]></category><category><![CDATA[SaaS]]></category><category><![CDATA[Security]]></category><category><![CDATA[C#]]></category><category><![CDATA[dotnet]]></category><dc:creator><![CDATA[PermitCore]]></dc:creator><pubDate>Mon, 02 Nov 2026 09:28:49 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6adc6d80a6d3c5bbf6194933/f43cc39a-3fa2-465d-b92f-ff48589fe704.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>Originally published on the <a href="https://permitcore.dev/blog/device-activation-hwid">PermitCore blog</a>.</em></p>
<p>You've generated a license key. Now a real customer installs your app, pastes that key, and the app calls <code>activate()</code> for the first time. Here's what actually happens in that call: hardware fingerprinting, activation limits, and why reinstalling on the same machine shouldn't cost a seat.</p>
<h2>Validate vs. Activate: not the same call</h2>
<p>These two are easy to mix up, and they do genuinely different things:</p>
<ul>
<li><strong>Validate</strong> checks that the key is real and still active. It doesn't touch the activation count, so it's safe to call on every app launch.</li>
<li><strong>Activate</strong> does everything Validate does, plus permanently ties the key to a device and increments <code>CurrentActivations</code>. Call it once, the first time a customer installs your app.</li>
</ul>
<h2>What "device" actually means</h2>
<p>For a node-locked license, the SDK needs a stable identifier for the machine it's running on: something that survives reboots and app updates but isn't trivial to spoof. There's no universal API for this across operating systems, so each platform needs its own approach:</p>
<pre><code class="language-csharp">private static string GetHardwareId()
{
    // Windows: motherboard serial + CPU ID, hashed together
    if (OperatingSystem.IsWindows())
    {
        var board = GetWmiValue("Win32_BaseBoard", "SerialNumber");
        var cpu   = GetWmiValue("Win32_Processor", "ProcessorId");
        return Sha256Hex(board + cpu);
    }
    // macOS: IOPlatformUUID
    if (OperatingSystem.IsMacOS())
        return ReadMacOsPlatformUuid();
    // Linux: /etc/machine-id
    return File.ReadAllText("/etc/machine-id").Trim();
}
</code></pre>
<p>All 8 official PermitCore SDKs (.NET, Node.js, Python, Java, PHP, C++, Go, Unity) include a working implementation of this, so you only write it yourself if you're integrating over raw REST.</p>
<h2>Calling Activate</h2>
<pre><code class="language-csharp">var result = await client.ActivateAsync(new ActivateRequest
{
    LicenseKey = enteredKey,
    Version    = "1.0",
    DeviceId   = GetHardwareId(),
    DeviceName = Environment.MachineName
});

if (!result.IsValid)
{
    // result.ErrorCode is one of: NotFound, ProductInactive, Expired, VersionBlocked,
    // IpBlocked, RegionBlocked, VendorSubscriptionLapsed, PoolExhausted, SeatsExhausted, WrongProduct
    ShowActivationError(result.ErrorCode);
    return;
}

SaveLicenseLocally(result);
</code></pre>
<h2>What you see on the admin side</h2>
<p>Every successful activation shows up immediately on the license's detail page: the request's user agent and timestamp, and (when available) a rough geographic location resolved from the request IP. That's exactly what your support team needs when a customer writes "it says I'm out of activations but I only installed it once".</p>
<h2>Reactivating the same device</h2>
<p>Customers reinstall software constantly: OS reinstalls, a new hard drive, a factory reset. PermitCore recognizes when an activation request comes from a hardware ID that's already recorded against that license and treats it as a reactivation, not a new seat. It doesn't consume another slot out of <code>MaxActivations</code>, even if the license is already fully seated.</p>
<p>Why this matters: without that exemption, a customer who reinstalls Windows twice on a 2-seat license would be locked out on the third install, having never used more than one machine at a time. It's a small detail, but it's the difference between licensing that gets out of your way and a support queue full of angry emails.</p>
<h2>Deactivating a device</h2>
<p>When a customer replaces a machine, you (or they, via the self-service Customer Portal) can remove one specific device from a license, freeing that seat for a new activation without touching the other devices on the same key.</p>
<h2>What's next</h2>
<p>Activation assumes your app can reach the license server. The follow-up on the PermitCore blog covers <a href="https://permitcore.dev/blog/offline-grace-mode">offline grace mode</a>: keeping your app working without a connection, and how to stop a customer from editing a local file to stay "licensed" forever.</p>
<p>How do you identify devices in your own apps? Hashed hardware IDs, machine-id, or something else? I'd love to hear what's worked for you.</p>
]]></content:encoded></item><item><title><![CDATA[Offline License Validation in C#: Verifying Signed Tokens Without a Server]]></title><description><![CDATA[How to check an ECDSA-signed license token in .NET with no network call]]></description><link>https://permitcore.hashnode.dev/offline-license-validation-csharp</link><guid isPermaLink="true">https://permitcore.hashnode.dev/offline-license-validation-csharp</guid><category><![CDATA[C#]]></category><category><![CDATA[dotnet]]></category><category><![CDATA[licensing]]></category><category><![CDATA[SaaS]]></category><category><![CDATA[Security]]></category><dc:creator><![CDATA[PermitCore]]></dc:creator><pubDate>Sat, 31 Oct 2026 09:28:23 GMT</pubDate><content:encoded><![CDATA[<p><em>Originally published on the <a href="https://permitcore.dev/blog/offline-license-validation-csharp">PermitCore blog</a>.</em></p>
<p>A factory-floor terminal, a secure workstation with no internet, a laptop that's offline more often than not: apps like these can't call a license server on every launch. The answer is a signed offline token that your app verifies entirely on its own. Here's how that looks in C#, using the real verification method from PermitCore's .NET SDK.</p>
<h2>The token format</h2>
<p>An offline token is a plain string with three dot-separated parts:</p>
<pre><code>pc_offline_v1.&lt;base64url(payload_json)&gt;.&lt;base64url(signature)&gt;
</code></pre>
<p>The prefix identifies the format, so anything malformed is rejected immediately. The payload is a small JSON object (license ID, device ID, expiry, and a key ID <code>kid</code> naming the signing key), and the signature is ECDSA P-256 with SHA-256 over the raw bytes of the base64url-encoded payload. The issuer holds the private signing key; your application only ever needs the trusted public key.</p>
<h2>The verification code</h2>
<p>This is a static method with zero dependencies, so it's easy to audit, or even copy into your own project:</p>
<pre><code class="language-csharp">public static OfflineTokenResult VerifyOfflineToken(string token, string publicKeyBase64)
{
    var parts = token?.Split('.') ?? [];
    if (parts.Length != 3 || parts[0] != "pc_offline_v1")
        return new OfflineTokenResult { IsValid = false, Message = "Malformed token." };

    try
    {
        var payloadBytes = Encoding.UTF8.GetBytes(parts[1]);
        var sig          = FromBase64Url(parts[2]);

        using var ecdsa = ECDsa.Create();
        ecdsa.ImportSubjectPublicKeyInfo(Convert.FromBase64String(publicKeyBase64), out _);
        if (!ecdsa.VerifyData(payloadBytes, sig, HashAlgorithmName.SHA256))
            return new OfflineTokenResult { IsValid = false, Message = "Invalid signature." };

        var json    = Encoding.UTF8.GetString(FromBase64Url(parts[1]));
        var payload = JsonSerializer.Deserialize&lt;OfflineTokenPayload&gt;(json);

        if (payload.ExpiresAt &lt; DateTime.UtcNow)
            return new OfflineTokenResult { IsValid = false, Message = "Token expired.", Payload = payload };

        return new OfflineTokenResult { IsValid = true, Message = "Valid.", Payload = payload };
    }
    catch { return new OfflineTokenResult { IsValid = false, Message = "Invalid or corrupt token." }; }
}
</code></pre>
<p>The order matters: the signature is checked before the expiry date is trusted. Never make a security decision based on data you haven't verified yet. The method matches what the server does when it signs a token, so there's no drift between what gets issued and what gets accepted.</p>
<h2>The full offline flow</h2>
<p>Most apps use two higher-level calls built on that primitive:</p>
<pre><code class="language-csharp">// trustedTenantPublicKey comes from your application configuration.
using var client = new PermitCoreClient(new PermitCoreOptions {
    BaseUrl = apiUrl, TrustedPublicKeys = new[] { trustedTenantPublicKey },
    CacheDirectory = appCacheDirectory, DeviceId = deviceId
});

// First run: verify and retain the signed token.
var result = client.ActivateOffline(token, deviceId, productId);

// Every later launch, with no network call at all
var cached = client.ValidateOffline(deviceId, productId);
if (!cached.IsValid) { /* show activation screen */ }
</code></pre>
<p><code>ActivateOffline</code> also checks that the token's device ID matches the machine calling it, so a correctly signed token issued for one device is rejected when copied onto another. <code>ValidateOffline</code> reads the original signed token and re-verifies its signature, expiry, device and (optionally) product against your configured trust keys.</p>
<h2>What the signature gives you</h2>
<p>Because the contents are signed, nobody can edit the cached JSON on disk to push out the expiry date or swap the device ID: any change breaks the signature. How long a token stays valid between online checks is configured per license with Offline Grace Days, and for store subscriptions offline access is also bounded by the paid-through date plus a configurable payment grace (14 days by default). A successful online check refreshes the signed cache after renewal.</p>
<p>The full guide, including the related video tutorial, is on the <a href="https://permitcore.dev/blog/offline-license-validation-csharp">PermitCore blog</a>, and the <a href="https://permitcore.dev/docs/getting-started">offline setup docs</a> walk through configuring trusted keys.</p>
<p>How do you handle licensing for apps that spend most of their life offline? 🙂</p>
]]></content:encoded></item><item><title><![CDATA[How to Transfer a Software License to a New Computer]]></title><description><![CDATA[The real deactivate-then-reactivate flow that lets a customer move a node-locked license to a new machine themselves, without a support ticket.]]></description><link>https://permitcore.hashnode.dev/how-to-transfer-a-software-license-to-a-new-computer</link><guid isPermaLink="true">https://permitcore.hashnode.dev/how-to-transfer-a-software-license-to-a-new-computer</guid><category><![CDATA[software licensing]]></category><category><![CDATA[SaaS]]></category><category><![CDATA[webdev]]></category><category><![CDATA[GameDev]]></category><category><![CDATA[indiehackers]]></category><dc:creator><![CDATA[PermitCore]]></dc:creator><pubDate>Thu, 29 Oct 2026 09:32:55 GMT</pubDate><content:encoded><![CDATA[<p><em>Originally published on the <a href="https://permitcore.dev/blog/how-to-transfer-a-software-license-to-a-new-computer">PermitCore blog</a>.</em></p>
<p>A customer gets a new laptop. Their node-locked license is bound to the old one. Without a self-service way to move it, this becomes a support ticket every single time — and it happens constantly, since hardware turnover is routine. Here's the real mechanism that avoids that.</p>
<h2>The actual flow: deactivate, then reactivate</h2>
<p>A license's activation limit isn't a one-time allocation — it's a live count of currently-active devices. Freeing a seat is just removing one of those device records. PermitCore's Customer Portal exposes exactly this as a self-service action:</p>
<pre><code class="language-http">POST /portal/{tenantSlug}/deactivate
{ "licenseKey": "PERMIT-XXXX-XXXX-XXXX-XXXX", "activationId": "..." }
</code></pre>
<p>This removes the specific device's activation record and decrements the license's active-device count by one — freeing exactly one seat. The customer then runs your app on their new machine and calls <code>activate()</code> as normal; since a seat is now free, it succeeds and consumes it. No admin involvement, no support ticket, no manual seat-count edit.</p>
<h2>Why this needs real authentication, not just a click</h2>
<p>Removing someone else's active device is a real action — done maliciously, it could kick a legitimate user off their own machine. Depending on how strict a tenant wants their portal, this can require nothing beyond possessing the key itself (fine for most consumer software), a one-time email code sent before the deactivation is allowed, or a full customer account login. All three tiers use the same underlying deactivate mechanism — they differ only in what has to happen before it's allowed to run.</p>
<h2>What if the old machine is gone and can't be reached?</h2>
<p>This is the case a purely device-initiated "sign out" button can't handle — a stolen or dead laptop obviously can't call an API to deactivate itself. That's exactly why deactivation is a <em>server-side</em> action triggered by the customer (via the portal, using the license key or their account — not the old device), not something the old device has to cooperate with.</p>
<h2>What this looks like without self-service</h2>
<p>This flow moves the same customer's activation between devices while the service is available. Moving a license to a different buyer is a separate ownership process. Resale or assignment needs a separately reviewed policy and account process; see our <a href="https://permitcore.dev/blog/software-license-vs-ownership">ownership and end-of-life guide</a>.</p>
<p>The alternative — every transfer needs a support email and a manual admin seat reset — doesn't scale past a handful of customers, and it's the single most common support request a node-locked product with any real customer base will see. Building the self-service path once removes an entire category of recurring support load.</p>
]]></content:encoded></item><item><title><![CDATA[Why MAC Addresses Are Bad Hardware IDs]]></title><description><![CDATA[Spoofable, multi-valued, randomized by modern OSes and unstable in VMs. What works better for node-locked licensing.]]></description><link>https://permitcore.hashnode.dev/why-mac-addresses-are-bad-hardware-ids</link><guid isPermaLink="true">https://permitcore.hashnode.dev/why-mac-addresses-are-bad-hardware-ids</guid><category><![CDATA[licensing]]></category><category><![CDATA[Security]]></category><category><![CDATA[C#]]></category><category><![CDATA[SaaS]]></category><category><![CDATA[GameDev]]></category><dc:creator><![CDATA[PermitCore]]></dc:creator><pubDate>Tue, 27 Oct 2026 09:35:45 GMT</pubDate><content:encoded><![CDATA[<p><em>Originally published on the <a href="https://permitcore.dev/blog/why-mac-addresses-are-bad-hardware-ids">PermitCore blog</a>.</em></p>
<p>A MAC address looks like an obvious choice for a device fingerprint — every network interface has one, it's easy to read, and it feels unique. It's also a genuinely poor foundation for node-locked licensing, for reasons that have nothing to do with theory and everything to do with what real machines actually do.</p>
<h2>The four real problems</h2>
<p><strong>It's trivially spoofable.</strong> Changing a MAC address is a single command on every major OS — <code>ip link set</code> on Linux, a registry edit or free utility on Windows, <code>ifconfig</code> on macOS. A license lock a customer can defeat in ten seconds isn't protecting much.</p>
<p><strong>A machine usually has more than one.</strong> Wi-Fi adapter, Ethernet port, a virtual adapter from a VPN client, a Bluetooth interface — which one is "the" device identifier? Pick the wrong one (or the one that happens to be active at activation time) and the same physical machine can present a different fingerprint on a different day, depending on which network it's connected to.</p>
<p><strong>Modern OSes randomize it by default.</strong> Both iOS/macOS and Android now randomize the MAC address per network by default for privacy — a deliberate, permanent feature, not a bug. A hardware ID that changes every time the device joins a new Wi-Fi network is not a hardware ID.</p>
<p><strong>VMs and containers don't have stable physical hardware to report.</strong> A virtual NIC's MAC is assigned by the hypervisor and is typically the <em>first</em> thing that changes when a VM is cloned, migrated, or recreated — exactly the deployment pattern where you most need a fingerprint that survives infrastructure changes, and exactly where a MAC-based one fails hardest.</p>
<h2>What PermitCore's SDKs actually use instead</h2>
<p>Not a single identifier — a combination, hashed together so no individual component is exposed or trivially replayable. This is the real method from the .NET SDK, unmodified:</p>
<pre><code class="language-csharp">public static string GetHardwareId()
{
    var components = new List&lt;string&gt;
    {
        Environment.MachineName,
        Environment.OSVersion.Platform.ToString(),
        Environment.ProcessorCount.ToString(),
        GetOrCreateSeedFile(),   // a GUID written once, persisted locally
    };
    // + the Windows registry MachineGuid, on Windows

    var combined = string.Join("|", components.Where(c =&gt; !string.IsNullOrEmpty(c)));
    var hash = SHA256.HashData(Encoding.UTF8.GetBytes(combined));
    return Convert.ToHexString(hash).ToLowerInvariant();
}
</code></pre>
<p>Notice what's <em>not</em> in that list: no MAC address, no network-dependent value at all. Machine name and processor count are cheap, stable signals; the seed file (a GUID generated once and written to a local, per-user application-data path) is what actually anchors the fingerprint to this specific install rather than this specific network state — even if the machine changes networks, gets a new NIC, or runs entirely offline, the seed file and the other components stay the same. On Windows, the registry's <code>MachineGuid</code> (set once at OS install, not tied to any network interface) adds another stable signal. The whole combination is SHA-256 hashed, so the server only ever sees an opaque fingerprint, never the raw machine details.</p>
<h2>No hardware ID is unbeatable</h2>
<p>To be honest about the limits: a sufficiently motivated user with full control of their own machine can still spoof a seed file or fake environment values — no client-side fingerprint is unbeatable against someone willing to reverse-engineer your app. The realistic goal isn't "impossible to defeat," it's "significantly more effort than switching a MAC address in Settings," which shifts the population of people who'll actually bother from "everyone" to "a small minority" — combined with server-side activation-limit enforcement and abuse detection, which is where the real protection layer actually lives. See <a href="https://permitcore.dev/blog/prevent-license-key-sharing">How to Prevent License Key Sharing</a> for that side of the picture.</p>
<p>What do you use as a device fingerprint in your own desktop app, and has it ever bitten you after a hardware or OS change? 🙂</p>
]]></content:encoded></item><item><title><![CDATA[What Happens If a License Server Goes Down?]]></title><description><![CDATA[It's a fair question to ask before you depend on any licensing vendor: what happens to your customers' apps during a brief outage, an extended one, or if…]]></description><link>https://permitcore.hashnode.dev/what-happens-if-a-license-server-goes-down</link><guid isPermaLink="true">https://permitcore.hashnode.dev/what-happens-if-a-license-server-goes-down</guid><category><![CDATA[SaaS]]></category><category><![CDATA[licensing]]></category><category><![CDATA[webdev]]></category><category><![CDATA[indiehackers]]></category><category><![CDATA[GameDev]]></category><dc:creator><![CDATA[PermitCore]]></dc:creator><pubDate>Sat, 24 Oct 2026 08:37:06 GMT</pubDate><content:encoded><![CDATA[<p>It's a fair question to ask before you depend on any licensing vendor: what happens to your customers' apps during a brief outage, an extended one, or if the vendor disappears entirely? These are three genuinely different failure modes, and a serious answer treats them separately rather than waving at "high availability" and moving on.</p>
<h2>Layer 1: a brief network blip</h2>
<p>The most common case by far is a flaky connection, not a real outage — a customer's Wi-Fi drops for ten seconds while your app happens to call <code>validate()</code>. This shouldn't be a special case an app has to handle explicitly. With offline grace configured on the license and a usable signed result already cached, PermitCore's SDKs can use that cache on a connection failure until its signed deadline:</p>
<pre><code class="language-csharp">catch (Exception ex) when (ex is HttpRequestException or TaskCanceledException)
{
    // Server unreachable — fall back to the last cached result
    return TryLoadFromCache(licenseKey) ?? new LicenseResult
    {
        IsValid   = false,
        Message   = "Cannot reach license server.",
        IsOffline = true
    };
}
</code></pre>
<p>This is the real code from PermitCore's .NET SDK — a bad network moment does not automatically mean a locked-out customer.</p>
<h2>Layer 2: extended, expected disconnection</h2>
<p>Some apps need to run offline by design — an air-gapped terminal, a laptop that's disconnected more often than not. That's a different problem from a transient blip and needs a different mechanism: a deliberately signed offline token (ECDSA P-256), issued once while online, cached, and verified entirely on-device with no server call for as long as the token's grace period allows. See our <a href="https://permitcore.dev/blog/offline-grace-mode">offline grace mode</a> and <a href="https://permitcore.dev/blog/offline-license-validation-csharp">offline validation</a> posts for exactly how this works and what it honestly can't protect against (a hostile local clock, mainly).</p>
<h2>Layer 3: what if the vendor itself goes away?</h2>
<p>This is the question that actually matters for a purchasing decision, and it's the one vendors talk about least. If a licensing vendor shuts down entirely, what happens to software you've already shipped to customers? Already-issued offline tokens can be verified with a trusted public key shipped in the app until their signed expiry. Token generation currently accepts 1–365 days; a perpetual license does not make its token permanent. Enterprise signing-key export and self-hosting support a separately operated continuity solution, with its own protected keys, backups and tested deployment. Plan for reinstalls and replacement devices too. See our <a href="https://permitcore.dev/blog/software-license-vs-ownership">ownership and shutdown planning guide</a> for a final-build and end-of-life checklist.</p>
<h2>What to actually check before you commit to a vendor</h2>
<p>Ask specifically: does a routine network blip fail closed or gracefully degrade? Is there a real mechanism for extended offline use, not just a marketing claim? And critically — is there any continuity plan if the vendor itself stops operating, or does every customer's license silently stop working the day the company does? The honest answer for most licensing services is "we haven't thought that far," which is worth knowing before you build a business on top of one.</p>
<p>Originally published at <a href="https://permitcore.dev/blog/what-happens-if-a-license-server-goes-down">https://permitcore.dev/blog/what-happens-if-a-license-server-goes-down</a></p>
]]></content:encoded></item></channel></rss>