All guides

License Error Messages That Help Paying Customers

Design clear license errors for wrong products, device capacity, expiry and outages. Give buyers a recovery path while preserving the entitlement decision.

A license error should explain the current state and offer the next safe action. “Invalid license” is rarely enough for a paying customer, especially when the actual problem is a full device allowance, a different edition or an unavailable service.

Keep entitlement decisions separate from presentation. Helpful wording must not turn a rejection into access, erase customer data or encourage repeated purchases for a vendor-side problem. Use machine-readable outcomes for branching and plain text for the person.

Separate HTTP status from license status

A public validate request can return HTTP 200 with isValid: false. A processed request is not the same as an accepted entitlement. Check the result, expected product and required features before protected work.

HTTP 4xx, 5xx and timeouts are separate transport/request outcomes. Error bodies may be text, problem details or proxy HTML. Do not assume every body has the license-response schema or display raw HTML in the application. The error reference documents the current public endpoint behavior; Admin authentication errors have their own sign-in flow.

A missing error code still needs a safe generic branch. Do not parse English message text as an API contract, and do not treat an unknown response as permission to continue.

Make the next action match the rejection

  • NotFound: “We could not verify this key. Check the copied value or contact the seller.” Do not invent a separate Revoked enum value; some inactive keys are reported through a general rejection.
  • WrongProduct: “This key is for a different product.” Keep the expected product check; do not switch your application's identity to make the key pass.
  • SeatsExhausted: “This key has reached its device limit.” Offer the authorised portal or support workflow where available.
  • PoolExhausted: “The seller's activation capacity needs attention.” This is the vendor account's allowance, not a demand that this buyer purchase another key.
  • VersionBlocked: “This app version is outside the key's allowed range.” Offer an eligible build or a support/upgrade path that matches the commercial policy.
  • Expired: explain that the allowed access period and applicable grace have ended, with a renewal or seller-contact action.
  • VendorSubscriptionLapsed: the vendor must resolve its service account; do not falsely describe the buyer's own payment as failed.
  • IpBlocked / RegionBlocked: explain the restriction and contact route without coaching users to bypass the configured policy.

These are illustrative application messages, not literal SDK response strings. Localize them and preserve the underlying reason for support. Device slots, concurrent sessions and the monthly activation pool are different counters; see the capacity guide.

An outage needs a bounded recovery state

For a timeout or unavailable service, say that verification could not finish and offer a bounded retry or support action. Keep the interface responsive. Honor Retry-After when present, cap waiting and prevent repeated clicks from creating a request storm.

SDK 2.1.1 can use only verified signed cache authority within its configured policy. An explicit online rejection is not a reason to fall back to an older cached success. Missing trust, malformed proof or an expired deadline must not become an unconditional grant.

If the SDK returns valid grace, show its state and deadline rather than calling it a fresh paid renewal. Offline proof does not renew a floating seat. A denied heartbeat or missed session deadline requires the application to stop protected work, independently of what a separate license validation says.

Do not blindly replay mutations

A timed-out activation may already have committed. Replaying count-based activation can consume additional capacity. Use the supported nonce flow and reconcile uncertain results; a fresh nonce alone does not prove the previous operation failed.

Checkout, device removal, usage recording and check-in have their own confirmation semantics. Do not add a generic retry loop around every SDK method. Use the request settings reference and floating lifecycle instead of translating every exception into “try again forever”.

Protect customer work and support information

Keep saved documents and user data accessible through the recovery policy your product promises. Where appropriate, disable the paid action while allowing the person to copy diagnostic information or reach support. Do not silently delete a key, deactivate other devices or initiate another charge as an error-handling shortcut.

A support report can contain the app version, target platform, time, operation, structured reason and a correlation identifier if your integration supplies one. Redact keys, private tokens, signing material, credentials and unnecessary customer details. Render messages as text and keep stack traces and raw provider responses out of normal buyer UI.

Test the message and the protected action together

  • Every accepted, rejected and unavailable state has a deliberate UI branch.
  • A friendly error never enables the protected action.
  • Unknown codes and non-JSON responses remain safe.
  • Keyboard focus, retry/cancel controls and support links work on the actual target.
  • Capacity and seller-account errors do not tell buyers to pay twice.
  • Restart, reconnect, valid grace and expiry preserve the intended state.

A useful error gives the customer a practical recovery route while preserving the license decision. Keep that route consistent with the configured portal authority and your support process.

Put the guide into practice

Build your first licensed product without building a license server.

Create a free PermitCore account, issue a test license, and validate it from your application.