Documentation · SoftActivate Licensing

Automatic trials

An automatic trial lets anyone who downloads your application try it without asking you for a key. You build one public trial key into the application, and the application activates it on its first run. Each machine then gets its own trial period, 30 days by default, counted from that machine's first activation. The customer types nothing.

The trial key is the key of a trial license: an ordinary license with trial terms, so activation, validation and renewal work as they do for any license. It works with SDK 1.1.0, the current release, so no SDK upgrade is needed.

Creating the trial key

On the product's page (Products, then the product), click Create trial key…. It opens the ordinary order form, pre-filled:

  • the product, quantity 1, and a total of 0;
  • you, the signed-in person, as the order's customer (the order only records it);
  • Send the order email to me instead of the customer, ticked;
  • customized license terms, the trial preset:
    • Duration Counts From: "Each device's first activation";
    • Duration (days): 30;
    • Trial License: on, with no device limit;
    • New Devices per Day: 1,000;
    • Allow Virtual Machines: on;
    • Allow a New Trial on the Same Device After (days): 365;
    • license metadata: the product's own metadata plus "edition": "trial".

You can change any of it before saving, the license terms included, through the usual Customize License step. The fields are explained in products and license terms.

Saving creates the order and the trial license. The trial key arrives by email, sent to you, with two extra lines: "This is a trial key: build it into your application." and "Each device that activates a trial key gets its own trial period, counted from its first activation." The console never displays the key, because keys are stored only as hashes, so keep that email.

Building it into your application

At every start, the application picks its key: the purchased key once the customer has bought, the built-in trial key until then. The rest is the usual validation call (activation and validation). On the first run there is no stored license, so the call activates the trial key and this machine's trial begins. Save the updated license whenever the SDK issues one, as for any license.

C++

#include "softactivate/sws_licensing.h"
#include <ctime>
#include <fstream>
#include <memory>
#include <sstream>
#include <string>

// built into the application: the product's trial key (emailed to you when you created it)
const char* TRIAL_KEY = "XXXXX-XXXXX-XXXXX-XXXXX-XXXXX";

// a whole file as a string; "" when there is none
auto read_file = [](const char* path) {
    std::ifstream file(path);
    std::stringstream buffer;
    buffer << file.rdbuf();
    return buffer.str();
};

auto client = sws::licensing::client::create(endpointUrl);
client->set_metadata("trusted_domain", trustedDomain);
client->set_vendor_id(vendorId);
client->set_product_id("YOUR_PRODUCT_ID");

// the purchased key, saved when the customer buys; empty until then
std::string purchased = read_file("purchased-key.txt");
bool trial = purchased.empty();
client->set_license_key(trial ? TRIAL_KEY : purchased.c_str());

// the stored license: none on the first run, or after a reinstall
auto params = client->create_license_validation_params();
std::unique_ptr<sws::licensing::license> stored;
std::string stored_data = read_file("license.dat");
if (!stored_data.empty())
{
    stored = std::make_unique<sws::licensing::license>(stored_data.c_str());
    params->set_license(stored);
}

// with no stored license, this starts this machine's trial, or resumes it
auto result = client->validate_license(params);

auto updated = result->get_updated_license();
if (updated)
    std::ofstream("license.dat") << updated->serialize();

int status = result->get_status();

if (status == SWS_LICENSE_STATUS_VALID)
{
    auto& current = updated ? updated : stored;

    // the trial preset puts "edition": "trial" in the license metadata
    const char* edition = current->get_license_metadata_value("edition");

    if (edition && std::string(edition) == "trial")
    {
        // a trial license expires at the last moment of this machine's trial
        long long days_left = (current->get_expiration_date() - std::time(nullptr)) / 86400;
        // show "N days left in your trial" and a Buy button
    }
}
else if (trial && (status == SWS_LICENSE_STATUS_EXPIRED ||
                   status == SWS_LICENSE_STATUS_REVOKED))
{
    // this machine's trial is over (EXPIRED), or you stopped all trials by revoking
    // the trial license (REVOKED): show your purchase page
}
else
{
    // as for any license (statuses reference); on a first run, UNAVAILABLE with the
    // error code "new_device_limit" means: try again after midnight UTC
}

// on purchase: activate the purchased key with no stored license first; only when that
// succeeds, save the key and write its license over license.dat (see "Buying")

C#

using System;
using System.IO;
using SWS.Licensing;

// built into the application: the product's trial key (emailed to you when you created it)
const string TrialKey = "XXXXX-XXXXX-XXXXX-XXXXX-XXXXX";

using var client = new LicensingClient(endpointUrl)
{
    VendorId = vendorId,
    TrustedDomain = trustedDomain
};
client.SetProductId("YOUR_PRODUCT_ID");

// the purchased key, saved when the customer buys; empty until then
string purchased = File.Exists("purchased-key.txt")
    ? File.ReadAllText("purchased-key.txt").Trim()
    : "";
bool trial = purchased.Length == 0;
client.LicenseKey = trial ? TrialKey : purchased;

// the stored license: none on the first run, or after a reinstall
License? stored = File.Exists("license.dat")
    ? License.Parse(File.ReadAllText("license.dat"))
    : null;

// with no stored license, this starts this machine's trial, or resumes it
LicenseValidationResult result = await client.ValidateLicenseAsync(stored);

if (result.UpdatedLicense != null)
    File.WriteAllText("license.dat", result.UpdatedLicense.Serialize());

if (result.IsValid)
{
    License current = result.UpdatedLicense ?? stored!;

    // the trial preset puts "edition": "trial" in the license metadata
    if (current.GetMetadataValue("edition") == "trial")
    {
        // a trial license expires at the last moment of this machine's trial
        int daysLeft = (int)(current.ExpirationDate - DateTimeOffset.UtcNow).TotalDays;
        // show "N days left in your trial" and a Buy button
    }
}
else if (trial && (result.Status == LicenseStatus.Expired ||
                   result.Status == LicenseStatus.Revoked))
{
    // this machine's trial is over (Expired), or you stopped all trials by revoking
    // the trial license (Revoked): show your purchase page
}
else
{
    // as for any license (statuses reference); on a first run, Unavailable with the
    // error code "new_device_limit" means: try again after midnight UTC
}

// on purchase: activate the purchased key with no stored license first; only when that
// succeeds, save the key and write its license over license.dat (see "Buying")

VB.NET

Imports System
Imports System.IO
Imports SWS.Licensing

' built into the application: the product's trial key (emailed to you when you created it)
Const TrialKey As String = "XXXXX-XXXXX-XXXXX-XXXXX-XXXXX"

Using client As New LicensingClient(endpointUrl) With {
    .VendorId = vendorId,
    .TrustedDomain = trustedDomain
}
    client.SetProductId("YOUR_PRODUCT_ID")

    ' the purchased key, saved when the customer buys; empty until then
    Dim purchased As String = If(File.Exists("purchased-key.txt"),
                                 File.ReadAllText("purchased-key.txt").Trim(), "")
    Dim trial As Boolean = (purchased.Length = 0)
    client.LicenseKey = If(trial, TrialKey, purchased)

    ' the stored license: none on the first run, or after a reinstall
    Dim stored As License = Nothing
    If File.Exists("license.dat") Then
        stored = License.Parse(File.ReadAllText("license.dat"))
    End If

    ' with no stored license, this starts this machine's trial, or resumes it
    Dim result As LicenseValidationResult = Await client.ValidateLicenseAsync(stored)

    If result.UpdatedLicense IsNot Nothing Then
        File.WriteAllText("license.dat", result.UpdatedLicense.Serialize())
    End If

    If result.IsValid Then
        Dim current As License = If(result.UpdatedLicense, stored)

        ' the trial preset puts "edition": "trial" in the license metadata
        If current.GetMetadataValue("edition") = "trial" Then
            ' a trial license expires at the last moment of this machine's trial
            Dim daysLeft As Integer = CInt(Math.Floor(
                (current.ExpirationDate - DateTimeOffset.UtcNow).TotalDays))
            ' show "N days left in your trial" and a Buy button
        End If
    ElseIf trial AndAlso (result.Status = LicenseStatus.Expired OrElse
                          result.Status = LicenseStatus.Revoked) Then
        ' this machine's trial is over (Expired), or you stopped all trials by revoking
        ' the trial license (Revoked): show your purchase page
    Else
        ' as for any license (statuses reference); on a first run, Unavailable with the
        ' error code "new_device_limit" means: try again after midnight UTC
    End If
End Using

' on purchase: activate the purchased key with no stored license first; only when that
' succeeds, save the key and write its license over license.dat (see "Buying")
  • Days left. The certificate the SDK receives ends exactly at this machine's trial end, so the license's expiration date is the trial's last moment: get_expiration_date() (unix seconds) in C++, License.ExpirationDate in .NET.
  • Trial or purchased license. With SDK 1.1.0, the license metadata tells them apart: the trial preset sets "edition": "trial". Read it with get_license_metadata_value("edition") in C++, and with License.GetMetadataValue("edition") or the whole License.Metadata in .NET (product binding). Trial certificates also carry a trial marker, which SDK 1.1.0 does not read.
  • Online once. The first run needs a connection, as every activation does. After that, the trial validates on the machine itself, offline included, until its end.

What your customer sees

  • The first run: nothing to type. The application activates the built-in trial key, and the trial starts.
  • During the trial: the application validates as with any license, and can show the days left.
  • At the end: validation returns EXPIRED on this machine. Show your purchase page. Other machines are unaffected: each one's trial ends on its own date.
  • After buying: the purchased key replaces the trial key (buying).

Following trials in the console

The trial license's page lists every device that started a trial, with when its trial started and when it ends (licenses). From there you can:

  • Extend one device's trial. Extend… on the device's row adds 1 to 365 days to that device's current trial, an ended one included, and the row shows "+N days". The device picks up its new end at its next contact with the service, and at the latest when its current certificate expires and the SDK renews it. Other devices are unaffected. There is no "end now" for a single device: a trial certificate keeps validating offline until its own end. To stop every trial, revoke the trial license (stopping trials).
  • See which machines bought. When a machine in a trial later activates a purchased license of the same product, its row on the trial license reads converted, and its tooltip names the purchased license. Conversions are recorded for the product's ten newest trial licenses.

Elsewhere, trial activity stays out of the way: the Notifications page shows it only when Show trial activity is switched on, and the dashboard shows trial starts as a figure of their own under Activations (7d), when there were any. The Trial Funnel report, on the Reports page, counts trial starts and conversions over a period, day by day, with the conversion rate (reports).

Webhooks

If your systems receive the console's outbound webhooks (Settings → Webhooks), two events follow trials:

  • trial.started: a device began a trial. Its data carries license_id (the trial license), sku_id, device_id, started_at, and ends_at.
  • trial.converted: a machine in a trial activated a purchased license of the product. Its data carries trial_license_id, license_id (the purchased license), sku_id, device_id, and converted_at.

device_id is the licensing service's id for the machine: the same machine has the same id in both events.

Reinstalls, hardware changes, and virtual machines

A machine's trial period starts at its first activation and ends Duration days later. If you set a Valid To date on the trial license, no trial runs past it: on a trial license, Valid To reads as "the last day any trial may run".

What keeps that end, and what starts a new trial:

  • Repeat runs and renewals keep the same end.
  • Reinstalling your application, which deletes the stored license file, keeps the same end: the next start activates again and gets the same trial back.
  • Deactivating and then activating again keeps the same end, because a deactivation no longer makes the service forget the machine (hardware IDs).
  • A tolerated hardware change, such as a replaced network card, is the same machine.
  • A different machine id is a new device, with its own trial.

A trial is therefore as strong as the machine id:

Platform Machine id
Windows The SMBIOS UUID, which survives an operating-system reinstall.
Linux /etc/machine-id, which an operating-system reinstall regenerates and root can edit.
macOS The hardware UUID.
AWS and GCP instances The instance id.

So a new virtual machine, a copied virtual machine given a new UUID, a new cloud instance, or a Linux user who edits /etc/machine-id gets a fresh trial. Other licensing vendors document the same class of limit.

To keep virtual machines out of your trials, untick Allow Virtual Machines in the trial license's terms. A new device that reports running in a virtual machine then cannot start a trial: the service answers HTTP 403 vm_not_allowed. Devices already in their trial keep it. The report comes from the machine's SMBIOS manufacturer and model strings, where hypervisors and cloud platforms put their own names; a physical PC that uses virtualization-based security does not count as a virtual machine. SDK 1.2.0 and later send this report. SDK 1.1.0 does not, so devices running it are never refused.

Setting the device clock back keeps a time-limited certificate, a trial certificate included, validating within its window while the device stays offline, as for any license. Clock-rollback detection is not offered yet.

Limits and abuse bounds

The trial key is public: anyone who has your application has the key. All it buys is a trial certificate for the machine whose fingerprint is presented, and the SDK checks that fingerprint against the real machine at every validation, so a certificate requested for an invented machine is useless on a real one. Mass requests are bounded by the activation service's limit per IP address (60 requests a minute), by the trial license's daily cap on new devices (New Devices per Day), and by monitoring.

Both bounds can be changed on a trial license that is already in use: open its page, click Edit, and change New Devices per Day or Allow Virtual Machines. A change applies from the next new device on.

Trial devices are not active devices. They do not appear in the active-device count, are never billed, and do not count toward your plan's device limit (plan and billing). A flood of requests therefore costs you nothing you pay for. The worst it can do is use up a day's cap: new machines then get "try later" until midnight UTC (HTTP 429 new_device_limit, which SDK 1.1.0 reports as UNAVAILABLE). Renewals and returning devices are never capped, so machines already in their trial carry on.

Your own SoftActivate Licensing plan matters in one case. When your account's own 30-day trial has ended without a subscription, no new trial device starts (HTTP 402 payment_required with the reason trial_expired, reported as PAYMENT_REQUIRED), exactly as no new device of any license activates. Devices already in their trial keep renewing.

Weak fingerprints

A device whose hardware fingerprint is too weak to recognise again is refused with HTTP 403 device_unidentifiable, which SDK 1.1.0 reports as NOT_FOUND. The usual case is a .NET application published with Native AOT, running on a Windows machine with fewer than three network adapters: under Native AOT, the fingerprint is built from MAC addresses only. Both SDKs already refused to validate a license locally on such a device. Publish trial builds without Native AOT until the SDK fix lands; it is planned for SDK 1.2.0, which will read the SMBIOS UUID under Native AOT (publishing your app).

Buying

When the customer buys, they enter the purchased key. Activate it before changing anything on disk, so that a mistyped key never costs the customer a working trial:

  1. Set up a client as at startup, but with the purchased key, and validate with no stored license. That activates the purchased key on this machine.
  2. Only if the status is VALID, save the purchased key and write the returned license over the stored trial license.
  3. Otherwise, leave the trial license as it is and tell the customer why. A mistyped key gives NOT_FOUND with the error code license_not_found, and the error code names any other cause (why an activation failed).

The trial license must not stay on disk as the stored license for the purchased key. The SDK validates a stored license only under the key it was issued for, so the trial license under the purchased key gives BAD_CONTEXT, without asking the service. Writing the purchased license over it on success takes care of that.

C++

#include "softactivate/sws_licensing.h"
#include <fstream>
#include <string>

// the customer bought and entered the key from their purchase email (customerLicenseKey):
// a client set up as at startup, but with the purchased key
auto client = sws::licensing::client::create(endpointUrl);
client->set_metadata("trusted_domain", trustedDomain);
client->set_vendor_id(vendorId);
client->set_product_id("YOUR_PRODUCT_ID");
client->set_license_key(customerLicenseKey);

// validate with NO stored license: this activates the purchased key on this machine.
// Never pass the trial license here: the SDK validates a stored license only under the
// key it was issued for, so the trial license would give SWS_LICENSE_STATUS_BAD_CONTEXT.
auto params = client->create_license_validation_params();
auto result = client->validate_license(params);
auto purchased = result->get_updated_license();

if (result->get_status() == SWS_LICENSE_STATUS_VALID && purchased)
{
    // only now: keep the purchased key, and write its license over the trial license
    std::ofstream("purchased-key.txt") << customerLicenseKey;
    std::ofstream("license.dat") << purchased->serialize();
}
else
{
    // nothing on disk changed, so the trial goes on; tell the customer why, from the
    // error code: "license_not_found" is a mistyped key, "network" means no connection
    std::string code = result->get_error_code();
}

C#

using System.IO;
using SWS.Licensing;

// the customer bought and entered the key from their purchase email (customerLicenseKey):
// a client set up as at startup, but with the purchased key
using var client = new LicensingClient(endpointUrl)
{
    VendorId = vendorId,
    TrustedDomain = trustedDomain,
    LicenseKey = customerLicenseKey
};
client.SetProductId("YOUR_PRODUCT_ID");

// validate with NO stored license: this activates the purchased key on this machine.
// Never pass the trial license here: the SDK validates a stored license only under the
// key it was issued for, so the trial license would give LicenseStatus.BadContext.
LicenseValidationResult result = await client.ValidateLicenseAsync(null);

if (result.IsValid && result.UpdatedLicense != null)
{
    // only now: keep the purchased key, and write its license over the trial license
    File.WriteAllText("purchased-key.txt", customerLicenseKey);
    File.WriteAllText("license.dat", result.UpdatedLicense.Serialize());
}
else
{
    // nothing on disk changed, so the trial goes on; tell the customer why, from the
    // error code: "license_not_found" is a mistyped key, "network" means no connection
    string? code = result.Error?.Code;
}

VB.NET

Imports System.IO
Imports SWS.Licensing

' the customer bought and entered the key from their purchase email (customerLicenseKey):
' a client set up as at startup, but with the purchased key
Using client As New LicensingClient(endpointUrl) With {
    .VendorId = vendorId,
    .TrustedDomain = trustedDomain,
    .LicenseKey = customerLicenseKey
}
    client.SetProductId("YOUR_PRODUCT_ID")

    ' validate with NO stored license: this activates the purchased key on this machine.
    ' Never pass the trial license here: the SDK validates a stored license only under the
    ' key it was issued for, so the trial license would give LicenseStatus.BadContext.
    Dim result As LicenseValidationResult = Await client.ValidateLicenseAsync(Nothing)

    If result.IsValid AndAlso result.UpdatedLicense IsNot Nothing Then
        ' only now: keep the purchased key, and write its license over the trial license
        File.WriteAllText("purchased-key.txt", customerLicenseKey)
        File.WriteAllText("license.dat", result.UpdatedLicense.Serialize())
    Else
        ' nothing on disk changed, so the trial goes on; tell the customer why, from the
        ' error code: "license_not_found" is a mistyped key, "network" means no connection
        Dim code As String = result.Error?.Code
    End If
End Using

Stopping trials

To stop all trials at once, Revoke the trial license on its license page. From that moment no new device starts a trial and no device renews: the service answers HTTP 403 license_revoked, which the SDK reports as REVOKED. Trial certificates are never put on the revocation list, because a trial license may have tens of thousands of devices, so a trial already running keeps validating until its own end at the latest (revocation). The startup code above shows the purchase page for REVOKED, as it does for EXPIRED.

For a planned end, set the trial license's Valid To date instead. No trial runs past it, including the ones already running.

A new trial for a new major version

Create a new trial license (Create trial key… again) and build its key into the new version: every machine gets a fresh trial of that version. The flip side is that a trial key cannot be rotated without resetting trials. That is fine, because the key is public anyway.

Within one trial license, a machine may start a new trial period once its last one ended more than Allow a New Trial on the Same Device After (days) ago: 365 days in the preset, and never when the field is 0 or empty.

Statuses you may see

On top of VALID, a trial brings these statuses. The error code comes from get_error_code() in C++ or result.Error.Code in .NET (why an activation failed).

  • EXPIRED: this machine's trial is over (HTTP 403 license_expired, whose expired_at field gives the end). Show your purchase page.
  • UNAVAILABLE with the error code new_device_limit: the trial license's daily cap on new devices is reached (HTTP 429). The machine can start its trial after midnight UTC, so ask the customer to try again later.
  • NOT_FOUND with the error code device_unidentifiable: this device's fingerprint is too weak to recognise again (weak fingerprints).
  • NOT_FOUND with the error code vm_not_allowed: the trial license does not allow virtual machines, and this new device reported running in one (virtual machines). Only SDK 1.2.0 and later can get this status.
  • REVOKED: you revoked the trial license (stopping trials).

Every other status means what it means for any license; see the statuses and errors reference.