Risk controls
Orders rest on the book whether or not you are watching them. These controls decide what happens to yours when you are not the one deciding: when your connection drops, when the game starts, when a time you picked passes, and when play goes live. Most you set yourself; the in-play delay the exchange applies to you.
None of them are on by default. An order placed with no expiration and no
cancel_on_disconnect rests on the book until you cancel it or the market
settles, whether or not your process is still running.
Order expiration
Section titled “Order expiration”expiration is an optional field on POST /api/v1/orders.
expiration |
The order expires |
|---|---|
| omitted | Never. It rests until you cancel it or the market resolves. This is the default. |
good_till_start |
When the event starts. |
good_till_time |
At expiration_time. |
expiration_time is Unix microseconds, not milliseconds or seconds, and is
required when expiration is good_till_time. Microseconds is unusual enough
that a value in milliseconds looks plausible and puts the expiry in 1970, which
expires the order immediately.
good_till_start is the one to reach for if you place orders pre-game and do not
want to trade in-play. It is keyed on the event, not the market, so every market
on one game expires together.
Cancel orders on disconnect
Section titled “Cancel orders on disconnect”If your process dies, your host sleeps or the network drops, the orders you left
on the book keep trading without you. The cancel_on_disconnect flag has the
exchange pull them when it stops hearing from your client, so a connection
failure does not leave orders working that you can no longer manage.
Unlike the others, you arm it over the WebSocket rather than on the order alone, and it takes both halves to work.
Arming it on the channel
Section titled “Arming it on the channel”Enabled when you join, not per request:
["1", "1", "orders:{user_id}", "phx_join", { "cancel_on_disconnect": true, "ping_timeout": 5000 }]| Parameter | Notes |
|---|---|
cancel_on_disconnect |
Boolean. Defaults to false. |
ping_timeout |
Milliseconds. Must be an integer; a non-integer fails the join with {"ping_timeout": "Must be an integer"}. Clamped to 5000–20000; values outside the range are silently pulled to the nearest bound. |
Once joined, send a ping on the channel before each ping_timeout elapses. The
server replies {"ping": "pong", "ttl": <ping_timeout>} and resets the clock.
Ping at roughly 60% of the window so a single slow round trip does not cost you
the connection.
This timer is not the socket’s own keep-alive. Phoenix closes an idle socket
after 60 seconds; ping_timeout is a separate, much shorter deadline that only
governs cancellation.
Opting an order in
Section titled “Opting an order in”POST /api/v1/orders{ "market_id": "855faa81-7115-4f4f-b492-389fbd8c1ed8", "order_type": "limit", "action": "buy", "price": "0.46", "quantity": "25", "cancel_on_disconnect": true}The body is flat. Wrapping the order in user_order, as some older examples do,
returns 400 market_id is required, which reads like the
field is missing when the real problem is the wrapper. See
Place an order.
The server echoes back the ping_timeout it actually applied, which is not
always the one you asked for:
{"cancel_on_disconnect": true, "ping_timeout": 15000}Read the value from that reply rather than assuming your request was honored.
What happens when you stop pinging
Section titled “What happens when you stop pinging”- The deadline passes and the server closes the
orderschannel: your client receives aphx_errorevent on that topic. The socket itself stays open. - A 20-second grace period starts. Reconnect inside it and your orders are untouched, which is what makes a brief network blip safe.
- If the grace expires, every order whose own
cancel_on_disconnectistrueis cancelled. Orders without the flag stay on the book.
An order flagged after your last ping is still covered: it is cancelled when
that ping_timeout expires. The same applies to orders placed once the channel
is already considered dead, so placing new orders does not stop a cancellation
that is already running.
Cancelling in bulk
Section titled “Cancelling in bulk”The manual equivalent, for when you are shutting down deliberately rather than failing:
DELETE /api/v1/orders/all |
Everything resting on your account. |
DELETE /api/v1/orders/batched |
A named list. The body takes orders, an array of objects each carrying an order_id, not a flat array of ids. |
A cancel is a request, not a guarantee. An order can fill in the moment between
you sending the cancel and the exchange processing it, so reconcile against
fills rather than assuming a cancel left you flat.
The in-play delay
Section titled “The in-play delay”This one you do not set. While an event is in_progress, incoming orders
on its markets are held in a queue before reaching the book. It exists so a
participant with a faster feed than the exchange cannot pick off prices that have
not caught up with play yet.
An order sitting in that queue reads as
delayed: accepted, holding liability, not yet
working.
Whether it applies to a given order depends on:
- The event must be
in_progress. There is no delay pre-game. - The market sets the duration. Read
in_play_delay_secfromGET /api/v1/markets; it is also pushed on themarket_updateschannel. The value is configured per sport, so it differs between a football market and a cricket one. Read the field rather than assuming a number. - The account must be subject to it. It can be switched off per account, in which case orders are never delayed regardless of the market’s value.
Because the duration is per market and the exemption is per account,
in_play_delay_sec on a market is the ceiling, not a promise. If you need to
know whether your own orders are being delayed, watch how long they sit at
delayed rather than computing it.

