Cancel-on-Disconnect
Cancel-on-Disconnect (COD) is an account-scoped dead-man's switch built on cancelAllAfter. You arm a countdown; if you don't refresh it before the countdown elapses, the matching engine cancels every open order on the account. It's the safety net for "my client died — don't leave my orders sitting on the book."
It is exposed as POST /v2/cancelAllAfter over REST and as the cancelAllAfter message type over WebSocket Order Entry.
What it does
You arm the switch by sending a signed cancelAllAfter with a timeoutMs. While armed, the matching engine holds a countdown for your account. If you send another cancelAllAfter before it elapses, the countdown is reset (last-write-wins). If you don't, the countdown fires and the matching engine cancels every open order on the account — all markets, spot and perp — the same scope as cancelAll with no symbol filter, with one deliberate exception: protective stops are kept (see Protective stops).
The cancellations surface as CANCELLED order changes on the orderChanges channel, the same as any other cancel.
COD is not triggered by a WebSocket close. Closing or dropping the socket neither fires nor disarms the timer — only the countdown elapsing fires it. Order-entry traffic and pings do not refresh it; only another cancelAllAfter does. If you want your orders cancelled when your connection dies, you must arm a short timeout and refresh it on your own heartbeat (see Recommended use).
timeoutMs and bounds
timeoutMs
Effect
0
Disarm. Cancels the armed countdown; nothing will auto-cancel.
5000–60000
Arm / refresh. Sets the countdown to this many milliseconds (5–60 seconds, inclusive).
Any non-zero value outside [5000, 60000] is rejected with INPUT_VALIDATION_ERROR. Nothing is armed and any existing countdown is left unchanged.
Arm, refresh, disarm
Arm — send
cancelAllAfterwith atimeoutMsin range. The countdown starts.Refresh — send
cancelAllAfteragain before the countdown elapses. Each call replaces the deadline (last-write-wins); re-sending the sametimeoutMsis a valid refresh.Disarm — send
cancelAllAfterwithtimeoutMs = 0. The countdown is cleared.
Account scope
Countdown control has an account allowance separate from create/modify and cancellation requests. A rate-limited refresh leaves the previous countdown unchanged; track the last acknowledged triggerAt. Automatic cancellation on expiry is not blocked by an exhausted bulk-cancel allowance. Connection and IP limits still apply: the REST trading bucket includes countdown refreshes. See Rate Limits for the agreed levels and disarm behavior.
The timer is per account, not per connection or per order. One armed countdown covers every open order on the account across all markets (spot and perp).
Protective stops
The dead-man's switch preserves both armed triggers and fired protective children. Armed triggers remain outside the book; fired GTC/GTT children can remain on the book and execute. User cancelAll cancels both within its scope.
Trigger firing is live. Do not assume a disconnect removes protective orders or prevents them from trading; see Trigger Orders (SL/TP).
Transport-agnostic
REST and WebSocket Order Entry arm the same account-level matching-engine timer — there is one canonical deadline per account. You can arm over REST and refresh over WebSocket (or vice versa); the last cancelAllAfter to arrive, on whichever transport, wins.
Request fields
All fields are required:
accountId
integer
The account whose orders are governed by the switch.
timeoutMs
integer
0 to disarm, otherwise 5000–60000 (inclusive).
signature
string
EIP-712 signature over the request (see below).
signerWallet
string
The wallet that produced the signature.
deadline
integer
EIP-712 signature-validity window, in unix seconds. This is not the trigger time.
Signing
The EIP-712 envelope signs the full CancelAllAfter struct — verifyingChainId, deadline, accountId, timeoutMs, and nonce (verifyingChainId binds the signature to the target chain). Use a fresh nonce per request. See Signatures & Nonces for the signing model and the Python SDK helpers.
Response
A successful arm/refresh echoes timeoutMs and returns triggerAt — the time the switch will fire, on the exchange clock.
A successful disarm (timeoutMs = 0) returns no triggerAt.
Recommended use
Arm a short timeout and refresh it on your own heartbeat — comfortably faster than the timeout you chose. As long as your client is healthy it keeps refreshing the countdown and nothing happens; the moment it stops (process crash, network partition, hang), the countdown elapses and the matching engine cancels your orders for you.
Because the timer is refreshed only by cancelAllAfter — not by your order flow or by protocol pings — your refresh loop has to send cancelAllAfter explicitly. This is independent of the WebSocket heartbeat, which keeps the connection alive but never touches the COD timer.
Errors
INPUT_VALIDATION_ERROR
A missing or malformed field, or a non-zero timeoutMs outside [5000, 60000].
CANCEL_ALL_AFTER_OTHER_ERROR
A matching-engine-side failure with no more specific code.
SERVICE_UNAVAILABLE_ERROR
Not accepted: back off and re-sign with a fresh nonce.
ORDER_OUTCOME_UNKNOWN_ERROR
API 3.6.2: the timer may have been armed, refreshed, or disarmed. Open orders cannot establish timer state; another refresh changes the deadline. See Handling ambiguous outcomes.
NUMERIC_OVERFLOW_ERROR
A numeric field overflows its expected range.
UNAUTHORIZED_SIGNATURE_ERROR
The signature doesn't validate against the request contents and signerWallet.
Last updated