Use Keygen to license and distribute commercial Electron applications

Securely license and distribute cross-platform Electron apps with a single API.


Free during development, no upfront commitment

5 stars
Demo of the Keygen admin dashboard
const { autoUpdater } = require('electron-updater')
const log = require('electron-log')
 
const licenseToken = 'lic-6171564a4a59e5f3879c0584ebe92ce3v3'
 
log.transports.file.level = 'debug'
autoUpdater.logger = log
 
autoUpdater.addAuthHeader(`Bearer ${licenseToken}`)
autoUpdater.checkForUpdatesAndNotify()

From license activation to auto-upgrades, we can help.

  • checkUse our software distribution API to securely deliver your app to licensed users. Integrate directly with industry-standard tooling like electron-builder for dead-simple automatic updates.
  • checkProtect your code with our flagship software licensing API. Add feature entitlements, limit upgrades to specific version ranges, enforce activation limits, and more.
Read the Blog Post

Bring your own programming language

Quickly implement Keygen's API in any programming language.

package main
 
import "github.com/keygen-sh/keygen-go"
 
func activate() error {
keygen.Account = os.Getenv("KEYGEN_ACCOUNT")
keygen.Product = os.Getenv("KEYGEN_PRODUCT")
keygen.Token = os.Getenv("KEYGEN_TOKEN")
 
// 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:
fmt.Println("Machine limit has been exceeded!")
 
return err
case err != nil:
fmt.Println("Machine activation failed!")
 
return err
}
case err == keygen.ErrLicenseExpired:
fmt.Println("License is expired!")
 
return err
case err != nil:
fmt.Println("License is invalid!")
 
return err
}
 
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
}

Resources 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