What happens when your order is sent but the exchange never replies?
Most order-handling code is written as if there are two outcomes: the order worked, or it didn't.
There is a third, it is more common than people expect, and it is the one that actually costs money. You send an order. The connection stalls, or the venue returns a shape you don't recognise, or a gateway hands back a 502. You now do not know whether you have a position.
Why this is not just an error
The reason this state is special is that sending an order is not reversible.
Once the request has physically left, the venue may have accepted it. It may be resting on the book. It may have filled already. None of that depends on whether your process survived long enough to hear about it. The information was lost; the order was not.
So the usual error-handling instinct — catch it, retry, move on — is actively dangerous here. Retrying an order that already exists does not repair anything. It places a second one. On a leveraged product that means double the intended position, at a price you never agreed to, discovered later.
This is why "did the failure happen before or after the request was transmitted?" is the only question that matters, and why it deserves to be tracked explicitly rather than inferred from the error text.
Naming the state
BT365 marks these orders ambiguous, as a first-class status distinct from rejected.
The distinction is enforced at the point where a venue response is parsed. If the response has a shape the parser does not recognise — a missing status block, an unknown discriminator — it does not fall through to a default and it does not treat the order as failed. It raises an error explicitly flagged as post-broadcast, and that flag is what turns the order ambiguous instead of rejected. The same flag is set for 429 and 5xx responses, because a rate-limit or gateway error arriving after transmission tells you nothing about the order's fate either.
The alternative — the version this replaced — was to write the order down with a null exchange id and call it submitted. That looks harmless and is not: a row claiming to be a submitted order while carrying no identifier is unusable. You cannot cancel it, cannot look it up, and cannot tell it apart from a real order whose id you simply haven't stored yet. Refusing to persist that row is the whole point.
Rejected means the venue said no. Ambiguous means the venue didn't say. Collapsing the two is how a system convinces itself it has no position while holding one.
Getting the answer later
An ambiguous order is not left ambiguous. It is a question to be answered, and the answer comes from the venue.
The mechanism that makes this possible is an identifier attached to the order before it is sent — a client-supplied order id. Because it is chosen in advance, it survives the lost reply. The exchange-assigned id may never have arrived, but the order can still be looked up by the id we gave it, and the venue's answer is authoritative: it filled, it is resting, it never existed.
A reconciliation pass runs over orders in an unresolved state and does exactly that, asking the venue by exchange id where one is known and by client id where it is not, then persisting whatever the venue says. Two details make it trustworthy rather than merely present. Transport failures during reconciliation are swallowed and retried later rather than being read as answers — a failed question is not a "no". And an explicit "no such order" response is treated as terminal, because that is the venue affirmatively answering, which is categorically different from failing to reach it.
There is one wrinkle worth knowing if you build against this kind of venue: fills do not necessarily carry the client id back. So you cannot always match a fill to your original request directly, and recovery works by first resolving the order to its exchange id, then linking fills through that. It is one indirection more than you would expect, and skipping it is how fills go unattributed.
Resting orders make the window longer
All of this gets worse the longer an order can live.
An immediate order resolves in the same breath — filled or not, you find out now, and the ambiguous window is milliseconds. A good-til-cancel order that rests on the book can fill hours later, or partially, or in pieces. The gap between "we sent this" and "we know what it became" is no longer a network round trip; it is however long the order sits there.
Which means reconciliation cannot be a one-shot cleanup after a failure. It has to be a routine that keeps running, because an order that was genuinely unfilled when you last asked can be filled the next time you do. A system that only reconciles on error will correctly recover from the timeout and then miss the fill that happens twenty minutes later.
The same shape everywhere
The reason to write this up is that it is not a quirk of one venue or one asset class. The identical pattern shows up in three unrelated parts of our own system, and it is the same discipline every time.
An outcome-market order whose response was lost is ambiguous. A treasury payout whose transaction hash was recorded but whose confirmation wait timed out stays submitted, and explicitly must never be re-signed — a replacement would be a second payment. A funding transfer that cannot be confirmed either way is marked ambiguous for a human to look at, rather than synthetically reversed, because a synthetic reversal of a transfer that actually succeeded is just a second, wrong transfer.
Three different money paths, one rule: when you have sent an instruction you cannot un-send and lost the reply, stop and ask. Never assume, and never retry.
The general form is worth keeping, because it applies to anything that moves value over a network you don't control. There is a difference between this did not happen and I do not know whether this happened, and a system that cannot represent the second one will eventually act as though it were the first.
BT365 runs prediction markets, perps, and spot from one self-custodial account, with venue-truthful order reconciliation on every position. Explore the platform or read the documentation.