Implement the existing contract with independent key trust, stable installation state and fail-closed policy.
What you will learn
- Choose the client boundary and enforce every authorization layer.
- Run all published signature vectors and a real Sandbox lifecycle.
- Test storage, clock, offline and error behavior without fabricated success.
Prerequisites
- Complete the Sandbox quickstart.
- Read signed responses, authentication and offline mode.
Decide whether you need a custom client
Use an official SDK when its runtime and documented release limitations fit. Use a REST example when you need another native language, stricter application-specific policy or controlled storage. A custom client implements the existing API; it must not invent new server fields, endpoints, signing formats or offline extensions.
The ten complete REST projects are working examples, not ten additional official SDK products. TypeScript uses lossless verification modules, Kotlin shares the Java core through JVM interop, and C++ links the complete C ABI core. These are explicit native-library boundaries, not hidden Python/Node subprocess dependencies.
Conformance checklist
| Area | Required behavior | Negative test |
|---|---|---|
| Credentials | Public X-App-ID at activation; Bearer act_ for the installation; lmp_ only on a developer backend | A Live key or wrong environment never creates a test activation |
| Installation | Generate random stable identity once, persist privately across processes/updates | Same namespace retains the exact installation hash; no hardware serial fallback |
| HTTP | HTTPS peer/hostname verification, timeout, bounded JSON, no credential-bearing redirects | Nonlocal HTTP, URL credentials/query/fragment, redirect and oversized/invalid success are denied |
| Parser | Preserve numeric lexemes; reject duplicate keys, malformed UTF-8/Unicode, trailing data and excessive depth | Integral floats, Unicode, duplicate-property and invalid-encoding vectors |
| Key trust | Independently trust the raw Ed25519 public key and its derived key_id | Wrong key, algorithm, key ID, signature size or noncanonical base64url is denied |
| Signature | Verify the exact canonical payload before policy or features | Modified payload/cache never unlocks a feature |
| Binding | Match product, environment and SHA-256 installation binding | Correct signature for another product/environment/installation is still denied |
| Status/time | Require active status; validate start/expiry and signed validation/grace dates | Suspended/revoked/expired/future-start policy refuses access |
| Offline | Only transport failure can use still-eligible signed cache | Explicit HTTP refusal or invalid success cannot become offline permission |
| Rollback | Compare trusted server/checkpoint time with the documented tolerance | A future checkpoint behind which the device moved is rejected |
| Features | Require accepted validity/signature before boolean/numeric access | Invalid results with true features, negative/fractional/noninteger limits are denied |
| State | Atomic private writes, clear denied cache, serialize lifecycle operations | Corrupt/symlink state fails safely; concurrent writers cannot replace identity |
| Mutations | Reconcile uncertain activation/release rather than blind retries | Lost response does not imply the server never consumed an activation |
| UX/logging | Safe user text, bounded correlation ID, no key/token/customer payload output | Raw transport/exception/secret data never reaches a renderer or public log |
The strict REST example policy is deliberately distinct from existing SDK implementation limitations. Do not state that all official SDKs enforce every row identically; compare the compatibility page and current source. Stronger client-side start/date checks do not change the server contract.
Signature self-test procedure
- Download the complete verification archive. Expected: all source verifiers, public vectors and selftest.py; no private signing keys.
- Install the exact dependencies described by your language's verification page. Expected: a native verifier that prints signature_valid=true/false with exit0/1.
- Run selftest.py with the verifier command from that language page. Expected: PASS23, including valid signatures whose license policy is expired.
- Confirm that your application does not interpret signature_valid=true as current permission. Expected: the expired signature vector authenticates but fails expiry policy.
- Keep vector sources and dependency locks pinned with your application tests. Expected: a byte/signature regression fails before distribution.
The existing complete self-test harness is the one executed by this documentation program:
"""Run every public vector using your verifier; Python 3.9+, standard library.
Run: python3 selftest.py --command python3 python/verify.py
Expected: PASS23 and exit0. A mismatched result prints FAIL and exits1.
No network, database, credentials, SDK monkey-patching or access decision.
"""
import argparse
import json
import pathlib
import subprocess
import sys
parser = argparse.ArgumentParser()
parser.add_argument('--command', nargs=argparse.REMAINDER, required=True)
args = parser.parse_args()
root = pathlib.Path(__file__).resolve().parent
vectors = json.loads((root / 'vectors/index.json').read_text())['vectors']
failures = []
for row in vectors:
command = args.command + [str(root / 'vectors' / row['key']), str(root / 'vectors' / row['envelope'])]
try:
result = subprocess.run(command, capture_output=True, text=True, timeout=30)
expected = row['signature_valid']
ok = (result.returncode == (0 if expected else 1)
and result.stdout.strip() == 'signature_valid=' + str(expected).lower()
and not result.stderr)
except (OSError, subprocess.TimeoutExpired):
ok = False
if not ok:
failures.append(row['name'])
if failures:
print('FAIL: ' + ', '.join(failures))
sys.exit(1)
print('PASS' + str(len(vectors)))
Public vectors authenticate canonicalization/signatures only. Unicode/number conformance-only payloads are not complete licenses. Do not add fake product/time fields to them or turn them into fabricated server success. The separate actual Sandbox cycle establishes real binding and feature policy.
Real Sandbox self-test
- Create a fresh Sandbox product/plan/license with the actual documented dashboard/API workflow. Expected: a genuine App ID, TEST license and matching customer email; no developer key in the installed app.
- Start with an empty private application namespace and activate with the wrong email. Expected: INVALID_LICENSE and no feature permission.
- Activate with the correct data. Expected: genuine signed response, exact binding and explicit feature values.
- Restart using the same state root and check. Expected: stable identity and eligible verified cache, not a new server activation.
- Force online validation. Expected: an actual server decision; do not bypass signature failures.
- Inject transport failure only in an isolated local test. Expected: bounded signed offline acceptance or a clear refusal; initial activation still requires network.
- Inject an explicit HTTP refusal. Expected: no offline fallback grant and no stale authorization cache.
- Modify a cached payload without resigning it. Expected: signature failure and no feature access.
- Move the test checkpoint into the future. Expected: rollback rejection without editing a real signed timestamp.
- Check the exact signed grace/expiry boundary with a test-only clock. Expected: the selected client's documented edge semantics; no clock override in the distributed production app.
- Deactivate and restart. Expected: no credential/cache access and the same installation identity. Cumulative activation_count is not reset.
The documentation runs actual Sandbox lifecycles in all ten languages, plus230 published signature-vector checks. TypeScript also tests exact signed grace/expiry instants using a test-only preload. These results do not certify every concurrent host, native OS vault, target binary or malformed-date combination.
Build, storage and release checklist
Keep trusted-key provisioning separate from license input. Do not automatically replace a pinned key because the envelope asks for another key_id. The current platform does not provide an historical key ring or an advertised overlap/rotation window; plan a controlled application update.
Store the token and signed state outside executable extraction directories and public web roots. Use an OS vault where your application supports one, and document fallbacks honestly. A filename protected by Unix modes is not encrypted credential storage. Multiple tenants must not share one installation namespace.
Serialize activation/checkpoint/deactivation writes, preserve random identity across upgrades and reconcile uncertain mutations. Deactivation releases a seat but does not reset cumulative activation counts. Removing a package does not release the server seat or erase its application state.
Before shipping, run real responses and cache reload through the bundled executable, test explicit refusal and offline recovery, check logs for secrets, and show users a simple support/retry path. Record platform/runtime/dependency versions and known limitations rather than silently patching or suppressing signature checks.
Expected result
Your integration authenticates signed state and handles failures without granting features. Installation identity, source version and test boundaries remain explicit.
Common mistakes
- Treating signature authenticity as complete license validity.
- Retrying uncertain activation blindly or deleting identity to avoid a refusal.
- Replacing a pinned trusted key from untrusted envelope data.
Security notes
Keep developer keys on trusted backends, pin independent key trust, preserve HTTPS/signature verification and never equate token presence with permission. Local machine owners can patch software; a successful build or checksum is not an anti-tamper certification.
Troubleshooting
| Symptom | Cause | Action |
|---|---|---|
| Vector signature differs | Canonical bytes or key metadata differs | Compare the exact raw JSON and independently trusted key |
| Offline unexpectedly allowed | HTTP refusal was mistaken for network loss | Keep transport/HTTP/signature/policy failure boundaries separate |
| Repeated activations after upgrade | Namespace or identity storage changed | Restore the intended stable app state and reconcile extra seats |
Next steps
Review the SDK downloads, test vectors and API errors for the exact selected client.