Skip to main content
You ship a desktop application — Windows, macOS, or Linux, using Electron, .NET, Qt, native, or another stack. You want it to activate against Shoppex license keys. Each customer’s purchase issues a key. Your app calls Shoppex to validate the key and binds it to the user’s hardware so the key cannot be shared. This tutorial walks through the activation flow end to end.

How Shoppex licenses work

When a buyer purchases a product fulfilled by Licenses, Shoppex issues a license object with:
  • A unique license_key string the buyer sees and enters into your app.
  • Status: ACTIVE, SUSPENDED, REVOKED, or EXPIRED.
  • Optional HWID binding — when the first activation comes in with a hardware ID, that HWID gets bound. Future activations from different hardware fail.
  • Optional IP allowlistallowed_ips array. Non-matching IPs are rejected.
  • Optional max_uses and expires_at — limits on usage count and validity period.
Your job is to call the validate endpoint, hand it the buyer’s key plus their HWID, and act on the response.

What you will need

  • A Shoppex API key with the licenses.read scope. Create one at Settings → Developer → API Keys.
  • A way to compute a stable hardware fingerprint in your app. Common approaches: motherboard serial plus CPU ID on Windows (wmic), IOPlatformUUID on macOS, or /etc/machine-id on Linux. Any cross-platform library that derives a stable hash from hardware identifiers also works.

The endpoint

Request body:
  • key and product_id are required.
  • hardware_id is optional but recommended if you want anti-sharing.
  • ip is optional. If you do not pass it, Shoppex reads x-forwarded-for / x-real-ip from the request headers and uses that.

Response

On success, you get the license object back wrapped in Shoppex’s standard envelope:
On failure, you get an error response with one of these codes:
  • LICENSE_NOT_FOUND — key does not exist in this shop.
  • LICENSE_SUSPENDED — license is suspended (merchant-side action).
  • LICENSE_REVOKED — license was revoked.
  • LICENSE_EXPIRED — past expires_at.
  • LICENSE_HWID_MISMATCH — the HWID you passed does not match the bound one.
  • LICENSE_IP_BLOCKED — the resolved IP is not in allowed_ips.
  • LICENSE_MAX_USES_REACHEDuses >= max_uses.

HWID binding behavior

The HWID logic is automatic:
  • First call with a HWID, license hwid_pending: true — Shoppex binds the HWID to the license and returns success. The license is now locked to that machine.
  • Later calls with the same HWID — pass through. License returned.
  • Call with a different HWID — Shoppex returns LICENSE_HWID_MISMATCH. The buyer is trying to activate on a second machine.
When the buyer legitimately needs a new machine (replacement laptop, reinstalled OS), you or the merchant can call:
This unbinds the HWID. The next validate call with a new HWID will bind that one instead. For workflows where buyers self-service the HWID reset, for example through your app’s “Move to a new machine” button, there is also a key-scoped variant:
Setting hardware_id to null puts the license back in hwid_pending: true state. Setting it to a string binds that HWID directly.

Rate limits

The Dev API uses a token bucket — by default 30 tokens, refilling 10 tokens every 2 seconds. Validate calls count against this. For most apps that is plenty:
  • An app that validates once on launch hits the limit only if a single customer is brute-forcing.
  • Apps that validate periodically, for example every hour for an “online required” check, must batch and back off on 429.
Response headers tell you where you stand: x-ratelimit-limit, x-ratelimit-remaining, x-ratelimit-reset. On 429, respect the retry-after header.

A minimal activation flow

Caching: how to handle “the user is offline”

Shoppex does not currently issue signed offline-validation tokens. Every validate call hits the API live. That means a totally-offline machine cannot validate. Most apps handle this by caching successful validations for a grace period. Store the last successful validation timestamp locally, signed by your app’s own key to prevent tampering. Let the app run for N hours or N days before requiring a fresh online call. Pick the grace window based on what is acceptable in your domain. Game keys often run with 24-48 hour grace. Business software often runs with 30 days.

SDK note

The official @shoppexio/sdk for Node/TypeScript does not include a dedicated licenses service class yet. You can still call the endpoint through the SDK’s typed raw client:
Or use plain fetch — both work. There is no C#, .NET, Python, or other language-specific desktop SDK in the repo yet.

Common pitfalls

  • HWID that is not stable. Some hardware-id schemes change on OS update or BIOS reset. Test by validating, rebooting, validating again — same HWID? If not, you will burn legitimate buyers.
  • Validating on every API call inside the app. Rate limits will hurt you. Validate on launch and every N hours. Cache between validations.
  • Not handling 429. Even a well-behaved app gets rate-limited if a user manually retries 10x. Honor retry-after.
  • No fallback for transient errors. A network blip is not the same as an invalid license. On 500/503/connection errors, fall back to your cached grace window. Do not lock the user out.