Activating Machines

Use Keygen's API and your code to implement machine activation for your product. If you need help after reading this, can reach out to us anytime at [email protected].

If you're looking for something simpler than machine activation, then you should check out the license resource's usage-related actions. These actions allow you to increment/decrement a license's usage count as an alternative to implementing machine activation.

This tutorial is written in JavaScript, but you can follow along in any other language that you're comfortable in. Throughout this tutorial, you will see placeholders such as {ACCOUNT} within the code examples that will need to be replaced with an ID for that particular resource type.

Before getting started, you will either need to log into your Dashboard and generate a product token, or generate an authentication token via the API. To create a token directly through the API, check out the code examples in the API reference or check out our guide on authenticating users.

Admin and product tokens should not be included in any client-facing code, as they offer full access to all of your account's resources. You can authenticate as one of your users or use an activation token to perform client-side machine activation. Admin and product tokens should only be used server-side.

Once you've created a license, you can "activate" individual machines for your product to be used on, making it easier for you to implement e.g. a node-locked licensing model. Although Keygen doesn't use the term "activation" much in our resource documentation, machine creation accomplishes the same thing, while machine deletion would be "deactivation."

Implementing a node-locked or node-limited license model consists of 3 to 4 separate steps, each done in a separate API request:

  1. Create the license - this is the step where you create the user's license resource, typically done server-side after you've received a payment, or after they've registered for an account.
  2. Validate their license - this step should be initiated client-side from within your software (e.g. prompt them for a license key after starting your software), and can be performed both before and after activation. Validating before activation so you can be sure the license supports another machine; validating after activation to ensure the newest activation didn't invalidate the license.
  3. Activate their machine - this step should be initiated from within your software, so you can have access to the underlying machine to be able to query information such as MAC address, HDD ID, etc. for use in fingerprinting the user's current machine.

Activation requests can either be performed client- or server-side, and depending on which one of those you choose, the tokens you use to make the request will be different i.e. product tokens vs. user tokens vs. activation tokens, for server-side and client-side machine activations.

We have examples of server-side implementations that you can use as a reference for implementing your own license activation system. We have example servers written in Node, as well as in PHP.

In order to perform machine activation client-side, you will need to be authenticated as a user or use an activation token which belongs to the license you're activating a machine for.

Embedding your admin or product tokens within your software is not secure.


When activating a machine, you will at minimum need a license ID for the license the activation is for, a fingerprint for the current device, as well as an optional user ID for client-side activation. Other machine attributes, such as IP or hostname, are completely optional.

The easiest way to obtain a license's ID is to perform a license validation before the machine activation. The license's ID will be included within the validation response.

In the example below, we'll be using a SHA512 hash of the current device's unique identifer as a fingerprint. We're hashing the user's device ID as a simple way to anonymize it, since we have no real use in knowing its actual value. Using an HMAC digest (i.e. HMAC-SHA512) might be an even better way to anonymize the user's MAC address.

Although we're using a device's unique machine ID in this example, a fingerprint can be any unique string able to be reproducibly queried from the device, e.g. you could write a secure random string to a file on disk or to a registry and use that, but for example purposes the device's ID works well enough.

Fingerprinting the device

const { machineId } = require('node-machine-id')
const crypto = require('crypto')

const getFingerprint = async () => {
  const id = await machineId({ original: true })

  return crypto.createHash('sha512')
               .update(id)
               .digest('hex')
}

Next up, we'll use our getFingerprint function above while creating a new machine resource that is associated with the current user and their license. Before activating a machine, you should validate the current license to make sure its policy will allow for an additional machine activation.

You can utilize a policy's maxMachines, floating and concurrent attributes to enforce your desired rules for machine usage. For example, you can create a policy which allows up to 3 machines to be activated per-license by setting maxMachines=3, floating=true, and concurrent=false.

You can check how many machines a license is allowed to have through its maxMachines attribute, which is synced with the license's policy. You can also query all of a license's machines through its machines relationship. These can be useful for managing UI state, e.g. not showing an "activate machine" action if the user is already at their machine activation limit.

Activating the machine

Below is an example of performing the machine activation request.

const fetch = require("node-fetch")

const response = await fetch("https://api.keygen.sh/v1/accounts/{ACCOUNT}/machines", {
  method: "POST",
  headers: {
    "Content-Type": "application/vnd.api+json",
    "Accept": "application/vnd.api+json",
    "Authorization": "Bearer {TOKEN}"
  },
  body: JSON.stringify({
    "data": {
      "type": "machines",
      "attributes": {
        "fingerprint": await getFingerprint()
      },
      "relationships": {
        "license": {
          "data": { "type": "licenses", "id": "{LICENSE}" }
        },
        "user": {
          "data": { "type": "users", "id": "{USER}" }
        }
      }
    }
  })
})

const { data: machine, errors } = await response.json()
if (errors) {
  // … handle errors
}

console.log(`Machine activated: ${machine.attributes.fingerprint}`)
Do you need to validate license keys offline? We support cryptographically signed or encrypted license keys for offline validation via the policy's scheme attribute. Alternatively, check out our example client/server implementation of offline license activation for air-gapped machines using Keygen, Node.js, React, TOTP, QR codes, and a mobile device.

Example Activation Flow

Below is a full example of how to implement a robust machine activation flow, following the above mentioned steps. You may of course choose to implement a different flow if needed - this only serves as an example implementation.

If you'd rather run this example locally, we have a similar activation example up on our GitHub.
View example of activation flow
async function activateLicense(key, fingerprint) {
  // Validate the license key before activation, so we can be sure it supports
  // another machine. Notice that this validation is scoped to the current
  // machine via its fingerprint - this ensures that license activation is
  // not performed for machines that are already activated.
  const validation = await fetch('https://api.keygen.sh/v1/accounts/{ACCOUNT}/licenses/actions/validate-key', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/vnd.api+json',
      'Accept': 'application/vnd.api+json'
    },
    body: JSON.stringify({
      meta: {
        scope: { fingerprint },
        key
      }
    })
  })

  const { meta, data: license } = await validation.json()

  // If the license is valid, that means the current machine is already
  // activated. We can safely return.
  if (meta.valid) {
    return null
  }

  // If we've gotten this far, our license is not valid for the current
  // machine and we should attempt to activate it.
  switch (meta.constant) {
    // This means the license already has at least 1 machine associated with
    // it, but none match the current machine's fingerprint. We're breaking
    // on this case because, for this example, we want to support activating
    // more than 1 machine.
    case 'FINGERPRINT_SCOPE_MISMATCH':
    // You will receive a NO_MACHINES status when the license IS floating,
    // and when it does not currently have any associated machines.
    case 'NO_MACHINES':
    // You will receive a NO_MACHINE status when the license IS NOT floating
    // i.e. it's node-locked, and when it does not currently have any
    // associated machines.
    case 'NO_MACHINE': {
      break
    }
    default: {
      throw new Error(`Activation failed: ${meta.detail} (${meta.constant})`)
    }
  }

  // Attempt to activate the current machine for the license, using the
  // license ID that we received from the validation response and the
  // current machine's fingerprint.
  const activation = await fetch('https://api.keygen.sh/v1/accounts/{ACCOUNT}/machines', {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer {PRODUCT_TOKEN}',
      'Content-Type': 'application/vnd.api+json',
      'Accept': 'application/vnd.api+json'
    },
    body: JSON.stringify({
      data: {
        type: 'machines',
        attributes: {
          fingerprint
        },
        relationships: {
          license: {
            data: { type: 'licenses', id: license.id }
          }
        }
      }
    })
  })

  const { data: machine, errors } = await activation.json()
  if (errors) {
    throw new Error(errors.map(e => `${e.title}: ${e.detail}`).join(', '))
  }

  // All is good - the machine was successfully activated.
  return machine
}

async function main() {
  const fingerprint = await getFingerprint()
  const key = await getLicenseKey()

  try {
    const machine = await activateLicense(key, fingerprint)
    if (machine == null) {
      console.log('The current machine has already been activated!')

      return
    }

    console.log('The current machine was successfully activated!', machine)
  } catch(err) {
    console.error('Activating the current machine failed', err)
  }
}

main()

Next steps

That's that! You've activated your first machine using Keygen. Next up, we could update a license's policy to require a machine fingerprint during validating using the requireFingerprintScope setting. If you have any questions about what you've learned today, be sure to reach out!