Idempotency
Every call that moves money carries a key you choose:
X-Idempotency-Key: order-2026-09-03-00417Required on POST /payments and POST /payouts. 8 to 128 characters.
The rule
Section titled “The rule”| You send | Moria does |
|---|---|
| Same key, same body | Replays the stored result. No second payment, no second transfer. |
| Same key, different body | Refuses with 409. The key is already spoken for. |
| New key | A new operation, every time. |
So: one fresh key per distinct thing you want to happen. When retrying, keep the same financial intent and send the same request body. The signature must cover the exact bytes sent on each attempt.
Derive it from something in your own system that is already unique — an order id, an invoice number — rather than a random value you would have to store to reuse.
The retry that charges twice
Section titled “The retry that charges twice”What to do instead:
- Retry with the same key. A completed request returns its prior result. If the outcome is still uncertain, you may receive
PROVIDER_INDETERMINATEagain; do not switch keys. - If you have the transaction ID, poll
GET /payments/{id}orGET /payouts/{id}. If a payment response was lost before you received its ID, searchGET /payments?external_ref=...using the uniqueexternal_refyou supplied. An uncertain payment may have nopayment_codeyet; do not present it as payable. - Wait for a terminal state to treat money as final. A payment also needs a non-null
payment_codebefore you can give the customer payment instructions. Contact Moria support with your key, reference andrequest_idif you cannot identify or use the pending order. Never create a second operation to resolve uncertainty.
Why this exists on our side too
Section titled “Why this exists on our side too”Moria records the key before dispatch and writes the response against it when the work completes. If the payment was committed but the response never reached you, a retry can rebuild the answer from the order once its payment instrument is known. An uncertain provider outcome remains unresolved until Moria can establish what happened.
It also means a 409 is informative rather than annoying. It says the key you sent is already attached to a different request — usually a bug where a key is being reused across orders, which is exactly the bug you want reported loudly rather than silently honoured.
Not idempotency
Section titled “Not idempotency”Two things that look related and are not:
- A retried webhook. That is at-least-once delivery, deduplicated on the event
id, and it is a different mechanism — see Webhooks. - A duplicate customer payment. If a customer genuinely pays a virtual account twice, that is two payments and you will hear about both. Idempotency governs your instructions to us, not your customer’s behaviour.