KDPika Licensing Sign inStart free
API v1
Show all languages
Browse documentation

Choose a page in the navigation drawer.

KDPika Licensing · API v1 Advanced

Signed responses

Understand the exact Ed25519 envelope, PHP canonical bytes, trusted keys, time checks and replay limits before accepting a license.

On this page
Updated 2026-10-17 UTCDownload Markdown

Verify the signed payload with a trusted public key, then independently check whether its license state permits the requested action.

What you will learn

  • Reproduce the exact signed bytes without losing numbers or Unicode.
  • Bind a response to your product, environment and installation.
  • Handle key changes, expiry, clock changes and replay honestly.

Prerequisites

  • Read authentication and the activation flow.
  • Retain the original UTF-8 JSON response before a normal parser changes numeric tokens.
  • Configure a trusted public key separately from the response being verified.

Envelope and trust boundary

Activation and validation return a license envelope. A successful HTTP request alone is not proof of authenticity or permission. The signed part is only payload; activation_token, success, valid and request_id in the outer API response are not covered by this signature.

Envelope fieldTypeRequired meaning
payloadobjectThe license snapshot whose canonical UTF-8 bytes are signed
signaturestringUnpadded base64url representation of a 64-byte detached signature
algorithmstringMust equal Ed25519; do not negotiate a different algorithm from the response
key_idstringMust match the independently trusted key, not authorize downloading an arbitrary key URL

The public key is 32 raw bytes, not a PEM file. The signature is 64 raw bytes, not a hex string. Ed25519 signs the message directly; do not pre-hash the payload or use Ed25519ph. See RFC 8032.

Exact signed bytes

The server recursively sorts associative PHP-array keys with ksort using SORT_STRING, preserves list order, then encodes the payload with JSON_UNESCAPED_SLASHES, JSON_UNESCAPED_UNICODE and JSON_PRESERVE_ZERO_FRACTION. The result has no indentation or separating whitespace. Strings and object names use UTF-8 byte ordering; JavaScript and .NET default UTF-16 ordering can differ for non-BMP keys.

Strings use literal Unicode, except U+2028 and U+2029 are escaped. Quotes, backslashes and JSON controls are escaped; forward slashes remain literal. There is no Unicode normalization. A composed character and its decomposed form are different bytes. Duplicate object names are not a supported wire format; reject them before signature checking.

An integral float such as 19.0 remains 19.0. Numeric encoding is PHP-specific and also depends on its serialize_precision setting. This contract is not RFC 8785 / JSON Canonicalization Scheme. Normal JSON.parse followed by JSON.stringify changes some signed numbers; sorting a normal parsed JavaScript object is insufficient. The PHP JSON documentation describes these flags and precision behavior.

The verification samples retain numeric tokens from the original server JSON and canonicalize its keys and strings. They do not reconstruct PHP floating-point formatting from rounded language numbers. Keep those original numeric tokens in any local cache too. An intermediary that changes a numeric representation can cause a safe verification failure. Do not bypass that failure.

Signed payload field reference

Nullable means the field may explicitly contain null. A missing required field is not the same as null. Your application must validate the fields it uses after verifying the signature.

FieldTypeMeaning and handling
license_idstringPublic license identifier; compare with the expected license when known
product_idstringPublic App ID; must equal your configured product
productstringProduct display name; not an authorization identifier
planstringProduct-plan slug; not the developer's platform subscription
statusstringCurrent license state; require active for normal licensed access
environmentstringsandbox or live; must match the configured environment
license_key_maskstringDisplay mask; never a credential
starts_atstring or nullUTC ISO 8601 start date; do not grant use before a future start
expires_atstring or nullUTC ISO 8601 expiry; null has no license expiry deadline
created_atstring or nullCreation display timestamp; not a freshness proof
max_devicesintegerConcurrent active-device limit
max_activationsinteger or nullCumulative activation limit; null means no configured cumulative limit
activation_countintegerCumulative activation count
active_devicesintegerActive count at snapshot time; not a live counter offline
min_versionstring or nullConfigured lower application-version restriction
max_versionstring or nullConfigured upper application-version restriction
customerobjectCustomer information; treat as personal data
customer.idstringCustomer's public identifier
customer.namestring or nullOptional name
customer.emailstringCustomer email associated with the license
customer.companystring or nullOptional company
customer.countrystring or nullOptional country
product_detailsobjectProduct display metadata
product_details.idstringPublic App ID, consistent with product_id
product_details.namestringProduct display name
product_details.platformstringLowercase configured application platform
plan_detailsobjectProduct-plan snapshot
plan_details.idstringProduct-plan identifier
plan_details.namestringProduct-plan display name
plan_details.slugstringProduct-plan slug
plan_details.billing_typestringLowercase product-plan billing type
plan_details.pricenumberPHP float; 19.0 and 19 have different signature bytes
plan_details.currencystringProduct-plan currency; not evidence of a payment
plan_details.duration_daysinteger or nullConfigured license duration
plan_details.trial_daysinteger or nullConfigured trial duration
featuresobject or empty arrayDynamic feature keys; empty PHP feature arrays can serialize as []
features[key]JSON valueBoolean or configured JSON value; validate its type before use
deviceobjectBound installation metadata
device.idstringDevice's public identifier
device.installation_hashstring or nullSHA-256 of the persisted installation ID; require a match
device.namestring or nullOptional installation label
device.platformstring or nullOptional platform description
device.app_versionstring or nullOptional application version description
server_timestringUTC ISO 8601 snapshot time; signed, but still replayable
next_validation_atstringSuggested online-validation deadline
offline_grace_untilstringAbsolute offline-fallback deadline

Activation can return empty device-description strings; validation can return null for the same absent descriptions. Null is signed data, not a reason to remove a property before verification. Omitting X-Installation-ID during validation can produce a null installation hash; always send the persisted ID and reject an unbound response in your client.

Fetch and cache the trusted public key

  1. Fetch GET /api/v1/public-key from the configured KDPika HTTPS origin, using normal certificate and hostname verification.
  2. Read key.algorithm, key.key_id, key.public_key and key.encoding from the successful response.
  3. Require Ed25519, base64url and a decoded 32-byte key.
  4. Calculate key_id as ed25519_ followed by the first 16 lowercase hexadecimal characters of SHA-256 of those raw public bytes.
  5. Pin the approved key in your application or save the HTTPS-fetched key in protected application state according to your trust policy.
  6. Cache the current key for at most the endpoint's one-hour cache lifetime before an intentional refresh.

Expected result: a response cannot choose its own key, host or algorithm. An untrusted file beside the application is not a secure pin merely because its filename is key.json.

Trust on first use from HTTPS depends on the origin, certificate validation and the client's local environment. A compiled pin reduces dependence on a later key fetch, but a fully controlled client machine can still replace the program. Do not fetch a key using a URL supplied in an envelope.

Key changes and rotation

The platform currently exposes one configured public key and reads one signing-key pair. There is no public historical key ring, overlapping-signature period, key-validity schedule or rotation-history endpoint.

  1. Treat an unknown key_id as untrusted, not as permission to skip verification.
  2. If your policy allows refresh, fetch once from the fixed HTTPS public-key endpoint and validate its format and identity.
  3. Accept a new key only through your intended trust-update policy; a strictly pinned client needs an application/configuration rollout.
  4. Retry verification once with the approved key or return a clear connection/update-required error.
  5. Discard old cached signed states according to the rollout and expiry policy.

Expected result: rotation is deliberate and failures remain closed. Any client-managed overlap is your integration policy, not an existing server guarantee. Compromise of a signing key requires a separate coordinated response with KDPika support.

Time, binding and replay

After authenticity succeeds, require the intended product, environment and installation hash, active status, acceptable application version, valid start/expiry and the appropriate signed validation/grace deadlines. Use the official SDK's valid result as the first gate before feature helpers.

Both next_validation_at and offline_grace_until are calculated from server_time. Grace is not automatically added after the validation interval. A valid signature can belong to an expired license: the expired vector intentionally demonstrates this distinction.

The payload contains no signed request nonce, client challenge or monotonic sequence number. A previously valid signed snapshot can be replayed until your time/binding policy rejects it. The SDKs use a five-minute local clock-rollback tolerance, but local files and clocks are mutable. Revocation on the server cannot instantly invalidate a disconnected cached response. Shorter intervals and server-side authorization reduce exposure; they do not make a local binary unpatchable.

The current source has a recorded database-timezone discrepancy for created_at on a non-UTC local database. Do not use created_at as proof of freshness. This documentation does not change stored timestamps or signing behavior.

Expected result

The positive vector verifies. A changed payload or wrong key fails. The expired vector verifies cryptographically but must fail your time-based access decision. A response for another product, environment or installation does not unlock your application.

Common mistakes

  • Signing the outer response, or signing an already rounded JavaScript number tree.
  • Using a public key embedded in the same untrusted response as the trust anchor.
  • Removing nulls, normalizing Unicode or rewriting numbers before checking the signature.
  • Treating signature_valid as permission to use an expired or revoked snapshot.

Security notes

The public signature prevents undetected edits under a trusted key. It does not encrypt customer information, confirm a buyer payment, make the local clock trustworthy or prove a response is the newest available state. Keep raw caches private and avoid logging customer payloads or activation tokens.

Troubleshooting

SymptomLikely causeSafe next step
Only integral-price responses failA JSON round trip changed 19.0 to 19Preserve original numeric tokens; run the integral-float vector
Unicode works online but not from cacheThe cache changed string escaping or the client canonicalizer preserved escaped raw stringsRun Unicode vectors; reject the cached result and investigate the known SDK discrepancy
New responses use an unknown key_idA key changed or the response is untrustedFollow the deliberate key-refresh/pin-update policy; never bypass verification
Signature passes but access failsExpiry, status, environment, product or installation checks rejected itExplain the actual license state to the customer and validate online when appropriate

Next steps

Use the complete verification samples, then run every test vector.

Was this page helpful?

Choose an answer

Do not include personal data, license keys or credentials. Retained for up to 30 days. This is not a support inbox.

Search documentation

Arrow keys to move, Enter to open, Escape to close. Search stays in your browser.

Cookie settings