How Shoppex licenses work
When a buyer purchases a product fulfilled by Licenses, Shoppex issues a license object with:- A unique
license_keystring the buyer sees and enters into your app. - Status:
ACTIVE,SUSPENDED,REVOKED, orEXPIRED. - 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 allowlist —
allowed_ipsarray; non-matching IPs are rejected. - Optional max_uses and expires_at — limits on usage count and validity period.
What you’ll need
- A Shoppex API key with the
licenses.readscope. Create one at Settings → Developer → API Keys. - A way to compute a stable hardware fingerprint in your app. Common approaches:
motherboard serial + CPU ID on Windows (
wmic),IOPlatformUUIDon macOS,/etc/machine-idon Linux, or any cross-platform library that derives a stable hash from hardware identifiers.
The endpoint
keyandproduct_idare required.hardware_idis optional but recommended if you want anti-sharing.ipis optional. If you don’t pass it, Shoppex readsx-forwarded-for/x-real-ipfrom the request headers and uses that.
Response
On success, you get the license object back wrapped in Shoppex’s standard envelope:LICENSE_NOT_FOUND— key doesn’t exist in this shop.LICENSE_SUSPENDED— license is suspended (merchant-side action).LICENSE_REVOKED— license was revoked.LICENSE_EXPIRED— pastexpires_at.LICENSE_HWID_MISMATCH— the HWID you passed doesn’t match the bound one.LICENSE_IP_BLOCKED— the resolved IP isn’t inallowed_ips.LICENSE_MAX_USES_REACHED—uses>=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. - Subsequent 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.
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’s plenty:- An app that validates once on launch hits the limit only if a single customer is brute-forcing.
- Apps that validate periodically (e.g. every hour for “online required”) should batch and back off on 429.
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 doesn’t currently issue signed offline-validation tokens. Every validate call hits the API live. That means a totally-offline machine can’t 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), and let the app run for N hours / N days before requiring a fresh online call. Pick the grace window based on what’s 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 doesn’t include a dedicated licenses
service class yet. You can still call the endpoint via the SDK’s typed raw client:
fetch — both work. There’s no C#, .NET, Python, or other
language-specific desktop SDK in the repo yet.
Common pitfalls
- HWID that isn’t stable. Some hardware-id schemes change on OS update or BIOS reset. Test by validating, rebooting, validating again — same HWID? If not, you’ll burn legitimate buyers.
- Validating on every API call inside the app. Rate limits will hurt you. Validate on launch + every N hours; cache between.
- 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. Network blip ≠ invalid license. On
500/503/ connection errors, fall back to your cached grace window, don’t lock the user out.