Guide
Idempotency
Retry money calls safely. One UUID key per payment attempt, reused on every retry.
POST /payments requires an Idempotency-Key header. It is what guarantees that
a retry never creates a second payment: delivery is at least once, the effect is
exactly once.
POST /v1/payments HTTP/1.1
Host: api-sandbox.mojaswitch.com
Authorization: Bearer sk_sandbox_…
Idempotency-Key: 6f1c8a52-3b0e-4d7a-9c41-2e8f5b6a9d10
Content-Type: application/jsonThe rules
- One key per payment attempt. Generate a UUID (v4) when you decide to make the payment.
- Persist it before the first request, next to your own transaction record. If your process dies mid-request, the retry must find and reuse the same key.
- Retry with the same key and the same body on a timeout, a dropped
connection, a
5xxor a429. That is always safe. - Never reuse a key for a different payment. Keys do not expire: a key used once stays bound to its payment.
What a retry returns
| Situation | Result |
|---|---|
| New key | 201 with the new payment |
| Same key, same body | 200 with that payment as it is now, not a copy of the first response. No second payment, no second collection. |
| Same key, different body | 409 idempotency_key_conflict. Nothing is created. |
Different key, same reference |
409 duplicate_reference. reference is unique per partner and is a second guard. |
| Header missing | 400 invalid_request. Nothing is created. |
| Header is not a UUID | 400 invalid_request. Nothing is created. |
"Same body" means the same JSON content: key order and whitespace do not matter.
A replay is answered before any business rule runs. A retry therefore returns the original payment even if your configuration changed in between, and a quote used by that payment is never consumed twice.
The key namespace is your partner account
Keys are scoped to (partner, key). end_user_id does not narrow the scope.
If you send on behalf of many end users, keep keys unique across all of them,
for example by deriving the key from the end user and your own attempt id.
When a payment fails
- A
201withstatus: FAILEDis a real, recorded payment. Retrying the same key returns that sameFAILEDpayment. To try again, create a new attempt with a new key. - A
500onPOST /paymentscan carry the Payment itself rather than an error envelope: the payment was persisted, then processing failed. Tell them apart by the body (a payment hasidandstatus; an envelope haserror). Retrying the same key returns the stored payment, never a second one. - Refusals that create nothing (
403,422,409) can be retried with the same key once the cause is fixed. Only409 idempotency_key_conflictneeds a new key, because the body changed.
Example
IDEM="$(uuidgen | tr '[:upper:]' '[:lower:]')" # persist this with your record first
curl -sS -X POST "$MOJA_BASE/payments" \
-H "Authorization: Bearer $MOJA_KEY" \
-H "Idempotency-Key: $IDEM" \
-H 'Content-Type: application/json' \
-d @payment.jsonRun the same command again with the same $IDEM and payment.json: you get
200 and the same payment id.
Only POST /payments honours Idempotency-Key. Other routes ignore it.
