> For the complete documentation index, see [llms.txt](https://docs.reya.xyz/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.reya.xyz/developers/devnet/order-entry/cancel-on-disconnect.md).

# 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](/developers/devnet/api-reference/rest-api-reference.md) and as the `cancelAllAfter` message type over [WebSocket Order Entry](/developers/devnet/api-reference/ws-exec-api-reference.md).

## 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](#protective-stops)).

The cancellations surface as `CANCELLED` order changes on the [`orderChanges`](/developers/devnet/api-reference/websocket-api-reference.md) channel, the same as any other cancel.

{% hint style="warning" %}
**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](#recommended-use)).
{% endhint %}

## `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 `cancelAllAfter` with a `timeoutMs` in range. The countdown starts.
* **Refresh** — send `cancelAllAfter` again before the countdown elapses. Each call **replaces** the deadline (last-write-wins); re-sending the same `timeoutMs` is a valid refresh.
* **Disarm** — send `cancelAllAfter` with `timeoutMs = 0`. The countdown is cleared.

## Account scope

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 on devnet1. Do not assume a disconnect removes protective orders or prevents them from trading; see [Trigger Orders (SL/TP)](/developers/devnet/order-entry/trigger-orders.md).

## 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:

| Field          | Type    | Description                                                                                              |
| -------------- | ------- | -------------------------------------------------------------------------------------------------------- |
| `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).                                                          |
| `nonce`        | string  | Signature nonce — see [Signatures & Nonces](/developers/devnet/authentication/signatures-and-nonces.md). |
| `signerWallet` | string  | The wallet that produced the signature.                                                                  |
| `deadline`     | integer | EIP-712 signature-validity window, in **unix seconds**. This is **not** the trigger time.                |

{% hint style="info" %}
`deadline` is only the signature-validity window — how long the *signed request* is accepted for. The time the switch will fire is `triggerAt` in the response, derived from `timeoutMs` on the exchange clock. The two are unrelated.
{% endhint %}

### 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](/developers/devnet/authentication/signatures-and-nonces.md) 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](/developers/devnet/connectivity/heartbeats.md), which keeps the *connection* alive but never touches the COD timer.

## Errors

| Error code                          | When                                                                             |
| ----------------------------------- | -------------------------------------------------------------------------------- |
| `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.                       |
| `UNAVAILABLE_MATCHING_ENGINE_ERROR` | The matching engine is temporarily unavailable — retry after a short backoff.    |
| `NUMERIC_OVERFLOW_ERROR`            | A numeric field overflows its expected range.                                    |
| `UNAUTHORIZED_SIGNATURE_ERROR`      | The signature doesn't validate against the request contents and `signerWallet`.  |
