Deduplicate your business events durably; the current API creation key is useful but does not guarantee exactly-once fulfillment.
What you will learn
- Choose stable keys and identical retry bodies.
- Understand expiry, concurrency and duplicate-work defects.
- Handle store fulfillment and renewal uncertainty without guessing.
Prerequisites
- Read API conventions.
- Complete the Sandbox quickstart.
Creation key contract
POST /api/v1/licenses requires an Idempotency-Key header with 8–190 trimmed characters. The key is scoped to the whole organization. Include a stable product/store/event identity so unrelated operations cannot collide. Store event IDs are input to your own mapping; KDPika does not verify an external store event here.
The retained response lasts 24 hours. Reusing the unexpired key and identical decoded-body hash returns the original 201 body, including the original request_id and plaintext newly issued license key, plus Idempotent-Replay:true. X-Request-ID identifies the current request. A different hash returns 409 IDEMPOTENCY_CONFLICT.
The server hashes PHP JSON encoding of the decoded body. JSON whitespace is decoded away, but object member order is retained and can change the hash. Unknown fields also affect the hash even if creation ignores them. Serialize one stable request and retain its exact business meaning; do not regenerate/reorder customer fields between retries.
Known limitations: not exactly once
An expired row before cleanup can be missed by the lookup and then collide with the unique insert after a new license commits. The caller receives 500 and a duplicate license can exist. Simultaneous same-key requests can both pass the lookup and commit separate licenses; one response then fails storing the idempotency record. Both issues are reproduced in isolated Sandbox tests and remain unchanged.
Do not reissue with a fresh key simply because a request timed out or returned 500. Use your durable fulfillment mapping and support to reconcile uncertain creation. No public license-list/customer-search endpoint exists to reconstruct that mapping automatically. A management GET can reconcile only when you already retained the license ID. These limitations make unattended exactly-once payment fulfillment unsupported without an approved product fix.
Retry safety by operation
| Operation | Idempotency-Key behavior | Client decision |
|---|---|---|
| GET /api/v1/public-key | Ignored / no replay | Safe bounded read retry |
| POST /api/v1/licenses | Required; retained response with known defects | Reconcile uncertain result before another write |
| POST /api/v1/licenses/activate | Ignored / no replay | Reconcile uncertain result before another write |
| POST /api/v1/licenses/validate | Ignored / no replay | Token/last-seen refresh; coordinate retries |
| POST /api/v1/licenses/deactivate | Ignored / no replay | Reconcile uncertain result before another write |
| GET /api/v1/licenses/{license} | Ignored / no replay | Safe bounded read retry |
| POST /api/v1/licenses/{license}/revoke | Ignored / no replay | Reconcile uncertain result before another write |
| POST /api/v1/licenses/{license}/suspend | Ignored / no replay | Reconcile uncertain result before another write |
| POST /api/v1/licenses/{license}/restore | Ignored / no replay | Reconcile uncertain result before another write |
| POST /api/v1/licenses/{license}/renew | Ignored / no replay | Reconcile uncertain result before another write |
| POST /api/v1/licenses/{license}/reset-devices | Ignored / no replay | Reconcile uncertain result before another write |
Activation rotates tokens even on an existing ACTIVE installation. Renewal extends time on every accepted call. Deactivation repeats401; repeated status/reset actions can repeat events. Database row locks serialize updates but do not identify a repeated external business event.
Durable fulfillment example
For a verified event such as YOUR_STORE_EVENT_ID, persist a fulfillment row under a unique key of store/account/product/event. Save the normalized request, intended action and state before calling the API. Serialize workers on that row; do not use process-local locks as durable deduplication.
- Verify the store webhook or server-side purchase with your own provider integration. Expected: authenticated event, not a success-page query string.
- Acquire the unique durable fulfillment row. Expected: only one worker owns this event; an already completed row returns its saved license mapping without another API call.
- Use a stable creation key such as store:YOUR_STORE_ACCOUNT:YOUR_PRODUCT:YOUR_STORE_EVENT_ID. Expected: an 8–190 character key with no buyer email or card data.
- Send the saved normalized JSON to the create operation. Expected: a documented201 with a new license ID/key, or an explicit error.
- Persist the license mapping and secret-delivery state atomically in your system. Expected: subsequent deliveries reuse the durable record, not another API creation.
- Mark any timeout/500 as uncertain and reconcile before releasing another write. Expected: no automatic duplicate issuance or renewal. Do not claim a provider charged the buyer unless your provider data confirms it.
A stable illustrative request key is YOUR_STORE_EVENT_ID, not a new random value on every retry. Use your provider account in the dedupe namespace when event IDs are not globally unique. This is a workflow specification, not a new provider integration or a fake payment example. Store-specific tested integration code belongs to the separate integration guides.
Renewal requires its own deduplication
Persist each verified renewal event and expected old/new expiration before calling renew. Renew has no replay key. If a response is lost, compare a known license summary and your durable record; do not add the same days automatically a second time. Concurrent unrelated renewals need deliberate business serialization, because merely reading then writing is not compare-and-swap.
Retention and secrecy
The server currently stores the complete creation response, including plaintext license_key, in its idempotency record for 24 hours; cleanup depends on a functioning scheduler. Treat database access/backups as sensitive. Your own customer-key delivery must be authorized and confidential. The API does not automatically email the key to the buyer.
Expected result
A replayed business event does not trigger an intentional second write from your worker, and uncertain outcomes are surfaced for reconciliation.
Common mistakes
- Choosing a new random header for every attempt of the same event.
- Assuming renew supports the creation replay mechanism.
- Advertising exactly-once fulfillment despite the current expiry/concurrency defects.
Security notes
Verify purchases on your backend. Never put provider secrets or developer API keys in buyer applications. Keep replay bodies and fulfillment records private.
Troubleshooting
| Symptom | Cause | Action |
|---|---|---|
| 409 IDEMPOTENCY_CONFLICT | Different decoded request hash/member order | Use the saved original request; do not hide the conflict with a new key |
| 500 after create | Uncertain work, including documented replay defects | Reconcile; retain current/original request IDs |
| Expiry extended twice | Same renewal event repeated without durable dedupe | Investigate the event mapping; do not automate further extensions |
Next steps
Review the endpoint reference and signature verification.