This guide covers the full loop: activate → store → verify on startup → deactivate. It assumes you already have a workspace and a license key.
HTTP uses the standard urllib; verification uses the cryptography package (pip install cryptography).
1. Activate
license.py
import json
import urllib.request
BASE = "https://keygate.unionuser.com"
def activate(license_key, device_id):
payload = json.dumps({
"license_key": license_key,
"identifier": device_id,
"identifier_type": "device",
}).encode()
req = urllib.request.Request(
BASE + "/api/v1/license/activate",
data=payload,
headers={"Content-Type": "application/json"},
method="POST",
)
with urllib.request.urlopen(req, timeout=15) as res:
body = json.loads(res.read())
if not body.get("success"):
raise RuntimeError(body["error"]["code"])
return body["data"]["token"]
2. Verify offline
verify.py
import base64
import json
import time
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
RAW_PUBLIC = bytes.fromhex("bf17435ee7b937b3592808941683c21d37c4e124d95d4e8fdd86fe775dd23b11")
public_key = Ed25519PublicKey.from_public_bytes(RAW_PUBLIC)
def b64url(value: str) -> bytes:
return base64.urlsafe_b64decode(value + "=" * (-len(value) % 4))
def verify_token(token, device_id):
payload_b64, sig_b64 = token.split(".")
public_key.verify(b64url(sig_b64), payload_b64.encode("ascii"))
claims = json.loads(b64url(payload_b64))
if claims["did"] != device_id:
raise ValueError("DEVICE_MISMATCH")
if claims["exp"] < time.time():
raise ValueError("TOKEN_EXPIRED")
return claims
3. Deactivate
deactivate.py
def deactivate(license_key, device_id):
payload = json.dumps({
"license_key": license_key,
"identifier": device_id,
}).encode()
req = urllib.request.Request(
BASE + "/api/v1/license/deactivate",
data=payload,
headers={"Content-Type": "application/json"},
method="POST",
)
with urllib.request.urlopen(req, timeout=15) as res:
return json.loads(res.read())
Packaging notes
- Test cryptography as a hidden import when packaging with PyInstaller.
- Store the token with QSettings or platformdirs, not in a read-only install directory.
- Separate network failures from business errors before retrying.