TL;DR — Same key, same body → the original result, whatever its status (
201/200, even PENDING). Same key, different body → 409. Which 409 depends on the key: request_id compares the body; operation_id never does.When this happens
- A byte-identical replay: the same
request_id(purchase, authorize, refund, standalone refund) oroperation_id(capture, void) with the same body. This is the safe, expected retry path after a timeout. Balance inquiry also takes arequest_idfield, but it’s not deduplicated — see the note below. - A body-mismatch replay: the same
request_idwith a different body —409 urn:radiumone:transaction:idempotency-body-mismatch. - A cross-operation replay: the same
request_idfirst used for one operation type (say, a purchase) and then sent to a different operation (say, a referenced refund) — also409 urn:radiumone:transaction:idempotency-body-mismatch. Arequest_idis scoped to the operation type it was first used for. - A conflicting operation replay: the same
operation_idreused for a different operation type on the same transaction (for example, a capture’s key reused for a void) —409 urn:radiumone:tx:duplicate-operation.
How the gateway decides
- A request arrives carrying a
request_idoroperation_id. - If your merchant account hasn’t used that key before, the gateway creates a new transaction.
- If the key was used before and it’s a
request_id(purchase, authorize, refund, standalone refund): the gateway comparesamount,card,channel,metadata, andorder_referenceagainst the first attempt, and confirms the key was used for the same operation type both times. It does not comparethree_dsorloyalty— changing either on a retry replays the original silently instead of failing.- Match, and the first attempt already finished → the original result, at the original HTTP status — whatever the status, including
DECLINEDorFAILED. - Match, and the first attempt is still processing → the existing transaction comes back, usually
PENDING, with itsid— not a new transaction. Poll status or wait for the webhook. - Mismatch on any of the compared fields, or the same key reused for a different operation type →
409 idempotency-body-mismatch.
- Match, and the first attempt already finished → the original result, at the original HTTP status — whatever the status, including
- If the key was used before and it’s an
operation_id(capture, void): the gateway only checks whether it’s the same operation type on the same transaction — the body is never compared.- Same operation type → the original result, at the original HTTP status, regardless of what you sent this time.
- A different operation type reusing the key →
409 tx:duplicate-operation.
Referenced refunds match a narrower set of fields: the same original transaction, the same
amount, and a key already used for a refund — reason isn’t compared. The replay check runs before the batch/window/cap gates, so a retry always replays (never 422), even if another refund changed the refundable amount in between. Because a DECLINED or FAILED refund replays too, retry after a decline with a new request_id, not the same one. A wrong transaction_id in the path returns 404 before the replay check runs.What you see
What to do
1
Resend byte-identical for a genuine retry
If you’re retrying after a timeout or a The response is the original result, at the original HTTP status — this is always safe to do, any number of times.
5xx, resend the exact stored body with the exact same key (API reference):2
Fix the body if you get a mismatch
A
409 idempotency-body-mismatch means a request_id was reused with a body that doesn’t match the first attempt. Don’t retry as-is — resend the original body, or mint a new request_id if this is genuinely a different attempt (a different card, a different amount).3
Use one key per attempt, and one per operation
Generate one
request_id per order attempt and one operation_id per operation (a capture and a later void on the same transaction are two different operations — they each need their own key). Reusing an old key for a different attempt or a different operation type is what causes a conflict in the first place.Prevent it
- Generate the idempotency key once per attempt, before you send the first request, and reuse that exact value for every retry of that same attempt.
- Keep a local record of which key you used for which order attempt (and which operation), so a retry — even after a restart — reuses the right one.
- Never derive a key from mutable data (a timestamp, a request counter) that changes between retries — that guarantees a mismatch instead of a safe replay.
Test it
See Test your integration and Body mismatch for scenario coverage.Related
Prevent duplicate payments
The cross-product guide to avoiding duplicate charges.
Charge or authorize a payment
Idempotency rules for purchase and authorize.
Capture an authorization
Idempotency on
operation_id.Problem format and retries
The full retry-rules reference.