Idempotency Keys
Make every money-entry POST safe to retry with a required Idempotency-Key and a unique merchant_reference.
Two layers protect a merchant from accidental duplicate intents: the Idempotency-Key header owns
transport retries and merchant_reference owns unique business identity. Use both.
The Idempotency-Key header
Payment, Checkout, and Payout creation require an Idempotency-Key header. The server recognises
the key for 24 hours. A semantically identical request with the same key replays the response during
that period.
POST /payment_intents
Authorization: Basic <base64(pk_test_...:sk_test_...)>
Idempotency-Key: 7c2a9e3f-...
Content-Type: application/jsonRules:
- Up to 255 printable ASCII characters. A UUID v4 is the safe default.
- Scope is
(api_key_id, key). Two API keys can use the same idempotency string without collision. - A key expires after 24 hours. Use a new key for a new request after that period.
- A repeat with the same key but different canonical fields returns
409 idempotency_conflict. - A repeat sent while the original is still in flight returns
409 idempotency_conflict. - A
5xxresponse on the original request releases the lock so the retry reaches a fresh handler.
Send an Idempotency-Key on every supported money-entry POST. Payment, Checkout, and Payout
creation reject a missing key before persistence.
merchant_reference on intents
merchant_reference is your application's unique order or payout id. The platform enforces
uniqueness on (merchant_id, environment, merchant_reference) per intent resource:
- Reusing a Payment, Checkout, or Payout
merchant_referenceunder anotherIdempotency-Keyreturns409 duplicate_merchant_reference, even when the create fields are unchanged. - Only a retry with the original
Idempotency-Keycan replay the original response. - The reference does not replace
Idempotency-Key; both identities are required.
For direct Payment requests, the fingerprint covers amount, currency, country, customer,
payment_method, payment_method_options, return_url, device fingerprint,
metadata, and Merchant reference. Checkout requests fingerprint their smaller contract separately:
amount, currency, country, optional customer, return_url, metadata, and Merchant reference.
Normalised values are fingerprinted canonically, so equivalent requests with the same key replay.
The header protects transport retries while merchant_reference preserves business identity. Store
both values in your own system before sending the request.
Pattern
const idempotencyKey = crypto.randomUUID()
const merchantReference = `order_${myInternalOrderId}`
const auth = "Basic " + Buffer.from(`${publicKey}:${secretKey}`).toString("base64")
await fetch(`${base}/payment_intents`, {
method: "POST",
headers: {
Authorization: auth,
"Idempotency-Key": idempotencyKey,
"Content-Type": "application/json",
},
body: JSON.stringify({
amount: "10000",
currency: "MYR",
country: "MY",
merchant_reference: merchantReference,
customer: { email: "aisyah@example.com" },
payment_method: "FPX",
}),
})Persist idempotencyKey next to your internal order before you POST. On retry after a crash, replay the same key. The platform replays the response.