Skip to content
UnknownPay
ไทย
Core Concepts

Idempotency

Every money-moving POST requires an Idempotency-Key header so a retry returns the original result instead of creating a duplicate

Every endpoint that creates/moves money (a money-moving POST) must send a header to prevent duplication. If the network times out and the merchant retries, the system returns the original result instead of creating a duplicate record.

1. Header

HeaderDirectionMeaning
Idempotency-Keyrequest (merchant sends)A merchant-defined key bound to a single transaction attempt
Idempotent-Replayresponse (system returns)If set to true, this response is a replay from cache — no new record was created

2. Endpoints that require it (money-moving POST on the S2S side)

Method + PathDescription
POST /v1/depositsCreate a deposit
POST /v1/withdrawalsCreate a payout/withdrawal
If you call these endpoints without sending the Idempotency-Key header, the system responds with HTTP 400 and code IDEMPOTENCY_KEY_REQUIRED immediately — it is a mandatory header.GET / cancel / read-back endpoints (e.g. GET /v1/deposits/:id, GET /v1/withdrawals, POST /v1/deposits/:id/cancel) do not need to send Idempotency-Key.
Pending confirmation — confirm the full list of S2S money-moving POSTs, in case there are additional S2S endpoints that create money

3. TTL and replay behavior

  • TTL = 24 hours — within 24 h, a retry with the same key returns the original result. After that the old key is considered expired (purged) and is treated as a new record.
  • The scope of the key is separated by the (mode, entity) of the caller that the system derives from the credential — i.e. the same key in live and test will not collide (a test replay will not return a live result, and vice versa). The merchant does not have to manage scope.

Behavior across 3 cases:

SituationResult
Same key + same body (path/method/body all match)The system replays the original response (same status + body) with header Idempotent-Replay: trueno new record is created
Same key + different bodyThe system rejects with HTTP 422 code IDEMPOTENCY_KEY_MISMATCH — "Idempotency-Key was reused with a different request" (prevents reusing a key for a different transaction)
New keyProcessed as a new record as usual
The system binds the key to the method + path + the entire body (a hash), so even a tiny change to the body with the same key is rejected as IDEMPOTENCY_KEY_MISMATCH — when retrying you must send the exact same body.If a prior record failed with a server-level error (HTTP 5xx), the system rolls everything back — a retry with the same key can re-run cleanly (it does not get stuck on a dangling in-progress).
  • The system does not enforce a format or length for the key — it accepts any string that is non-empty but must be unique per record.
  • Use a UUID v4 (random, collision chance near zero), e.g. 9f1c2e7a-3b4d-4f8a-9c10-2b6d5e7f8a90.
  • Generate 1 key per transaction intent and keep that key so you can retry with the same key when the network fails.

Example request (deposit) with idempotency:

curl -X POST https://api.unkpay.co/v1/deposits \
  -H "X-Api-Key: unk_live_xxxxxxxxxxxx" \
  -H "X-Timestamp: 1718790000" \
  -H "X-Signature: <hex hmac-sha256 per the canonical string>" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 9f1c2e7a-3b4d-4f8a-9c10-2b6d5e7f8a90" \
  -d '{
    "amount": "100.50",
    "currency": "THB"
  }'
For how to compute X-Signature, see Authentication or use a ready-made client from the Code Samples that signs automatically.
Pending confirmation — confirm the Authorization / HMAC header form of S2S in the Authentication section; this example uses only fields that exist (amount, currency) — add other CreateReq fields per the real spec