Documentation · SoftActivate Licensing

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 touches no network and returns VALID in milliseconds. Two rules keep the rest of the lifecycle automatic:

  1. Always save the updated license when the SDK issues one. That is how renewals reach disk.
  2. 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).

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 — 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.