Integrating the license manager with your product

The first step to take is to enable licensing for your product as explained in section License manager integration and download the product detail template:

Your code needs to submit some fields from the product JSON details in every call to the license server. Store the product template details securely somewhere in the code of your application.

{
    "product": {
        "name": "Your Product Name",
        "variant": "",
        "version": "1.0.0",
        "releaseDate": "2015-12-25",
        "shop": "yourcompanyname.myshopify.com",
        "id": 2,
        "variantId": null,
        "shopId": 503,
        "publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCu+qqMs1pP2wolT/0aakQ5FBaTuWbIK7yJerg6OcrpV7gj+jVt4fhzX87SBBu6aMQfixguvabNEFWMJTrcWXKlDMwW4EyocZA0Ln6wGSJPJvfWwskJcH4ix2C5SGZuo+E8qHb7eCki3wLeoX9wJMy2Rg75z5Dbw3rxsR5J8AD7XQIDAQAB"
    }
}

Note: you have to manually update fields version and release date every time a new version of your product is released.

For convenience, we also provide a Java library which handles all server communication securely for you, performs local license validation and also comes with a secure copy-protection implementation that binds each license you sell to your user’s hardware. See Integrating the license manager with your JAVA software to learn how to use it.

We are working to build similar libraries for other programming languages. If that’s your case contact us and let us know about your specific needs.

The licensing API

The licensing API consists of a couple of endpoints your product must use to interact with the license server:

  • POST /apps/licenses/api/register: registers/activates the user’s license.

  • POST /apps/licenses/api/validate: validates the user’s license and returns any updates.

Both endpoints are available from the base URL: https://yourcompanyname.myshopify.com/ or your domain name if you have it connected with Shopify.

Generating a host signature

The registration and validation endpoints need your application to provide a “host signature”, i.e. a string that identifies the hardware where your software is being executed, or the user himself. This is used for copy protection and prevents the same license to be shared with someone else who isn’t your customer.

There are 3 distinct approaches to generating a host signature:

  1. you don’t care about it: just set the host signature to be any non-empty string. The serial key of a license assigned to a given e-mail address can be used from multiple devices without restriction. Note that users may share their credentials and nothing will prevent people who didn’t pay for your software from using it.

  2. bind it to the user: set the host signature to some value that identifies your customer, such as an internal user account token used by your application, combined with their location or some other piece of information that isn’t likely to change for a particular person.

  3. bind it to the hardware: set the host signature to use information such as current operating system, MAC or IP addresses, disk serial numbers, etc. Some operating systems have a unique identifier which you can use. For example, Microsoft Windows generates a MachineGuid in its registry (under HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography) during the installation process and it won’t change unless the operating system is reinstalled.

On every remote license validation request your program is expected to provide this information. You must encrypt it or generate some sort of hash so it isn’t transferred and stored in our databases as plain text. The license server will simply verify that the signature it received upon registration is the same it received on each validation request.

Let’s proceed to the license request process.

Generating a license request

The process to obtain a trial license and a full license for your product is essentially the same. All your software has to do is to POST the product registration details to the /apps/licenses/api/register endpoint.

  • To obtain a trial license: from within your product capture the user’s e-mail address and name (first and last name).
{
    "product":{
        "name":"Your Product Name",
        "variant":"",
        "version":"1.5.2",
        "releaseDate":"2018-01-30",
        "shop": "yourcompanyname.myshopify.com",
        "id": 2,
        "variantId": null,
        "shopId": 503,
        "publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCu+qqMs1pP2wolT/0aakQ5FBaTuWbIK7yJerg6OcrpV7gj+jVt4fhzX87SBBu6aMQfixguvabNEFWMJTrcWXKlDMwW4EyocZA0Ln6wGSJPJvfWwskJcH4ix2C5SGZuo+E8qHb7eCki3wLeoX9wJMy2Rg75z5Dbw3rxsR5J8AD7XQIDAQAB"
    },
    "email":"some@email.com",
    "firstName":"John",
    "lastName":"Doe",
    "currentHostSignature":"Something that identifies the user's hardware or the user himself"
}

A successful trial license activation will return a response in this format:

{
    "result":"VALID",
    "signature":"MCwCFBVJLkXE7HkkCCkkPpKMsiQFTXEXAhRtUmzAZhGjdgmaPU2v0l5jmyCR8g==",
    "license":{
        "product":{
            "name":"Your Product Name",
            "variant":"",
            "version":"1.5.2",
            "releaseDate":"2018-01-30",
            "shop": "yourcompanyname.myshopify.com",
            "id": 2,
            "variantId": null,
            "shopId": 503
        },
        "email":"some@email.com",
        "firstName":"John",
        "lastName":"Doe",
        "hostSignature":"Something that identifies the user's hardware or the user himself",
        "expiration":"2018-02-15",
        "supportEnd":"2018-02-15",
        "signedLicenseKey":"MCwCFADQzvgVIrYLMUuCTGQX6TNIaI4PAhRpk7P7Gl5c2/wx7G8ETcqa2tYDBw=="
    }
}
  • To activate a full license: from within your product capture the user’s e-mail address and the serial key (which was sent to that e-mail address).
{
    "product":{
        "name":"Your Product Name",
        "variant":"enterprise",
        "version":"1.5.2",
        "releaseDate":"2018-01-30",
        "shop": "yourcompanyname.myshopify.com",
        "id": 2,
        "variantId": 5,
        "shopId": 503,
        "publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCu+qqMs1pP2wolT/0aakQ5FBaTuWbIK7yJerg6OcrpV7gj+jVt4fhzX87SBBu6aMQfixguvabNEFWMJTrcWXKlDMwW4EyocZA0Ln6wGSJPJvfWwskJcH4ix2C5SGZuo+E8qHb7eCki3wLeoX9wJMy2Rg75z5Dbw3rxsR5J8AD7XQIDAQAB"
    },
    "serialKey":"K1N7-WACB-Q5VP-G09K",
    "email":"some@email.com",
    "firstName":"John",
    "lastName":"Doe",
    "currentHostSignature":"Something that identifies the user's hardware or the user himself"
}

A successful response will be in this format:

{
    "result":"VALID",
    "signature":"MCwCFHD6Bbudd/jpSQS464aApDD8sQq9AhQ+zIydBB48zylnlWP84o5XE1Qh3Q==",
    "license":{
        "product":{
            "variant":"enterprise",
            "version":"1.5.2",
            "releaseDate":"2018-01-30",
            "shop": "yourcompanyname.myshopify.com",
            "id": 2,
            "variantId": 5,
            "shopId": 503
        },
        "serialKey":"K1N7-WACB-Q5VP-G09K",
        "email":"some@email.com",
        "firstName":"John",
        "lastName":"Doe",
        "hostSignature":"Something that identifies the user's hardware or the user himself"
        "expiration":"2019-05-23",
        "supportEnd":"2020-05-23",
        "signedLicenseKey":"MCwCFE29c8tE3Ke3+mTa3XpLyGhfM8XMAhQOTEFDnSVstEXclyB9lbjWhW4Jww=="
    }
}

Note that the product node is simply a copy of the template obtained in section Integrating the license manager with your product, with updated version and releaseDate.

All content inside the license node is your customer’s license. Your application must store this information locally to later be able to validate and synchronize the customer license using the license server, as detailed in section Validating the software license.

Server responses

In case of any errors registering or validating a license, the server will respond with a message in this format:

{
    "result":"EXPIRED",
    "signature":"MCwCFGCTYAmd/e7VWxLrlvFZByJYhy2WAhR7R2YgiJyEG1a+iMylYl/h/iVwKg==",
    "error":"Your license has expired on 2015-12-31"
}

Currently, the server produces the following result types:

  • VALID - The license is valid

  • ERROR - An internal error occurred while registering/validating the license. Does not mean that the license is invalid.

  • INCOMPLETE - The license registration details are incomplete.

  • EXPIRED - The license has expired.

  • SUPPORT_ENDED - The current version of the software being used is not supported by the current user’s license, i.e. the release date of your product version is after the license’s support end date.

  • UNKNOWN_HOST - Indicates the current hardware doesn’t match the original hardware signature from when the license was generated. If the license transfer permission is not set to No new devices as explained in section Customer license management, it can be reassigned to the current hardware, invalidating the old license.

  • TRIAL_EXPIRED - Indicates that the current license is a trial and the trial period has ended

  • RETRIAL_ATTEMPTED - Indicates the user tried to register for a trial license for a second time.

  • INVALID - Indicates the license is invalid, i.e. the product information doesn’t match with the license, or the license has been tampered with.

  • DISABLED - Indicates that a purchased license has been reassigned to someone else or another device. Your program must remove any copies of the local license.

  • TRIALS_DISABLED - Indicates that trial licenses have been disabled and the server won’t emit such licenses.

  • LICENSE_TRANSFER_DISABLED - Indicates an invalid attempt to transfer an existing license to a new user or device.

The error attribute always comes with a human-readable message that explains the result.

The signature attribute has a Base64 encoded signature that allows you to validate that the message actually came from the license server.

Validating server responses

To validate all responses returned by the server and ensure they are coming from the actual server, concatenate all values in the response (except for signature and nulls), in the order they appear, separated by the ^ character and validate the result against your product’s public key. Refer to Useful functions for server and license validation for suggested implementations.

For example, to validate the following error response:

{
    "result":"SUPPORT_ENDED",
    "signature":"MC0CFQCGe3rAe4cKWioB0KYe1DwBrLhm1wIUNLbekcLfjvjisFk5TdqZBbW6/+M=",
    "error":"Your license has no upgrade support for version 4.0.0. The support period ended on 2020-12-31"
}

Generate a String with SUPPORT_ENDED^Your license has no upgrade support for version 4.0.0. The support period ended on 2020-12-31

Then validate the signature using a method such as something like this (in Java):

public static boolean isSignatureValid(String message, String messageSignature, String publicKeyString) {
    byte[] publicKeyBytes = decodeBase64(publicKeyString);
    byte[] messageBytes = message.getBytes(Charset.forName("UTF-8"));
    byte[] messageSignatureBytes = decodeBase64(messageSignature);
    
    PublicKey publicKey = KeyFactory.getInstance("DSA").generatePublic(new X509EncodedKeySpec(publicKeyBytes));
    Signature signature = Signature.getInstance("SHA1withDSA");
    signature.initVerify(publicKey);

    signature.update(messageBytes, 0, messageBytes.length);

    boolean valid = signature.verify(messageSignatureBytes);
    return valid;
}

Or with JavaScript and the help of the fantastic jsrsasign library (download):

function isSignatureValid(message, signature, publicKey) {
    var publicKeyHex = b64tohex(publicKey)
    var signatureHex = b64tohex(signature);

    var validator = new KJUR.crypto.Signature({"alg": "SHA1withRSA"});
    validator.init(KEYUTIL.getKey(publicKeyHex, null, "pkcs8pub"));

    validator.updateString(message);

    var valid = validator.verify(signatureHex);
    return valid;
}

Using our example:

var message = "SUPPORT_ENDED^Your license has no upgrade support for version 4.0.0. The support period ended on 2020-12-31";
var signature = "ou4/B8yjGVFALPp54YzUON+w4WkQNI2WctXk8T8F78yrTmEyltCbV2Ah8F6shqofuGz81zuO01igF/Vgl/AvSgXo+/SooCUCJay9AD/YUaFv2NF1EldggGwYKLxn1YSu5q/I4sYFgP5G6AAgj3RWSpdOYw1MVccjxisVh6tkHVE=";
var publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCu+qqMs1pP2wolT/0aakQ5FBaTuWbIK7yJerg6OcrpV7gj+jVt4fhzX87SBBu6aMQfixguvabNEFWMJTrcWXKlDMwW4EyocZA0Ln6wGSJPJvfWwskJcH4ix2C5SGZuo+E8qHb7eCki3wLeoX9wJMy2Rg75z5Dbw3rxsR5J8AD7XQIDAQAB";
var valid = isSignatureValid(message, signature, publicKey); // -> true

When the server returns a VALID result, instead of an error message, you get a license:

{
    "result":"VALID",
    "signature":"TmKQje9GeI5L3//iI2Cn0ZQbaFs53ituwdwLx2YmwVlJsIRPGgqgS0VnrRe8b6IzbDxmgIeTOQ4IY0Q5OLGWvZN+LjDrLaS3YWwN8v4Q4xeaWfrnp5lDxQYZ6MNXbcGRIDTpvO+egeiaq2sdNLgSsCZsJv3v5NseYHibkCEgmHw=",
    "license":{
        "product":{
            "name":"license for A",
            "variant":"professional",
            "version":"1.0.0",
            "releaseDate":"2015-12-12",
            "shop":"shop2",
            "id":2,
            "variantId":4,
            "shopId":2
        },
        "serialKey":"IUZV-SEFH-AW9X-QKBI",
        "email":"me@email.com",
        "firstName":"me",
        "lastName":"mario",
        "hostSignature":"_SIG_",
        "expiration":"2017-12-12",
        "supportEnd":"2018-05-23",
        "signedLicenseKey":"rw46l6FffSaKXlaCdkQlN34U0HYOOLEljPPy2EbDxMhEoiH3cqB9aOcJtMiWtlKgksFRQpZb3VfyBjdjy9iBlcK24Vx7KaTPG/vQU4Umi3DbqETthZVMUMLVf8zR1bMf3UHQOVDDzA9fUAHz35T574Zk5EAtk42kdUhUM4zH9qE="
    }
}

Again, we concatenate all non-null values (except for signature), in the order they appear, separated by ^

var message = "VALID^license for A^professional^1.0.0^2015-12-12^shop2^2^4^2^IUZV-SEFH-AW9X-QKBI^me@email.com^me^mario^_SIG_^2017-12-12^2018-05-23^rw46l6FffSaKXlaCdkQlN34U0HYOOLEljPPy2EbDxMhEoiH3cqB9aOcJtMiWtlKgksFRQpZb3VfyBjdjy9iBlcK24Vx7KaTPG/vQU4Umi3DbqETthZVMUMLVf8zR1bMf3UHQOVDDzA9fUAHz35T574Zk5EAtk42kdUhUM4zH9qE=";
var signature = "TmKQje9GeI5L3//iI2Cn0ZQbaFs53ituwdwLx2YmwVlJsIRPGgqgS0VnrRe8b6IzbDxmgIeTOQ4IY0Q5OLGWvZN+LjDrLaS3YWwN8v4Q4xeaWfrnp5lDxQYZ6MNXbcGRIDTpvO+egeiaq2sdNLgSsCZsJv3v5NseYHibkCEgmHw=";
var publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDPWMlbkjJW5RRmXeBwqXivJyC814LJjtWBxr94dcrcOu1ySGEHP2jFf/+fYcLkF+nChti6aYO5zpGGBNb/XBWciNQMsOlvZKxMRP6a7013ayDfjyXnOX3de2F1AwUitWdnG9Gq1M3jgAPYc/5g9A+OZc6MVVmSFyoRqIdAgZY9kQIDAQAB";
var valid = isSignatureValid(message, signature, publicKey); // -> true

Validating the software license

Let’s revisit the output produced after Generating a license request, and obtaining a VALID license:

{
    "result":"VALID",
    "signature":"TmKQje9GeI5L3//iI2Cn0ZQbaFs53ituwdwLx2YmwVlJsIRPGgqgS0VnrRe8b6IzbDxmgIeTOQ4IY0Q5OLGWvZN+LjDrLaS3YWwN8v4Q4xeaWfrnp5lDxQYZ6MNXbcGRIDTpvO+egeiaq2sdNLgSsCZsJv3v5NseYHibkCEgmHw=",
    "license":{
        "product":{
            "name":"license for A",
            "variant":"professional",
            "version":"1.0.0",
            "releaseDate":"2015-12-12",
            "shop":"shop2",
            "id":2,
            "variantId":4,
            "shopId":2
        },
        "serialKey":"IUZV-SEFH-AW9X-QKBI",
        "email":"me@email.com",
        "firstName":"me",
        "lastName":"mario",
        "hostSignature":"_SIG_",
        "expiration":"2017-12-12",
        "supportEnd":"2018-05-23",
        "signedLicenseKey":"rw46l6FffSaKXlaCdkQlN34U0HYOOLEljPPy2EbDxMhEoiH3cqB9aOcJtMiWtlKgksFRQpZb3VfyBjdjy9iBlcK24Vx7KaTPG/vQU4Umi3DbqETthZVMUMLVf8zR1bMf3UHQOVDDzA9fUAHz35T574Zk5EAtk42kdUhUM4zH9qE="
    }
}

Once your software receives such a message, you must save the content of the license node locally. With the license details, you can perform a local license validation without constantly depending on the server to respond timely.

This information is also needed to generate remote license validation requests from time to time and ensure the license is still valid, has been renewed or even disabled.

Local license validation

Notice that the values inside the license node are signed and any modification to its content will make it invalid, so it should be safe to store these values in plain text anywhere.

To validate that your local license has not been tampered with, concatenate all non-null values inside the license and its product node, in the sequence they appear, separated by the character ^, excluding the signedLicenseKey. The resulting String can then be validated against the public key of your product. Let’s used the validation method presented in Useful functions for server and license validation for suggested implementations.

var licenseDetails = "license for A^professional^1.0.0^2015-12-12^shop2^2^4^2^IUZV-SEFH-AW9X-QKBI^me@email.com^me^mario^_SIG_^2017-12-12^2018-05-23";
var signedLicenseKey = "rw46l6FffSaKXlaCdkQlN34U0HYOOLEljPPy2EbDxMhEoiH3cqB9aOcJtMiWtlKgksFRQpZb3VfyBjdjy9iBlcK24Vx7KaTPG/vQU4Umi3DbqETthZVMUMLVf8zR1bMf3UHQOVDDzA9fUAHz35T574Zk5EAtk42kdUhUM4zH9qE=";
var publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDPWMlbkjJW5RRmXeBwqXivJyC814LJjtWBxr94dcrcOu1ySGEHP2jFf/+fYcLkF+nChti6aYO5zpGGBNb/XBWciNQMsOlvZKxMRP6a7013ayDfjyXnOX3de2F1AwUitWdnG9Gq1M3jgAPYc/5g9A+OZc6MVVmSFyoRqIdAgZY9kQIDAQAB";
var valid = isSignatureValid(licenseDetails, signedLicenseKey, publicKey); // -> true

Once that verification passes, you should test that the product details are the same and whether the host signature is still valid. If applicable you must also test that the license did not expire, and its release date comes before the support end date.

Remote license validation

You should validate the license remotely only once, after your software is started. Any changes to the license will be sent back to your program so the local copy can be updated.

CAUTION: The license server will block frequent validation/registration requests automatically, so avoid checking for the license validity remotely more than once after your program starts.

To generate a validation request, POST the product registration details, the current host signature, and the latest license details your program stored locally to the /apps/licenses/api/register endpoint.

For example:

{
    "product":{
      "name":"Your Product Name",
      "variant":"",
      "version":"1.6.0",
      "releaseDate":"2018-06-26",
      "shop": "yourcompanyname.myshopify.com",
      "id": 2,
      "variantId": null,
      "shopId": 503,
      "publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCu+qqMs1pP2wolT/0aakQ5FBaTuWbIK7yJerg6OcrpV7gj+jVt4fhzX87SBBu6aMQfixguvabNEFWMJTrcWXKlDMwW4EyocZA0Ln6wGSJPJvfWwskJcH4ix2C5SGZuo+E8qHb7eCki3wLeoX9wJMy2Rg75z5Dbw3rxsR5J8AD7XQIDAQAB"
    },
    "currentHostSignature":"Something that identifies the user's hardware or the user himself",
    "license":{
        "product":{
            "name":"Your Product Name",
            "variant":"enterprise",
            "version":"1.5.2",
            "releaseDate":"2018-01-30",
            "shop": "yourcompanyname.myshopify.com",
            "id": 2,
            "variantId": 4,
            "shopId": 503
        },
        "serialKey":"W954-T7H1-QTI2-UXA4",
        "email":"some@email.com",
        "firstName":"John",
        "lastName":"Doe",
        "hostSignature":"Something that identifies the user's hardware or the user himself"
        "supportEnd":"2018-07-21",
        "signedLicenseKey":"zHtthQ+gZO/pBOGvFg0K/aGc6uazPjqT8wn+/MMk2nqmCUkaXaBv2ZtrerTQEqQTVQfuX2XbFeCDwvd3ahmq8rWhFrcZGtqQvzldTn5f6KTBcBRzXPqDj6RzvxcbwdsGJjBuLVZQqLcOBw99Tel4yD2Sgyw4XUVkqwwycdDWwH8="
    }
}

Here the product version is not the same version of the product in the license. This means the user got a new version of your software (released on 2018-06-26). As this version was released before the support end date specified in the license (2018-07-21), the user is authorized to use the new version. Therefore, the server will return an updated license:

{
    "result":"VALID",
    "signature":"MCwCFHD6Bbudd/jpSQS464aApDD8sQq9AhQ+zIydBB48zylnlWP84o5XE1Qh3Q==",
    "license":{
        "product":{
            "name":"Your Product Name",
            "variant":"enterprise",
            "version":"1.6.0",
            "releaseDate":"2018-06-26",
            "shop": "yourcompanyname.myshopify.com",
            "id": 2,
            "variantId": 4,
            "shopId": 503
        },
        "serialKey":"W954-T7H1-QTI2-UXA4",
        "email":"some@email.com",
        "firstName":"John",
        "lastName":"Doe",
        "hostSignature":"Something that identifies the user's hardware or the user himself"
        "supportEnd":"2018-07-21",
        "signedLicenseKey":"MCwCFE29c8tE3Ke3+mTa3XpLyGhfM8XMAhQOTEFDnSVstEXclyB9lbjWhW4Jww=="
    }
}

Validate the server license details stored locally and replace the old details with what the server returned.

If the server returns a DISABLED, INVALID or INCOMPLETE result remove the local license details so your software behaves as if it never had a license.

Further Reading

We hope this should be enough to enable licensing on your software. Feel free to proceed to the following sections (in any order).

If you find a bug

We deal with errors very seriously and stop the world to fix bugs in less than 24 hours whenever possible. It’s rare to have known issues dangling around for longer than that. We provide a test environment for you to test whether our updates work for you as soon as the adjustments are made.

If you have suggestions or find a bug don’t hesitate to open an issue here or send us send us an e-mail.

We are happy to help if you have any questions in regards to how to use our app for your specific use case. Just send us an e-mail with your query and we’ll reply as soon as humanely possible.

We can work for you

If you don’t have the resources or don’t really want to waste time coding we can build a custom solution for you using our products. We deliver quickly as we know the ins and outs of everything we are dealing with. Send us an e-mail to sales@univocity.com with your requirements and we’ll be happy to assist.

The univocity team.

www.univocity.com