Documentation · SoftActivate Licensing

Subscriptions

A subscription license follows the normal lifecycle with one difference: the license stays valid while the subscription is paid, plus a grace period, so payment state expresses itself entirely through validation results. There is no billing API to integrate in your application.

How the renewal loop works

The customer activates, and the app validates offline on every start. As the paid-through date approaches, the stored license nears the end of its validity. The next validation renews it silently, extending the license by whatever period the customer has paid for. If the customer stops paying, validation answers PAYMENT_REQUIRED. The moment they pay again, the next validation recovers automatically. Renewal needs no code; your only job is handling the lapsed state well.

Handling PAYMENT_REQUIRED

The status message carries the subscription state. Treat the states differently, rather than as one generic error:

C++

#include "softactivate/sws_licensing.h"
#include <iostream>
#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);

auto params = client->create_license_validation_params();
auto result = client->validate_license(params);

if (!result->is_license_valid() &&
    result->get_status() == SWS_LICENSE_STATUS_PAYMENT_REQUIRED)
{
    // the message carries the subscription state - each deserves its own UX
    std::string state = result->get_status_message();

    if (state == "past_due")
    {
        // a payment failed but the subscription is still recoverable:
        // "please update your payment method" + a link to your portal
    }
    else if (state == "canceled")
    {
        // the customer ended the subscription: a win-back conversation,
        // not an error dialog
    }
}

C#

using SWS.Licensing;

var client = new LicensingClient(endpointUrl)
{
    VendorId = vendorId,
    LicenseKey = customerLicenseKey,
    TrustedDomain = trustedDomain
};

LicenseValidationResult result = await client.ValidateLicenseAsync(null);

if (!result.IsValid && result.Status == LicenseStatus.PaymentRequired)
{
    // the message carries the subscription state - each deserves its own UX
    switch (result.StatusMessage)
    {
        case "past_due":
            // a payment failed but the subscription is still recoverable:
            // "please update your payment method" + a link to your portal
            break;
        case "canceled":
            // the customer ended the subscription: a win-back conversation,
            // not an error dialog
            break;
    }
}

VB.NET

Imports SWS.Licensing

Dim client As New LicensingClient(endpointUrl) With {
    .VendorId = vendorId,
    .LicenseKey = customerLicenseKey,
    .TrustedDomain = trustedDomain
}

Dim result As LicenseValidationResult = Await client.ValidateLicenseAsync(Nothing)

If Not result.IsValid AndAlso result.Status = LicenseStatus.PaymentRequired Then
    ' the message carries the subscription state - each deserves its own UX
    Select Case result.StatusMessage
        Case "past_due"
            ' a payment failed but the subscription is still recoverable:
            ' "please update your payment method" + a link to your portal
        Case "canceled"
            ' the customer ended the subscription: a win-back conversation,
            ' not an error dialog
    End Select
End If
  • past_due - a payment failed, but the subscription is still recoverable. This is your moment to prompt the customer: ask them to update their payment method, link to the payment portal, and optionally soften the app's behavior while payment is pending. Cards expire and payments fail for ordinary reasons, so keep the tone matter-of-fact.
  • canceled - the customer ended the subscription. Consider a win-back message, or a graceful downgrade to a free tier if you offer one.
  • Unknown states may appear as billing evolves. Fall back to a generic message such as "subscription needs attention" with a link to the payment portal.

What to keep in mind

  • Grace is server-side. The license's validity already accounts for the grace period, so avoid adding a second, hidden grace period in your application unless that is a deliberate product decision.
  • Machines that sleep through a renewal wake up with a lapsed license and renew automatically at the next online validation. EXPIRED while offline is not necessarily a payment problem (statuses reference).
  • Moving machines works exactly like any other license: deactivate and re-activate.
  • Subscriptions depend on a live connection and cannot use the offline activation exchange.

The vendor-side view, orders, subscription records, and payment events, lives in the console guide.