Idempotency Keys
Make every POST safe to retry. Send an Idempotency-Key. Use merchant_reference for application-level deduplication.
Two layers protect a merchant from accidental duplicate intents on a network retry: the Idempotency-Key header and the merchant_reference field. Use both. They cover different failure modes.
The Idempotency-Key header
Most Merchant API POST routes accept an optional Idempotency-Key header. POST /topup_intents requires it. The server stores the request hash and the final response under (api_key_id, key); a second request with the same key replays the stored response.
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 keys can use the same idempotency string without collision. - The key is held for 24 hours after the first request, then expires.
- A repeat with the same key but a different request body 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 POST that creates or cancels an intent. Network retries are
normal; a retried create without the key can produce a duplicate intent.
merchant_reference on intents
merchant_reference is your application's order or payout id. The platform enforces uniqueness on (merchant_id, merchant_reference) per intent resource. Two semantics fall out:
- A repeat
POST /payment_intentswith the samemerchant_referencereturns the same original intent rather than creating a duplicate. - A repeat
POST /payout_intentswith the samemerchant_referencereturns the same original intent rather than creating a duplicate. - A
POSTwith a conflictingmerchant_referencereturns409 duplicate_merchant_reference.
The header protects against network-level retries inside the 24-hour window. merchant_reference protects against application-level duplicates across deploys, days, and process restarts beyond that window. The combination means the same logical intent can be retried indefinitely without creating a second row.
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: "[email protected]" },
}),
});Persist idempotencyKey next to your internal order before you POST. On retry after a crash, replay the same key. The platform replays the response.