Skip to main content
Two transactions exist for what should have been one order — a shopper says they were charged twice, or your own reconciliation shows two successful payments against the same order.
TL;DR — Two real transactions with different request_ids both succeeded — that’s a genuine duplicate, not a replay. Void the extra one while its settlement batch is still open, or refund it once the batch closes.

When this happens

  • Your server retried a purchase or authorize call after a timeout or network error, but generated a new request_id for the retry instead of reusing the one from the first attempt. RadiumOne has no way to know the two calls were the same attempt, so both go through as separate, legitimate transactions.
  • A double-click, a page refresh, or a resubmitted form reaches your server twice, and your server builds a fresh payment call — with its own request_id — each time, instead of recognizing it’s the same attempt.
  • You’re integrating through Hosted checkout or Elements and the duplicate started upstream of the Payments API call itself — see Prevent duplicate sessions and double payments or Prevent double submission.
This is different from an idempotency conflict. Idempotency is the mechanism that stops a repeated request_id/operation_id from charging twice; a duplicate payment is the problem it exists to prevent. It shows up here specifically because the same attempt got two different keys, so nothing on RadiumOne’s side ever saw them as the same request. A 409 idempotency-body-mismatch or 409 tx:duplicate-operation is actually the safe outcome — it means a reused key was caught before a second charge went through. See that page for what those responses mean and how to resolve them.

What you see

  • Two transactions with different ids and different request_ids, both AUTHORIZED or CAPTURED, for the same order.
  • Two separate webhook deliveries — payment.captured or authorization.created twice, each with a different event id and a different data.transaction.id. That’s not the same as one event delivered twice: a repeated event id for the same transaction is a redelivery, not a duplicate charge — see Recover from missed, duplicate or out-of-order webhooks.

What to do

1

Confirm it's a real duplicate, not a replay or a redelivery

Check both transaction ids (API reference):
Two distinct ids, each with its own request_id, both successful — that’s a genuine duplicate. The same id reported twice is a webhook redelivery, not a double charge; see Recover from missed, duplicate or out-of-order webhooks instead.
2

While the batch is still open, void the extra transaction

(API reference)
3

Once the batch has closed, refund the extra transaction instead

(API reference)
See Resolve void and refund conflicts if you’re not sure which side of the batch boundary you’re on.
4

Fix the retry path so it doesn't happen again

Persist the request_id before you send the first call, and reuse that exact value on every retry of the same attempt — see Prevent it below.

Prevent it

  • Generate one request_id per payment attempt, save it to your database before you send the request, and reuse the exact same value for every retry of that attempt — never mint a new one just because the first call was slow or failed.
  • Give capture and void each their own operation_id. They’re different operations on the same transaction, so reusing one operation’s key for the other returns 409 tx:duplicate-operation instead of doing what you meant — it doesn’t create a second charge, but it does mean your retry logic needs its own key per operation type.
  • If a call times out or fails without a response, resend it with the same key — a replay returns the original result instead of charging again, so it’s always safe (see Handle timeouts and unknown outcomes). Start a new attempt with a new key only once you know the first one didn’t succeed — from the confirming webhook (recommended) or status inquiry.
  • Disable your pay action for the whole attempt, not just the network call, so a double-click can’t reach your server twice with two different keys.
See Prevent duplicate payments for the full cross-product pattern, including the checklist and anti-patterns table.

Test it

See Test your integration: idempotent replay and body mismatch to confirm your retry path reuses the key correctly before you go live.

Handle failures

Back to every Payments API failure scenario.

Handle replays and idempotency conflicts

What happens when a key is reused correctly — or incorrectly.

Prevent duplicate payments

The cross-product guide, checklist, and anti-patterns.

Resolve void and refund conflicts

Void vs refund, and the batch-boundary rule.
Last modified on September 15, 2026