Use Keygen to license and distribute your Windows desktop application

Securely license and distribute your Windows program from a single API.


Free during development, no upfront commitment

5 stars
Demo of the Keygen admin dashboard
using System.Threading.Tasks;
using Squirrel;
 
static async Task Main()
{
var licenseToken = "lic-6171564a4a59e5f3879c0584ebe92ce3v3";
var releases =
$"https://get.keygen.sh/demo/win32?auth=token:{licenseToken}";
 
using (var mgr = new UpdateManager(releases))
{
await mgr.UpdateApp();
}
}

From licensing to automatic updates, we've got you covered.

  • checkUtilize our software distribution API to allow downloads for licensed users. Integrate directly with industry-standard tooling like Squirrel or NSIS for painless automatic updates.
  • checkProtect your app with our flagship software licensing API. Add powerful entitlement constraints, upgrade version constraints, enforce activation limits, and more.
Get Started with Keygen

Bring your own programming language

Quickly implement Keygen's API in any programming language.

package main
 
import (
"github.com/denisbrodbeck/machineid"
"github.com/keygen-sh/keygen-go/v2"
)
 
func main() {
keygen.Account = "YOUR_KEYGEN_ACCOUNT_ID"
keygen.Product = "YOUR_KEYGEN_PRODUCT_ID"
keygen.LicenseKey = "key/..."
 
fingerprint, err := machineid.ProtectedID(keygen.Product)
if err != nil {
panic(err)
}
 
// Validate the license for the current fingerprint
license, err := keygen.Validate(fingerprint)
switch {
case err == keygen.ErrLicenseNotActivated:
// Activate the current fingerprint
machine, err := license.Activate(fingerprint)
switch {
case err == keygen.ErrMachineLimitExceeded:
panic("Machine limit has been exceeded!")
case err != nil:
panic("Machine activation failed!")
}
case err == keygen.ErrLicenseExpired:
panic("License is expired!")
case err != nil:
panic("License is invalid!")
}
 
fmt.Println("License is activated!")
}
const fetch = require('node-fetch')
 
const res = await fetch('https://api.keygen.sh/v1/accounts/demo/licenses/actions/validate-key', {
method: 'POST',
headers: {
'Content-Type': 'application/vnd.api+json',
'Accept': 'application/vnd.api+json'
},
body: JSON.stringify({
meta: {
key: 'C1B6DE-39A6E3-DE1529-8559A0-4AF593-V3'
}
})
})
 
const { meta } = await res.json()
 
if (meta.valid) {
// Do something
} else {
// Do something else
}
import SwiftyJSON
import Alamofire
 
Alamofire.request("https://api.keygen.sh/v1/accounts/demo/licenses/actions/validate-key",
method: .post,
headers: [
"Content-Type": "application/vnd.api+json",
"Accept": "application/vnd.api+json"
],
parameters: [
"meta": [
"key": "C1B6DE-39A6E3-DE1529-8559A0-4AF593-V3"
]
],
encoding: JSONEncoding.default
).responseJSON { response in
let json = JSON(data: response.data!)
let valid = json["meta"]["valid"].bool
 
if valid {
// Do something
} else {
// Do something else
}
}
using RestSharp;
using System;
using System.Collections.Generic;
 
var client = new RestClient("https://api.keygen.sh/v1/accounts/demo");
var request = new RestRequest(
"licenses/actions/validate-key",
Method.POST
);
 
request.AddHeader("Content-Type", "application/vnd.api+json");
request.AddHeader("Accept", "application/vnd.api+json");
request.AddJsonBody(new {
meta = new {
key = "C1B6DE-39A6E3-DE1529-8559A0-4AF593-V3"
}
});
 
var response = client.Execute<Dictionary<string, object>>(request);
var meta = (Dictionary<string, object>) response.Data["meta"];
 
if ((bool) meta["valid"])
{
// Do something
}
else
{
// Do something else
}
import com.mashape.unirest.http.exceptions.*
import com.mashape.unirest.http.*
import org.json.*
 
val body = JSONObject(mapOf(
"meta" to mapOf(
"key" to "C1B6DE-39A6E3-DE1529-8559A0-4AF593-V3"
)
))
 
val res = Unirest.post("https://api.keygen.sh/v1/accounts/demo/licenses/actions/validate-key")
.header("Content-Type", "application/vnd.api+json")
.header("Accept", "application/vnd.api+json")
.body(body)
.asJson()
 
val data = res.getBody().getObject()
val meta = data.getJSONObject("meta")
 
if (meta.getBoolean("valid")) {
// Do something
} else {
// Do something else
}
import com.mashape.unirest.http.exceptions.*;
import com.mashape.unirest.http.*;
import org.json.*;
 
import static java.util.Map.ofEntries;
import static java.util.Map.entry;
 
JSONObject body = new JSONObject(ofEntries(
entry("meta", ofEntries(
entry("key", "C1B6DE-39A6E3-DE1529-8559A0-4AF593-V3")
))
));
 
HttpResponse<JsonNode> res = Unirest.post("https://api.keygen.sh/v1/accounts/demo/licenses/actions/validate-key")
.header("Content-Type", "application/vnd.api+json")
.header("Accept", "application/vnd.api+json")
.body(body)
.asJson();
 
JSONObject data = res.getBody().getObject();
JSONObject meta = data.getJSONObject("meta");
 
if (meta.getBoolean("valid"))
{
// Do something
}
else
{
// Do something else
}
#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
let validation: serde_json::Value = reqwest::Client::new()
.post("https://api.keygen.sh/v1/accounts/demo/licenses/actions/validate-key")
.header("Content-Type", "application/vnd.api+json")
.header("Accept", "application/vnd.api+json")
.json(&serde_json::json!({
"meta": {
"key": "C1B6DE-39A6E3-DE1529-8559A0-4AF593-V3"
}
}))
.send()
.await?
.json()
.await?;
 
if validation["meta"]["valid"].as_bool().unwrap() {
// Do something
} else {
// Do something else
}
 
Ok(())
}
#include <iostream>
#include <string>
#include <cpprest/http_client.h>
#include <cpprest/filestream.h>
 
using namespace std;
using namespace web;
using namespace web::http;
using namespace web::http::client;
using namespace web::json;
using namespace utility;
 
http_client client("https://api.keygen.sh/v1/accounts/demo");
http_request req;
 
value meta;
meta["key"] = value::string("C1B6DE-39A6E3-DE1529-8559A0-4AF593-V3");
 
value body;
body["meta"] = meta;
 
req.headers().add("Content-Type", "application/vnd.api+json");
req.headers().add("Accept", "application/json");
 
req.set_request_uri("/licenses/actions/validate-key");
req.set_method(methods::POST);
req.set_body(body.serialize());
 
client.request(req)
.then([](http_response res)
{
auto data = res.extract_json().get();
auto meta = data.at("meta");
 
if (meta.at("valid").as_bool())
{
// Do something
}
else
{
// Do something else
}
})
.wait();
import requests
import json
 
data = requests.post(
"https://api.keygen.sh/v1/accounts/demo/licenses/actions/validate-key",
headers={
"Content-Type": "application/vnd.api+json",
"Accept": "application/vnd.api+json"
},
data=json.dumps({
"meta": {
"key": "C1B6DE-39A6E3-DE1529-8559A0-4AF593-V3"
}
})
).json()
 
if data["meta"]["valid"]:
# Do something
else:
# Do something else
require 'httparty'
 
data = HTTParty.post(
'https://api.keygen.sh/v1/accounts/demo/licenses/actions/validate-key',
headers: {
'Content-Type' => 'application/vnd.api+json',
'Accept' => 'application/vnd.api+json'
},
body: {
meta: {
key: 'C1B6DE-39A6E3-DE1529-8559A0-4AF593-V3'
}
}
)
 
if data['meta']['valid']
# Do something
else
# Do something else
end
<?php
 
$url = 'https://api.keygen.sh/v1/accounts/demo/licenses/actions/validate-key';
$ch = curl_init($url);
 
$body = json_encode([
'meta' => [
'key' => 'C1B6DE-39A6E3-DE1529-8559A0-4AF593-V3'
]
]);
 
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'Accept: application/json'
]);
 
$json = curl_exec($ch);
$data = json_decode($json);
 
if ($data->meta->valid) {
// Do something
} else {
// Do something else
}

Solutions for Licensing and Distribution


  • library_booksKeygen Quickstarts

    Guides and API references for developers of all skill levels.

    View Docs

  • insert_chartKeygen Dashboard

    Manage your products from an intuitive dashboard.

    View Dashboard

  • businessKeygen Pricing

    From indie to enterprise, we have plans for all business sizes.

    View Pricing