← Decline Codes
Soft Decline

Stripe Decline Code · Glossary

duplicate_transaction

duplicate_transaction fires when Stripe or the issuing bank detects that an identical charge — same card, same amount, same merchant — has already been successfully processed within a short time window, and blocks the second attempt as a protective measure against accidental double-billing.

Recoverable·Updated May 2026

What It Means

What duplicate_transaction actually means.

duplicate_transaction fires when Stripe or the issuing bank detects that an identical charge — same card, same amount, same merchant — has already been successfully processed within a short time window, and blocks the second attempt as a protective measure against accidental double-billing. Unlike almost every other decline code which signals a payment problem, duplicate_transaction is a safeguard, not a failure — it means the original payment very likely went through, and the second attempt was correctly stopped.

Not sure if this code is recoverable for your specific situation? Use the Stripe Failure Lookup →

Why It Happens

The root causes.

  • 1Webhook retry loop firing a second chargeYour billing system receives a payment_failed webhook, doesn't properly check whether the original charge succeeded, and triggers a retry that Stripe correctly identifies as a duplicate — the most common developer-side cause in SaaS billing systems
  • 2Customer double-clicking the checkout buttonOn one-time or upgrade payment flows, a customer clicks "Pay" twice before the first request resolves — both requests hit Stripe simultaneously with identical parameters, triggering a duplicate block on the second
  • 3Race condition in your billing codeTwo concurrent processes (a webhook handler and a scheduled retry job) both attempt to charge the same invoice at the same time — Stripe's idempotency system catches and blocks the second attempt
  • 4Manual retry fired too quickly after an automatic attemptA support team member or founder manually retries a charge seconds or minutes after an automatic retry already processed — the rapid same-card, same-amount sequence triggers the duplicate detection window
  • 5Idempotency key not implemented or reused incorrectlyYour Stripe API calls don't use idempotency keys, or the same key is reused across different billing events — causing Stripe to misidentify new legitimate charges as duplicates of prior transactions

What NOT to Do

Common mistakes that make it worse.

Don't treat it as a payment failure and trigger dunning

duplicate_transaction almost always means the original charge succeeded — firing a "your payment failed" dunning email to a customer whose card was correctly charged creates confusion, destroys trust, and drives unnecessary support tickets and refund requests. Always verify the original charge status in your Stripe dashboard before any customer-facing action.

Don't immediately retry the blocked duplicate

The duplicate detection window at most issuers runs 10–30 minutes. Retrying within that window produces the same duplicate_transaction block. More critically, if you retry without first checking whether the original charge succeeded, you risk actually double-charging a customer — an error that generates chargebacks, refund requests, and irreparable trust damage.

Don't ignore it as a harmless false alarm

While duplicate_transaction is a protective block, its frequency in your Stripe logs is a code health signal — consistent occurrences indicate your billing architecture has a race condition, missing idempotency key implementation, or a broken webhook handler that needs engineering attention. Normalizing it as background noise leads to compounding billing integrity issues.

Retry Timing

Optimal retry schedule.

duplicate_transaction has no retry timing in the traditional sense — the recovery path is entirely a diagnostic and engineering response, not a payment retry schedule.

Recovery Benchmark

What good looks like.

MetricResult
Original charge already succeeded (most common)~70–80% of all occurrences
Both charges failed — genuine billing error~15–20% of occurrences
Customer actually double-charged~5–10% of occurrences
Recovery with idempotency key implementationEliminates ~80% of future occurrences
Engineering fix time to resolutionTypically 1–3 days once identified
Chargeback risk if double-charge not proactively refunded40–60% escalation rate

Zero duplicate_transaction occurrences in your Stripe logs. This is the one decline code where the recovery rate benchmark is irrelevant — the only meaningful KPI is occurrence rate reduction through engineering. A properly implemented idempotency key system reduces occurrences to near-zero. If you're seeing this code more than once per billing cycle, it's a billing architecture issue, not a payment problem.

At Scale

How to handle it at scale.

Automated

  • Webhook trigger: invoice.payment_failed → check failure_code === 'duplicate_transaction' → immediately query the Stripe API for the original charge outcome before taking any action — stripe.charges.retrieve(original_charge_id)
  • Auto-resolution if original succeeded: If the original charge shows status: succeeded → mark the invoice as paid in your system → close the event → no customer notification → log the duplicate occurrence for engineering review
  • Engineering alert: Every duplicate_transaction event should trigger an automatic engineering alert (Slack webhook, PagerDuty) — this code is a billing system health signal that requires developer attention, not just a customer workflow
  • Idempotency key enforcement: Audit every Stripe API charge call in your codebase — every stripe.charges.create() or stripe.paymentIntents.create() must pass a unique idempotency key; implement a code review rule that flags any charge call missing this parameter
  • Checkout double-click prevention: On all checkout and payment flows, disable the pay button immediately on first click and show a loading state — prevents client-side duplicate submissions at the source
  • Webhook deduplication layer: Implement a webhook event deduplication check using the Stripe event ID — store processed event IDs in Redis or your database and skip reprocessing if the ID already exists; this eliminates race-condition-driven duplicates from webhook retry logic

Manual Escalation

  • Double-charge confirmed: If a customer was actually charged twice, a proactive refund + personal apology email must go out within 2 hours — before the customer notices on their bank statement. A proactive response here has a near-zero chargeback conversion rate; a reactive response (waiting for the customer to complain) has 40–60% chargeback escalation
  • Recurring duplicate_transaction pattern: If occurrences spike above baseline, assign to a senior engineer immediately — a systematic race condition or webhook loop can compound quickly and result in batch double-charges across multiple customers simultaneously
  • Post-incident audit: After any duplicate_transaction incident, run a full audit of that billing cycle's charge history to confirm no customers were double-charged — even one missed double-charge that results in a chargeback triggers Stripe's dispute ratio monitoring

FAQs

Frequently asked questions.

What does the Stripe duplicate_transaction decline code mean?

duplicate_transaction means Stripe or the issuing bank detected that an identical charge — same card, same amount, same merchant — was already processed or attempted within a short time window, and blocked the second attempt as a protective measure. In most cases, the original charge succeeded and no customer action is needed. Always verify the original charge status before taking any action.

What are the most common causes of a duplicate_transaction error in Stripe?

Common causes include a webhook retry loop firing a second charge without checking whether the original succeeded, a customer double-clicking the pay button at checkout, a race condition between concurrent billing processes, a manual retry fired too quickly after an automatic attempt, and missing or incorrectly reused idempotency keys in Stripe API charge calls.

Does duplicate_transaction mean my customer's payment failed?

Not necessarily. In 70–80% of cases, the original charge succeeded and the duplicate_transaction code is blocking a second, redundant attempt. Always query the original charge status in the Stripe API before triggering any customer notification or retry logic. Sending a payment failure email to a customer whose original payment succeeded is damaging to trust and drives unnecessary refund requests.

How do I prevent duplicate_transaction errors in Stripe?

Implement unique idempotency keys on every Stripe charge API call — tied to the specific invoice ID and billing cycle date. Add webhook event deduplication using Stripe event IDs to prevent reprocessing. Disable checkout pay buttons on first click to prevent double submissions. Implement a race condition guard in your billing code so concurrent processes cannot fire simultaneous charge attempts for the same invoice.

What should I do if a customer was double-charged due to a duplicate_transaction error?

Issue a proactive refund and send a personal apology email within 2 hours — before the customer notices on their bank statement. Proactive refunds have near-zero chargeback escalation rates. Waiting for the customer to complain results in 40–60% chargeback escalation rates, plus dispute fees and damage to your Stripe dispute ratio.

Before you retry

Most duplicate_transaction failures are retried on the wrong schedule — which recovers the payment about 30% of the time. The other 70% leaves permanently. See what this code is actually costing at your MRR before deciding how to handle it.

See what duplicate_transaction costs me →

Stop leaving revenue on the table

duplicate_transaction is recoverable. Most teams don't have the retry logic to prove it.

Recurflux handles code-specific retry scheduling, adaptive dunning, and dispute intelligence across all 30 Stripe decline codes. Connect in under 5 minutes.