Go SDK
SDK 2.1.1 offline migration: configure trusted tenant public keys independently of cache files. No pins means no cached authorization. Persisted offline helpers use the configured client and re-verify the original signed token on every read. Offline setup and migration.
Device and usage operations in SDK 2.1.1: all clients can request key-only device removal using tenant slug, key and activation ID. HTTP 204 confirms removal; protected portals still require OTP/account customer flows. Usage reporting returns boolean confirmation, supports nested/empty metadata objects and normalizes quantities below 1 to 1. Java/C++ use tryMeter/try_meter for that boolean contract and retain legacy detailed meter results. Neither operation retries automatically or uses offline cache. See this SDK's README for signatures and failure handling.
Floating sessions in SDK 2.1.1: checkout and heartbeat return a failed session without credentials on network/HTTP/parse failure (RequestFailed). Business denial preserves its error code or uses FloatingDenied. Floating never uses offline cache. The new boolean TryCheckin equivalent confirms HTTP 204; legacy checkin is best effort. Method names follow this language's conventions. See the source README and SDK availability matrix before choosing a registry version.
Official SDKsGo SDK
SDK 2.1.1 request settings
A slow or unavailable license server must not leave your application waiting indefinitely. All eight SDKs default to a 10-second timeout for each attempt and two additional attempts for safe requests. The usual backoff is 250 milliseconds, then 500 milliseconds. This is not a single 10-second deadline for the complete operation: activation also fetches a nonce.
- License validation, nonce/public-key retrieval and floating heartbeat may retry a network failure or HTTP 5xx response. Set the retry count to zero to disable these retries; the range is 0–5.
- Floating checkout, activation POST, usage reporting, device removal and checkin are sent once. A lost response can follow a successful server change, so retrying these blindly can duplicate work.
- HTTP 3xx/4xx responses and malformed successful responses do not retry. SDK-owned transports do not follow redirects, so configure the final HTTPS API address directly.
For validate/activate, branch on the server's error code when available. Otherwise
HttpError means an HTTP rejection, InvalidResponse means malformed
successful license/nonce evidence and RequestFailed means the network/5xx attempts
were exhausted without a usable signed cache. Rejection or malformed successful evidence never
becomes cached authorization. A network outage can use only a separately trusted, unexpired proof.
| SDK | Configuration |
|---|---|
| Go | Options.Timeout uses time.Duration. MaxRetries is an optional integer pointer: nil defaults to two; a pointer to zero disables retries. |
Timeout mechanisms follow each runtime: Python uses socket I/O timeouts; Unity delegates to its transport. Caller cancellation is separate from retry policy. Unity's default adapter disables redirects; WebGL needs Unity 2021.3 or later for that protection and fails closed on older versions. Portable .NET tests do not certify an Editor, IL2CPP or browser build. Verify your actual target before shipping.
Install SDK 2.1.1 using the SDK installation references and follow the offline migration guide when upgrading from 1.x.
Pure Go 1.21+ client, standard library only — crypto/ecdsa handles offline
token verification natively, so unlike SDKs built on OpenSSL/PHP-openssl/Python
cryptography this needs no raw-signature-to-DER conversion step. No third-party
packages at all.
License: the Go SDK source is MIT-licensed by permitcore. Keep its notice when redistributing. See the Go SDK license. Existing 2.1.1 module archives are immutable; an older archive without the file can use the repository notice. This does not change SDK behavior or license the hosted service.
Installation
Published as a real, tagged Go module — go get works directly, no manual setup:
SDK 2.1.1 uses the /v2 module path. Install it with this command.
go get github.com/permitCore-spec/permitcore-sdk-go/v2@v2.1.1
Or, to work from the source ZIP above instead (e.g. for local edits before a release), add
a replace directive to your own go.mod pointing at the extracted
folder:
require github.com/permitCore-spec/permitcore-sdk-go/v2 v2.1.1
replace github.com/permitCore-spec/permitcore-sdk-go/v2 => ./permitcore-sdk-go
Quick start — validate
import "github.com/permitCore-spec/permitcore-sdk-go/v2"
client := permitcore.New("https://api.permitcore.dev")
result := client.Validate("PERMIT-XXXX-XXXX-XXXX-XXXX", "",
"7f3e9a2b-1c4d-4e8f-9a6b-3d2c1e0f5a8b") // expectedProductID — required trailing arg, pass "" to omit
if result.ErrorCode == "WrongProduct" {
log.Fatalf("This key is for a different product: %s", result.ProductID)
}
if !result.IsValid {
log.Fatalf("License invalid: %s", result.Message)
}
if result.HasFeature("export") {
enableExport()
}
Activate (call once per installation)
result := client.Activate(
"PERMIT-XXXX-XXXX-XXXX-XXXX",
"", // deviceId — auto-generated HWID when empty
"Production Server #1", // deviceName
"", // version, optional
"7f3e9a2b-1c4d-4e8f-9a6b-3d2c1e0f5a8b", // expectedProductID — required trailing arg, pass "" to omit
)
if result.ErrorCode == "WrongProduct" {
log.Fatalf("Wrong product: %s", result.ProductID)
}
if !result.IsValid {
log.Fatalf("Activation failed: %s", result.Message)
}
// Save result.IsValid to local storage — use Validate() on every subsequent launch.
Metered billing
// Record a single event
client.Meter(licenseKey, "api_call", 1, nil)
// Record bulk usage with metadata
client.Meter(licenseKey, "export", 5, map[string]any{"format": "pdf", "pages": 12})
Floating licenses
// Check out a seat at session start
session := client.Checkout(licenseKey, "", "")
if !session.Success {
log.Fatalf("No seats available: %s", session.Message)
}
token := session.SessionToken
// Heartbeat every 4-5 minutes to keep the seat alive
client.Heartbeat(token)
// Release the seat when done
client.Checkin(token)
Offline grace period
result := client.Validate(licenseKey, "", "") // falls back to disk cache automatically (expectedProductID omitted here)
if result.IsOffline {
// Running on cached result — server unreachable
showNotice("Running offline. Connect to refresh your license.")
}
Offline license tokens
No extra package needed — crypto/ecdsa is standard library. Verification is
pure local ECDSA P-256/SHA-256, zero network calls:
result := permitcore.VerifyOfflineToken(token, publicKeyBase64)
if result.IsValid {
fmt.Println("Valid! Product:", result.Payload.ProductName)
}
// Bind to this device + persist locally (call once, e.g. at install time)
result = client.ActivateOffline(token, deviceID, productID)
// On every later launch — no token needed, reads the local cache
result = client.ValidateOffline(deviceID, productID)
LicenseResult reference
| Field | Type | Description |
|---|---|---|
IsValid | bool | True if the license is active and valid |
ProductName | string | Product the license belongs to |
RemainingActivations | *int | Activation slots remaining |
ExpiresAt | string | ISO 8601 expiry date, empty = perpetual |
Features | []string | Feature flag list, e.g. ["export","api"] |
IsTrial | bool | True for trial licenses |
TrialDaysRemaining | *int | Days until trial expires |
NodeLocked | bool | True if bound to a specific device |
OfflineGraceDays | *int | How many days the cache remains valid |
MinVersion / MaxVersion | string | Version enforcement bounds |
VendorWarning | string | Non-fatal message from the vendor |
Message | string | Reason when IsValid == false |
IsOffline | bool | True when result came from local cache |
ErrorCode | string | Stable, machine-readable failure reason |
ProductID | string | Stable ID of the product this license belongs to. Always present when a license was found, even without passing expectedProductID. |
HasFeature(feature string) bool — case-insensitive check whether a feature flag is present on the license.
Product scoping: by default, any active key belonging to your tenant validates/activates successfully regardless of which product it was issued for. Both Validate and Activate take a trailing expectedProductID string argument (pass "" to omit) — set it and the server rejects a mismatch with ErrorCode == "WrongProduct". Find a product's ID in the Admin panel under Products.