How to add licence keys with an HWID lock to a Node.js app
Protect an Electron app, a CLI tool or any Node.js program with licence keys, a one-PC lock and a live kill switch using the free pwfauth package from npm: zero dependencies, TypeScript types included

Node.js runs a lot of paid software that never sees a browser: Electron desktop apps, command-line tools, automation scripts and bots. The pwfauth package from npm gives them licence keys, a lock to one PC and a kill switch in a few lines. It has no dependencies at all, it's built on Node's own fetch and crypto, and it ships TypeScript definitions
Server side only. The package holds your app secret, so it belongs in the Node.js side of your program (Electron's main process, a CLI, a service), never in code that runs in a web page
Step 1: create the application
Sign in to your panel, open Applications and press New application. Give it a name: the app secret is made for you
Step 2: install the package
npm install pwfauthIt needs Node 18 or newer
Step 3: sign in and start the heartbeat
Read the app secret from an environment variable, or from your build, never from a file you publish:
import { PwfClient } from 'pwfauth';
const client = new PwfClient(process.env.PWFAUTH_SECRET);
// banned, paused, expired, reset, ended from the panel, or no server for 90 seconds
client.on('sessionEnded', ({ errorCode, message }) => {
console.error(`Session ended (${errorCode}): ${message}`);
process.exit(1);
});
let login = await client.login(key);
if (!login.success) {
console.error(login.message); // written for the customer
process.exit(1);
}
client.startHeartbeat(); // the kill switch: never skip itstartHeartbeat() is the part that enforces your licence. Every 30 seconds it checks the key again, and a ban, a pause, an expiry or a reset in your panel ends the session on the next check-in. Only an encrypted reply counts as an answer: three check-ins in a row without one end the session with NETWORK_LOST, so blocking the server in a firewall or moving the clock doesn't keep a banned copy running. The timer never holds a process open on its own
Step 4: read the licence
const lic = login.license;
if (lic.isLifetime) {
console.log('Lifetime licence');
} else {
console.log(`Expires ${lic.expiresAt.toISOString()}, ${lic.daysRemaining} day(s) left`);
}expiresAt and daysRemaining are null for a lifetime key, which is the most common mistake with licence APIs, so isLifetime is there to check first
Step 5: move a licence to a new PC
import { PwfErrorCodes } from 'pwfauth';
if (login.errorCode === PwfErrorCodes.HWID_MISMATCH || login.errorCode === PwfErrorCodes.DEVICE_LIMIT) {
const moved = await client.resetHardwareId(key, 'New laptop');
if (moved.success) login = await client.login(key);
}You decide in the panel whether customers may move keys themselves, and how long they wait between two moves. The hardware ID is the same one the .NET, Python and C++ clients use, so one PC counts as one device whatever language your apps are written in
More you can do
await client.checkKey(key); // a key's state, without a session or a PC seat
await client.checkUpdate('1.4.2'); // is there a newer build? version, sha256, download link
await client.createTrial(); // a free trial for this PC, when trials are on
await client.accountLogin('alice', 's3cret'); // customer accounts instead of keys
await client.redeemKey(anotherKey); // adds a key's time to the accountA note on versions
The Node.js package checks that every reply is encrypted with your app secret. The extra check of PWF Auth's own server signature, which the C# and VB.NET, Python and C++ clients already have, isn't in the Node.js package yet. Keep it up to date with npm update pwfauth


