Activation and validation
The whole integration is a single call, validate_license / ValidateLicenseAsync,
which behaves differently depending on what you hand it. This page covers when to
call it and what to do with the result.
Day 0: activation
Call validate with no stored license. The SDK registers this device with the
service; this first call must be online. The service returns a license, which you
retrieve with get_updated_license() / result.UpdatedLicense. Persist it
atomically, and consider keeping a backup copy: a lost license file costs your
customer a re-activation, which holds a seat until the old one is deactivated.
A few first-activation failures deserve distinct handling in your UI: all seats
already in use on a standard license (point the customer at deactivating the old
machine), SEAT_LIMIT on a floating license
(the error carries a retry hint), PAYMENT_REQUIRED on a lapsed
subscription, and plain network failure, where the
fix is to retry later; nothing else is wrong.
Every start: local validation
Load the stored license, set it on the validation parameters, and call validate.
While the license is current this needs no network and returns VALID in
milliseconds; only in the second half of the license's window does the call also
renew it online (maximum offline period). Two rules keep
the rest of the lifecycle automatic:
- Always save the updated license when the SDK issues one. That is how renewals reach disk.
- Keep validating even after a failure. Renewal is self-healing: the moment the customer pays, or connectivity returns, the next validation call fixes it.
Renewal is silent
When a license lapses, the next validation call contacts the service once and returns a license reflecting whatever the customer has paid for by then. A subscription renewal needs no code on your part (subscriptions guide).
Choosing the maximum offline period
A license certificate is valid for a window, and that window is the re-verification
period. During the second half of the window, each validate call also re-verifies
(renews) the license with the service; a window that already ends with the license
itself, at the end of a trial or a fixed term, renews once it has ended instead. If the
service cannot be reached, the license stays VALID, and the next call tries again. A device that does not reach the service
for the whole window gets EXPIRED until it reconnects; the next validate with
connectivity renews the license automatically.
On non-floating licenses, this window is the Maximum offline period, set in the console on the license template or the product (products and license terms). Empty means the default of one year, and the minimum is one day. On floating licenses the same field is the Lease duration, with a minimum of 15 minutes (floating licenses).
A longer period lets customers stay offline longer. A shorter one makes revocations take effect sooner: a revocation reaches an offline device when its window ends, at the latest, while an online device sees it at its next start, through the revocation list (revocation). Re-verifying is not an activation, so a short period costs no activations: renewals never count toward Total activations or Activations per device, on any license type.
If you are moving from LimeLM (TurboActivate), nDaysBetweenChecks = 30 plus
nGraceDaysOnInetErr = 10 corresponds to a 40-day maximum offline period: the device
re-verifies from day 20 and stops validating at day 40 without contact.
The license's end and the end of the window
A license reports two dates (SDK 1.4.0 and later):
- The expiration date, when the license itself ends: a fixed term's end date, a
subscription's paid-through date plus its grace period, or this machine's trial end.
get_expiration_date()(unix seconds) in C++,License.ExpirationDatein .NET. A license that never ends returnsSWS_LICENSE_NO_EXPIRATIONin C++ andLicense.NoExpirationin .NET, the last second of the year 9999, so a "days left" figure computed from it is never negative. - The offline expiration date, the end of the current window: until when the
license keeps validating without reaching the service.
get_offline_expiration_date()(unix seconds) in C++,License.OfflineExpirationDatein .NET. It is never later than the expiration date.
Warning before the period runs out
When the time left offline is short and the last validation could not reach the
service, ask the user to connect to the internet before the license stops
validating. The time left is the offline expiration date minus now:
get_offline_expiration_date() (unix seconds) in C++, License.OfflineExpirationDate
in .NET. A renewal attempt that got no answer at all leaves the status VALID and reports the
error code network or timeout, with HTTP status 0 (get_error_code() and
get_error_http_status(); .NET result.Error):
C++
#include "softactivate/sws_licensing.h"
#include <ctime>
#include <fstream>
#include <sstream>
#include <string>
auto client = sws::licensing::client::create(endpointUrl);
client->set_metadata("trusted_domain", trustedDomain);
client->set_vendor_id(vendorId);
client->set_license_key(customerLicenseKey);
// the license saved at activation
std::ifstream file("license.dat");
std::stringstream buffer;
buffer << file.rdbuf();
auto stored = std::make_unique<sws::licensing::license>(buffer.str().c_str());
auto params = client->create_license_validation_params();
params->set_license(stored);
auto result = client->validate_license(params);
if (auto renewed = result->get_updated_license())
{
// re-verified: a new window starts - persist it as usual
std::ofstream("license.dat") << renewed->serialize();
}
else if (result->get_status() == SWS_LICENSE_STATUS_VALID)
{
// in the second half of the window, validation also tries to renew;
// "network" or "timeout" (HTTP status 0) means that attempt got no answer
std::string code = result->get_error_code();
bool unreachable = code == "network" || code == "timeout";
long long seconds_left = stored->get_offline_expiration_date() - std::time(nullptr);
if (unreachable && seconds_left < 7 * 24 * 60 * 60)
{
// ask the user to connect to the internet before the license stops
// validating, e.g. "Please connect within N days to keep working"
}
}
C#
using System;
using System.IO;
using SWS.Licensing;
var client = new LicensingClient(endpointUrl)
{
VendorId = vendorId,
LicenseKey = customerLicenseKey,
TrustedDomain = trustedDomain
};
// the license saved at activation
License stored = License.Parse(File.ReadAllText("license.dat"));
LicenseValidationResult result = await client.ValidateLicenseAsync(stored);
if (result.UpdatedLicense != null)
{
// re-verified: a new window starts - persist it as usual
File.WriteAllText("license.dat", result.UpdatedLicense.Serialize());
}
else if (result.IsValid)
{
// in the second half of the window, validation also tries to renew;
// "network" or "timeout" (HttpStatus 0) means that attempt got no answer
string? code = result.Error?.Code;
bool unreachable = code == "network" || code == "timeout";
TimeSpan remaining = stored.OfflineExpirationDate - DateTimeOffset.UtcNow;
if (unreachable && remaining < TimeSpan.FromDays(7))
{
// ask the user to connect to the internet before the license stops
// validating, e.g. "Please connect within N days to keep working"
}
}
VB.NET
Imports System
Imports System.IO
Imports SWS.Licensing
Dim client As New LicensingClient(endpointUrl) With {
.VendorId = vendorId,
.LicenseKey = customerLicenseKey,
.TrustedDomain = trustedDomain
}
' the license saved at activation
Dim stored As License = License.Parse(File.ReadAllText("license.dat"))
Dim result As LicenseValidationResult = Await client.ValidateLicenseAsync(stored)
If result.UpdatedLicense IsNot Nothing Then
' re-verified: a new window starts - persist it as usual
File.WriteAllText("license.dat", result.UpdatedLicense.Serialize())
ElseIf result.IsValid Then
' in the second half of the window, validation also tries to renew;
' "network" or "timeout" (HttpStatus 0) means that attempt got no answer
Dim code As String = result.Error?.Code
Dim unreachable As Boolean = (code = "network" OrElse code = "timeout")
Dim remaining As TimeSpan = stored.OfflineExpirationDate - DateTimeOffset.UtcNow
If unreachable AndAlso remaining < TimeSpan.FromDays(7) Then
' ask the user to connect to the internet before the license stops
' validating, e.g. "Please connect within N days to keep working"
End If
End If
Clock skew and manipulation
Validation relies on the local clock. Winding the clock back buys a limited amount of
time at most, because periodic renewal bounds the damage automatically, so you do not
need your own clock heuristics unless your threat model calls for them. Setting the clock
forward and then correcting it can produce NOT_YET_VALID; when that happens, tell
the customer to fix their clock.
Verify the product
If you sell more than one product, declare which one this application is before
validating: client->set_product_id("PRO_EDITION") (.NET
client.SetProductId("PRO_EDITION")), using the product's Reference Id from the
console's Products page. Two things then hold without further code: a key sold for a
different product is refused at activation, before it consumes a seat, and a stored
license for a different product fails validation with BAD_PRODUCT. Skip the call
and any license of your account validates in any of your apps, which is fine while you sell
one product, not once you sell two. The full contract, including reading the
license's product and metadata back out, is the
product binding guide.
Statuses that deserve their own handling
A few statuses deserve handling distinct from a generic error:
EXPIRED: apply your grace policy, and keep validating.PAYMENT_REQUIRED: your dunning moment. Show the subscription state and a link to renewal, not a generic error.BAD_CONTEXT: the license is bound to a different machine. Offer the move flow.BAD_PRODUCT: the key belongs to a different product of yours. Tell the customer which product it matches (it is on their receipt) and where to get the right one.REVOKED: terminal. Direct the customer to support.BAD_SIGNATURE: a damaged license file. Delete it and re-activate.
The full table with recommended actions is the statuses reference.
Threading
Create one licensing client per thread, or serialize access to a shared one. The client object is not thread-safe.