Practical guide
How to prevent duplicate payment or order processing
The same event can arrive twice. That is normal. Your application needs to safely say: I have already processed it.
First, the short version
Why do duplicates happen?
Webhook and other external messages can repeat because of a network failure, slow response, or automatic retry. A person can also click twice.
Idempotency means the same operation has the same result even when the application receives it repeatedly. For a payment, it often means one payment, one order, and one email.
Get ready
What you need to know
Do not look for a duplicate by time or amount. You need a stable identifier for the same event.
- An event ID from the payment provider, or your own unique key for the request.
- A place in the database where you record that the event has already arrived.
- A clear rule for the second delivery: do not create anything again; safely return the original result.
- A test event you can send twice without charging real money.
Step 1
Build protection in three layers
Do not rely on a code check alone. Two identical requests can arrive at the same moment.
1. Choose the right key
- Use the payment provider’s event ID. For your own action, create a key when it begins and send it with the request.
- The key must stay the same when the same event is repeated. A new attempt at another payment needs a new key.
- Do not derive it from price, email, or time. Those values are not unique enough.
2. Store the key with a uniqueness rule
- Create a processed-event record with its ID and result.
- Add a unique database constraint to the ID. This is the final safeguard when two requests pass a code check at the same time.
- Save the event and change the order in a single database transaction.
3. Finish the second delivery safely
- If you already know the key, do not create another payment, order, or email.
- Return a successful response or the original processing result. The sender then does not need to keep retrying.
- Record that it was a duplicate. This makes it clear during debugging that the protection works.
Step 2
Check the protection with two attempts
One successful request is not enough. You need to see what happens when it repeats.
-
Send the same event twice
Use the same event ID. Both requests can succeed, but the database must contain one result only.
-
Check side effects
Check the number of payments, orders, and emails. No second record or second notification may appear.
-
Try concurrent delivery
Send the same event at the same time in two requests. This is where the uniqueness rule and transaction must protect you.
If something goes wrong
Common problems
The check works for one request but not concurrently
Only checking first and inserting after is not enough. Two requests can pass the check at the same time. You need a unique database constraint.
The payment was saved, but the order was not
Save related changes together in a database transaction. If one part fails, the data must not look complete.
The application rejects another payment as a duplicate
You are probably reusing one key for different events. Check that every new payment or order gets its own stable ID.
An unverified webhook changes an order state
First verify the origin of an incoming webhook using the provider’s rules. Only then store its ID and start processing.
Done
The second attempt no longer causes damage.
Idempotency protects both your customer and you. Treat it as a normal design step for important events, not as a later repair.