> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sx.bet/llms.txt
> Use this file to discover all available pages before exploring further.

# Order Management

> Monitor your open orders in real-time, cancel orders, and set up a heartbeat to protect against connectivity loss.

## Overview

Once your orders are on the book, you need to track fills, react to market changes, and cancel orders that are no longer valid. This page covers the three tools for that: real-time order monitoring, order cancellation, and the heartbeat safety mechanism.

## Monitor your orders

Subscribe to the [`active_orders:{maker}` WebSocket channel](/api-reference/centrifugo-active-order-updates) to receive real-time updates when your orders are posted, partially filled, fully filled, or cancelled. Updates are batched and delayed by at most 100ms.

<CodeGroup>
  ```javascript JavaScript theme={null}
  import { Centrifuge } from "centrifuge";

  const RELAYER_URL = "https://api.sx.bet"; // Mainnet — use https://api.toronto.sx.bet for testnet
  const WS_URL = "wss://realtime.sx.bet/connection/websocket"; // Mainnet — use wss://realtime.toronto.sx.bet/connection/websocket for testnet

  async function fetchToken() {
    const res = await fetch(`${RELAYER_URL}/user/realtime-token/api-key`, {
      headers: { "x-api-key": process.env.SX_API_KEY },
    });
    if (!res.ok) throw new Error(`Token endpoint returned ${res.status}`);
    const { token } = await res.json();
    return token;
  }

  const client = new Centrifuge(WS_URL, { getToken: fetchToken });

  const sub = client.newSubscription(`active_orders:${wallet.address}`, {
    positioned: true,
    recoverable: true,
  });

  sub.on("publication", (ctx) => {
    for (const update of ctx.data) {
      console.log(`Order ${update.orderHash}: status=${update.status}, filled=${update.fillAmount}`);
    }
  });

  sub.subscribe();
  client.connect();
  ```

  ```python Python theme={null}
  import asyncio
  import aiohttp
  from centrifuge import Client, PublicationContext, SubscriptionEventHandler, SubscriptionOptions

  RELAYER_URL = "https://api.sx.bet"  # Mainnet — use https://api.toronto.sx.bet for testnet
  WS_URL = "wss://realtime.sx.bet/connection/websocket"  # Mainnet — use wss://realtime.toronto.sx.bet/connection/websocket for testnet

  async def fetch_token():
      async with aiohttp.ClientSession() as session:
          async with session.get(
              f"{RELAYER_URL}/user/realtime-token/api-key",
              headers={"x-api-key": os.environ["SX_API_KEY"]},
          ) as resp:
              data = await resp.json()
              return data["token"]

  async def main():
      client = Client(WS_URL, get_token=fetch_token)

      async def on_publication(ctx: PublicationContext) -> None:
          for update in ctx.data:
              print(f"Order {update['orderHash']}: status={update['status']}, filled={update['fillAmount']}")

      handler = SubscriptionEventHandler(on_publication=on_publication)
      options = SubscriptionOptions(positioned=True, recoverable=True)
      sub = client.new_subscription(f"active_orders:{account.address}", handler, options)

      await client.connect()
      await sub.subscribe()
      await asyncio.Future()

  asyncio.run(main())
  ```
</CodeGroup>

<Tip>Create only one `Centrifuge` instance per process. All subscriptions share the same connection — maximum 512 channel subscriptions per connection.</Tip>

You can also poll your open orders via [`GET /orders`](/api-reference/get-orders) with the `maker` parameter — useful for reconciling state on startup before subscribing.

Full payload reference: [Active Order Updates →](/api-reference/centrifugo-active-order-updates)

## Cancel orders

All cancellation endpoints require a private key signature. To update odds on a market, cancel the old order and post a new one — there is no in-place edit.

Three cancellation scopes are available:

| Endpoint                                                        | When to use                                         |
| --------------------------------------------------------------- | --------------------------------------------------- |
| [`POST /orders/cancel/v2`](/api-reference/post-cancel-orders)   | Cancel specific orders by hash                      |
| [`POST /orders/cancel/event`](/api-reference/post-cancel-event) | Cancel all orders tied to a specific event          |
| [`POST /orders/cancel/all`](/api-reference/post-cancel-all)     | Cancel everything — useful on shutdown or repricing |

Each endpoint uses the same pattern: generate a `salt` and `timestamp`, sign them with your wallet, and include the signature in the request. The domain name varies by endpoint — `"CancelOrderV2SportX"`, `"CancelOrderEventsSportX"`, or `"CancelAllOrdersSportX"`. See the API reference pages above for full signing code.

<CodeGroup>
  ```python Python theme={null}
  import os, secrets, requests
  from eth_account import Account

  BASE_URL = "https://api.sx.bet"  # Mainnet — use https://api.toronto.sx.bet for testnet
  CHAIN_ID = 4162  # Mainnet — use 79479957 for testnet

  account = Account.from_key(os.environ["SX_PRIVATE_KEY"])
  salt = "0x" + secrets.token_bytes(32).hex()
  timestamp = int(__import__("time").time())

  # Cancel specific orders by hash
  signed = Account.sign_typed_data(
      account.key,
      domain_data={"name": "CancelOrderV2SportX", "version": "1.0", "chainId": CHAIN_ID, "salt": salt},
      message_types={"Details": [{"name": "orderHashes", "type": "string[]"}, {"name": "timestamp", "type": "uint256"}]},
      message_data={"orderHashes": ["0xabc..."], "timestamp": timestamp},
  )
  requests.post(f"{BASE_URL}/orders/cancel/v2", json={
      "orderHashes": ["0xabc..."],
      "signature": "0x" + signed.signature.hex(),
      "salt": salt,
      "maker": account.address,
      "timestamp": timestamp,
  })

  # Cancel all orders for a specific event
  signed = Account.sign_typed_data(
      account.key,
      domain_data={"name": "CancelOrderEventsSportX", "version": "1.0", "chainId": CHAIN_ID, "salt": salt},
      message_types={"Details": [{"name": "sportXeventId", "type": "string"}, {"name": "timestamp", "type": "uint256"}]},
      message_data={"sportXeventId": "L15468276", "timestamp": timestamp},
  )
  requests.post(f"{BASE_URL}/orders/cancel/event", json={
      "sportXeventId": "L15468276",
      "signature": "0x" + signed.signature.hex(),
      "salt": salt,
      "maker": account.address,
      "timestamp": timestamp,
  })

  # Cancel all open orders
  signed = Account.sign_typed_data(
      account.key,
      domain_data={"name": "CancelAllOrdersSportX", "version": "1.0", "chainId": CHAIN_ID, "salt": salt},
      message_types={"Details": [{"name": "timestamp", "type": "uint256"}]},
      message_data={"timestamp": timestamp},
  )
  requests.post(f"{BASE_URL}/orders/cancel/all", json={
      "signature": "0x" + signed.signature.hex(),
      "salt": salt,
      "maker": account.address,
      "timestamp": timestamp,
  })
  ```

  ```javascript JavaScript theme={null}
  import { Wallet, hexlify, randomBytes } from "ethers";

  const BASE_URL = "https://api.sx.bet"; // Mainnet — use https://api.toronto.sx.bet for testnet
  const CHAIN_ID = 4162; // Mainnet — use 79479957 for testnet

  const wallet = new Wallet(process.env.SX_PRIVATE_KEY);
  const salt = hexlify(randomBytes(32));
  const timestamp = Math.floor(Date.now() / 1000);

  // Cancel specific orders by hash
  const sigV2 = await wallet.signTypedData(
    { name: "CancelOrderV2SportX", version: "1.0", chainId: CHAIN_ID, salt },
    { Details: [{ name: "orderHashes", type: "string[]" }, { name: "timestamp", type: "uint256" }] },
    { orderHashes: ["0xabc..."], timestamp },
  );
  await fetch(`${BASE_URL}/orders/cancel/v2`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ orderHashes: ["0xabc..."], signature: sigV2, salt, maker: wallet.address, timestamp }),
  });

  // Cancel all orders for a specific event
  const sigEvent = await wallet.signTypedData(
    { name: "CancelOrderEventsSportX", version: "1.0", chainId: CHAIN_ID, salt },
    { Details: [{ name: "sportXeventId", type: "string" }, { name: "timestamp", type: "uint256" }] },
    { sportXeventId: "L15468276", timestamp },
  );
  await fetch(`${BASE_URL}/orders/cancel/event`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ sportXeventId: "L15468276", signature: sigEvent, salt, maker: wallet.address, timestamp }),
  });

  // Cancel all open orders
  const sigAll = await wallet.signTypedData(
    { name: "CancelAllOrdersSportX", version: "1.0", chainId: CHAIN_ID, salt },
    { Details: [{ name: "timestamp", type: "uint256" }] },
    { timestamp },
  );
  await fetch(`${BASE_URL}/orders/cancel/all`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ signature: sigAll, salt, maker: wallet.address, timestamp }),
  });
  ```
</CodeGroup>

## Heartbeat

The heartbeat is a safety mechanism that automatically cancels all your open orders if your service loses connectivity. This prevents stale orders from sitting on the book when you can't manage them.

Register a heartbeat with an interval (in seconds). If SX Bet doesn't receive a heartbeat ping within that window, all your orders are cancelled.

<CodeGroup>
  ```python Python theme={null}
  import requests
  import time
  import os

  BASE_URL = "https://api.sx.bet"  # Mainnet — use https://api.toronto.sx.bet for testnet
  headers = {"X-Api-Key": os.environ["SX_API_KEY"]}

  # Register a heartbeat with a 30-second timeout
  requests.post(
      f"{BASE_URL}/user/heartbeat",
      json={"interval": 30},
      headers=headers,
  )

  # Send heartbeats in a loop to keep orders alive
  while True:
      requests.post(
          f"{BASE_URL}/user/heartbeat",
          json={"interval": 30},
          headers=headers,
      )
      time.sleep(15)  # send well within the 30s window
  ```

  ```javascript JavaScript theme={null}
  const BASE_URL = "https://api.sx.bet"; // Mainnet — use https://api.toronto.sx.bet for testnet
  const headers = { "X-Api-Key": process.env.SX_API_KEY, "Content-Type": "application/json" };

  // Register a heartbeat with a 30-second timeout
  await fetch(`${BASE_URL}/user/heartbeat`, {
    method: "POST",
    headers,
    body: JSON.stringify({ interval: 30 }),
  });

  // Send heartbeats in a loop to keep orders alive
  setInterval(async () => {
    await fetch(`${BASE_URL}/user/heartbeat`, {
      method: "POST",
      headers,
      body: JSON.stringify({ interval: 30 }),
    });
  }, 15_000); // send well within the 30s window
  ```
</CodeGroup>

<Warning>Always set up a heartbeat before going live. If your service goes down and misses a heartbeat, all open orders will be cancelled automatically.</Warning>

To stop the heartbeat and prevent auto-cancellation, call [`POST /user/heartbeat/cancel`](/api-reference/post-heartbeat-cancel).

Full reference: [Heartbeat →](/api-reference/heartbeat)

## Exposure management

You can post as many orders as you wish across the exchange as long as your total exposure per market remains below your wallet balance.

Total exposure can be calculated as the sum of `totalBetSize - fillAmount` across all your open orders on that market hash. Each market hash is checked independently, so you can post up to your full balance on as many market hashes as you like simultaneously.

**Example: posting across multiple markets**

Your wallet balance is 100 USDC. You post orders across three different markets:

| Market             | Side      | totalBetSize | fillAmount | Exposure |
| ------------------ | --------- | ------------ | ---------- | -------- |
| Spread (hash A)    | Outcome 1 | 50 USDC      | 0          | 50 USDC  |
| Spread (hash A)    | Outcome 2 | 50 USDC      | 0          | 50 USDC  |
| Moneyline (hash B) | Outcome 1 | 100 USDC     | 0          | 100 USDC |
| Totals (hash C)    | Outcome 1 | 50 USDC      | 0          | 50 USDC  |
| Totals (hash C)    | Outcome 2 | 50 USDC      | 0          | 50 USDC  |

Per-hash exposure: hash A = 100 USDC, hash B = 100 USDC, hash C = 100 USDC. All equal your balance — all orders are valid.

If your total exposure exceeds your wallet balance on any given market, your orders will be removed from the orderbook until your balance reaches the minimum again.

<Warning>If the API finds that your balance is consistently below your total exposure requiring orders to be removed, your account may be temporarily restricted.</Warning>

Tips for managing exposure:

* Track your open exposure in real-time using the [`active_orders:{maker}` WebSocket channel](/api-reference/centrifugo-active-order-updates)
* Set `apiExpiry` on orders to automatically cancel orders after a set amount of time
* Use the heartbeat to auto-cancel if your service goes down
* Orders posted pre-match will be automatically cancelled when the match begins

## Related

<CardGroup cols={2}>
  <Card title="Market Making →" icon="chart-line" href="/developers/market-making">
    Overview of the maker role, spreads, and exposure management.
  </Card>

  <Card title="Posting Orders →" icon="paper-plane" href="/developers/posting-orders">
    Constructing, signing, and submitting orders.
  </Card>

  <Card title="Real-time Data →" icon="bolt" href="/developers/real-time">
    All WebSocket channels including orderbook and market signals.
  </Card>

  <Card title="Heartbeat API →" icon="heart-pulse" href="/api-reference/heartbeat">
    Full heartbeat endpoint reference.
  </Card>

  <Card title="Latency & Server Locations →" icon="gauge" href="/developers/latency">
    Expected response times and co-location recommendations.
  </Card>
</CardGroup>
