How to Prevent Duplicate MT5 Orders From Retries and Double Clicks
Why a retry or a double click can open two MT5 positions, and how one request identifier — an idempotency key — makes every order safe to send again.
In short
- A duplicate happens when the same decision is sent twice: a double click, a retry after a timeout, a webhook delivered twice.
- The fix is one identifier per decision, sent with every attempt. fxapis calls it the Idempotency-Key and honours it for 24 hours.
- The same key returns the first result instead of placing a second order — even while the first is still running.
- Make the key when the decision is made, not when the request is sent. A new key on each retry protects nothing.
On this page
A user taps Buy. Nothing seems to happen, so they tap again. Two requests leave their phone, and they end up with two positions where they wanted one. The fix is one identifier per decision, sent with every attempt — an idempotency key.
Where duplicates come from
The same decision gets sent more than once in ordinary ways:
- A double click or double tap on a slow screen.
- A retry after a timeout. Your request reached the broker, the answer did not reach you, and your code tried again.
- A webhook delivered twice by the system that sent it.
- A queue job run twice after a worker restarts.
The timeout is the hard one. When a request times out, you do not know whether the order was placed. Retrying risks a second position; not retrying risks none at all.
One key per decision
Send an Idempotency-Key header with every order. fxapis remembers the key for 24 hours:
| Situation | What you get |
|---|---|
| Same key, same order, first call finished | The first response, replayed — no second order |
| Same key, first call still running | 409 IDEMPOTENCY_IN_FLIGHT — try again shortly |
| Same key, a different order | 409 IDEMPOTENCY_KEY_REUSED — refused |
| New key | A new order |
So a retry with the same key is always safe, whether or not the first attempt reached the broker.
The double-click example
The key has to be made when the user decides, not when the request is sent. Make it once when the order screen opens or the button is first pressed, and reuse it for every send that follows:
// One key per decision: created once, reused by every click and every retry.
const orderKey = crypto.randomUUID();
async function buy() {
const res = await fetch("/api/orders", {
method: "POST",
headers: { "Content-Type": "application/json", "Idempotency-Key": orderKey },
body: JSON.stringify({ symbol: "EURUSD", side: "buy", volume: "0.10" }),
});
return res.json();
}
Your backend passes the same key on to fxapis. Two taps send the same key, and the second gets the first one's result. Disabling the button helps too, but only the key also covers retries and timeouts.
The common mistake is generating the key inside the retry loop. A new key on every attempt tells the server each attempt is a new order — exactly what you were trying to prevent.
Picking the key
A random UUID per decision works for most cases. When the decision has a natural identity, build the key from it, so the same decision always maps to the same key:
- A member taking a signal:
signal_9931:member_4821. - A copier following a master trade: master deal + follower account.
- A queued job: the job's own id.
Open, close and each partial close are separate decisions, with separate keys.
The one result you must not retry
An idempotency key makes retries safe; it does not make every answer a reason to retry. If an order comes back ORDER_UNRESOLVED, fxapis does not know whether the broker executed it. Do not resend it under a new key. Poll the order — fxapis confirms it against the broker's records and moves it to what actually happened.
And when an order is rejected with retryable: true — the price moved — sending again is a new decision, so it gets a new key. Resending under the old key only replays the rejection.
Next steps
- Idempotent MT5 orders — replays, the unknown outcome, key patterns for signals, copiers and queues, and a checklist.
- The three failures — which errors are safe to resend.
- Copy trading — one order across many accounts, each with its own result and no duplicates.