Endpoint Integration Best Practices
The endpoint gives you control, which means the parts the SDK would have handled for you are now yours: timeouts, retries, rate limits, key storage and verification. None of them are difficult, and all of them are worth getting right.
Do and avoid
- Set an explicit connect timeout and request timeout on every call
- Retry 429 and 5xx with exponential backoff, never in a tight loop
- Send a fresh nonce on every attempt and never reuse one across a retry
- Verify the token signature and read your data from the verified payload
- Run the check off the main thread, once on startup and again every ten minutes
- Reuse the last successful result for a minute or two instead of asking on every command
- Read X-Quota-Remaining and log it, so you learn about the daily cap before you hit it
- Keep the apiKey and the product id in your config file rather than in code
- Cache the last successful result so a brief outage does not stop a running server
- Trusting the response body without verifying the token
- Sending the license key as a query parameter
- Disabling the plugin on the first network failure
- Hammering the endpoint after a 429 instead of honouring Retry-After
- Retrying inside a 429 caused by an exhausted daily quota, since Retry-After is hours away
- Reusing one nonce across restarts
- Accepting whatever alg the response header asks for
- Logging license keys, HWIDs or the full request URL
Retries and backoff
Only 400, 401, 403, 404 and 409 are definitive. Timeouts, 429 and 5xx are transient, and a definitive answer ends the loop. Send a new nonce on every attempt, because a nonce you reuse within its window comes back as 409.
private static final int MAX_ATTEMPTS = 5; private boolean checkLicense(String licenseKey, String hwid) { for (int attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) { try { EndpointResponse response = endpoint.validate(licenseKey, hwid, productVersion); if (response.ok) { return LicenseTokenVerifier.verify(response.token, productId, hwid); } return false; } catch (HttpTimeoutException | ConnectException error) { getLogger().warning("Attempt " + attempt + "/" + MAX_ATTEMPTS + " timed out"); } catch (HttpRateLimitException error) { getLogger().warning("Rate limited, waiting " + error.retryAfterSeconds + "s"); sleep(error.retryAfterSeconds * 1000L); } catch (IOException error) { getLogger().warning("Attempt " + attempt + "/" + MAX_ATTEMPTS + " failed"); } sleep(Math.min(30000L, 1000L * (1L << attempt))); } return false;}Key storage
The apiKey identifies your product, so treat it like a server credential: keep it in your config file or environment, not as a string literal in a class someone can decompile. The license key is your buyer's input and belongs in their config. Never log either one, and never put the license key in a query string.
Obfuscation and native code
Because the check runs in your code, how you protect it is entirely up to you. Obfuscating the class that makes the call, relocating it, or moving the request into a small native library all raise the cost of patching, and none of them replace signature verification. A native caller still needs the pinned public key, so make sure that key is available to it.
Daily quota
Free plans get 4,000 validation requests a day and Pro plans get no limit. The window is 24 hours measured from your first request, not a calendar day, so you cannot double up by straddling midnight. Every response tells you where you stand in X-Quota-Limit and X-Quota-Remaining, and Retry-After on a 429 tells you how long until the window resets. When the quota is exhausted the answer comes back as 429 with quotaExceeded set, which is a different situation from a short rate limit: do not retry in a loop, cache harder, and if you genuinely need more, a Pro plan removes the cap entirely.
Offline behaviour
A server that loses its connection mid-session should keep running. Re-check every ten minutes, treat 429 and 5xx as retryable, and only stop features after several consecutive definitive rejections such as 403 or 404.
UlisesLib.jar does the same validation for you, with retries, HWID collection and the auto-updater built in. It is a dependency you shade into your jar rather than a contract you implement.
Using the SDKEndpoint best practice questions
Should my plugin disable itself when a request times out?
No. Timeouts, 429 and 5xx are transient. Retry with backoff and only act after several consecutive definitive rejections.
Where should the apiKey live?
In your config file or environment, never as a literal in compiled code. It is a per-product credential, not a user credential.
Do I need to send a nonce?
Send one. It costs eight bytes and it is what stops a captured response from being replayed against you later.
Does the endpoint replace the auto updater?
No. The endpoint reports whether a newer version exists, nothing more. If you want download and checksum verification, use UlisesLib.
Built by Ulises - Citymoon Dynamics