Integrate PermitCore with Your .NET Application in 15 Minutes
PermitCore exposes a simple REST API for license validation and activation, which means you can integrate it with any HTTP client. But for .NET applications, we've put together a first-party client that handles connection management, retry logic, and error mapping so you don't have to.
This tutorial walks from zero to a working integration in a .NET 10 application. All code examples are production-ready and handle the edge cases (offline scenarios, expired keys, activation limits) that the happy path often glosses over.
Prerequisites
You'll need a PermitCore account, at least one product created, and a generated license key. The API endpoint to use is shown in your dashboard under Settings → API. For this tutorial, we'll use https://api.permitcore.dev as the base URL.
Step 1: Configure the HTTP Client
PermitCore's public API requires no authentication for validation and activation — the license key itself is the credential. For admin operations (generating keys, listing licenses), you'll use a Bearer token from your PermitCore account.
// appsettings.json { "PermitCore": { "BaseUrl": "https://api.permitcore.dev", "TimeoutSeconds": 5 } }
// Program.cs builder.Services.AddHttpClient("permitcore", client => { client.BaseAddress = new Uri(config["PermitCore:BaseUrl"]!); client.Timeout = TimeSpan.FromSeconds( config.GetValue<int>("PermitCore:TimeoutSeconds", 5)); client.DefaultRequestHeaders.Add("User-Agent", "MyApp/1.0"); });
Step 2: Validate a License Key
Validation confirms the key exists and is active, without consuming an activation slot. Call this on every application start to check whether the license is still valid.
public record ValidateRequest(string LicenseKey, string Version); public record ValidateResult(bool Valid, string? Message, DateTime? ExpiresAt); public class LicenseService(IHttpClientFactory factory) { public async Task<ValidateResult> ValidateAsync(string key) { var client = factory.CreateClient("permitcore"); var payload = new ValidateRequest(key, "1.0"); try { var response = await client.PostAsJsonAsync( "/api/v1/validate", payload); if (!response.IsSuccessStatusCode) return new ValidateResult(false, "License server error", null); return await response.Content .ReadFromJsonAsync<ValidateResult>() ?? new ValidateResult(false, "Empty response", null); } catch (Exception ex) when (ex is HttpRequestException or TaskCanceledException) { // Network error — fall through to offline grace return await CheckOfflineGraceAsync(key); } } }
Step 3: Activate on First Run
Activation ties the license to a machine and increments the activation counter. Call this once when the user first installs your application, not on every launch. Store the activation result locally so subsequent launches only need to validate.
public async Task<ValidateResult> ActivateAsync(string key) { var client = factory.CreateClient("permitcore"); var payload = new { LicenseKey = key, Version = "1.0", MachineId = GetMachineId() // see note below }; var response = await client.PostAsJsonAsync( "/api/v1/activate", payload); if (response.StatusCode == HttpStatusCode.Conflict) return new ValidateResult(false, "Activation limit reached for this license", null); var result = await response.Content .ReadFromJsonAsync<ValidateResult>(); if (result?.Valid == true) await StoreActivationLocallyAsync(key, result); return result ?? new ValidateResult(false, "Activation failed", null); }
Step 4: Machine Fingerprinting
Node-locked licenses need a stable machine identifier. The key properties: it must be stable across reboots, not change when the user installs updates, and not be trivially spoofable. On Windows, the motherboard serial number plus CPU ID is a reasonable starting point:
private static string GetMachineId() { // Windows: use WMI motherboard + CPU serial if (OperatingSystem.IsWindows()) { var board = GetWmiValue( "Win32_BaseBoard", "SerialNumber"); var cpu = GetWmiValue( "Win32_Processor", "ProcessorId"); return SHA256Hash(board + cpu); } // macOS: IOPlatformUUID if (OperatingSystem.IsMacOS()) return ReadMacOsUuid(); // Linux: /etc/machine-id return File.ReadAllText("/etc/machine-id").Trim(); }
Step 5: Offline Grace Period
Never hard-block your application when it can't reach the license server. Network outages, VPN misconfigurations, and corporate firewalls are all real scenarios your customers will encounter. A reasonable grace period is 7 days — enough to cover a vacation without enabling serial offline abuse:
private static readonly TimeSpan GracePeriod = TimeSpan.FromDays(7); private async Task<ValidateResult> CheckOfflineGraceAsync(string key) { var stored = await LoadActivationLocallyAsync(key); if (stored == null) return new ValidateResult(false, "No cached license. Connect to the internet to activate.", null); var lastOnline = stored.LastOnlineCheck; var offlineDuration = DateTime.UtcNow - lastOnline; if (offlineDuration <= GracePeriod) return new ValidateResult(true, $"Offline — {(int)(GracePeriod - offlineDuration).TotalDays}d grace remaining", stored.ExpiresAt); return new ValidateResult(false, "Offline grace period expired. Connect to the internet to continue.", null); }
Step 6: Wiring It Up
Register LicenseService as a scoped service and inject it into your startup sequence. The pattern below checks license status before showing the main window in a WPF or MAUI application:
// App.xaml.cs (WPF) protected override async void OnStartup(StartupEventArgs e) { var key = await _licenseStore.GetStoredKeyAsync(); if (key == null) { new ActivationWindow().Show(); return; } var result = await _licenseService.ValidateAsync(key); if (!result.Valid) { MessageBox.Show(result.Message ?? "License invalid."); new ActivationWindow().Show(); return; } new MainWindow().Show(); }
Error Handling Reference
The PermitCore API returns standard HTTP status codes. Map them in your client:
Testing Your Integration
PermitCore does not have a separate sandbox environment — instead, use your real account with test licenses you've marked as such. Create a product called "Test Product" and issue keys against it. Since validation uses a POST body rather than a query parameter, you can exercise the full flow against your real API without risk of keys leaking into logs.
For unit tests, mock IHttpClientFactory to return a handler that returns a pre-configured ValidateResult. Your business logic should depend on the LicenseService interface, not the HTTP client directly, which makes injection straightforward.
What's Next
This covers the core validate/activate flow for a node-locked perpetual or subscription license. For floating licenses (concurrent seat management), metered billing, or webhook-based expiry notifications, the PermitCore documentation covers each pattern with dedicated endpoint references.
The full source for a working .NET integration sample — including the machine fingerprinting helpers and a WPF activation dialog — is available in the SDK examples section of the docs.