Python integration guide

Activate, verify Ed25519 and deactivate with urllib and cryptography.

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.