Skip to main content

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 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.
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();
Create only one Centrifuge instance per process. All subscriptions share the same connection — maximum 512 channel subscriptions per connection.
You can also poll your open orders via GET /orders with the maker parameter — useful for reconciling state on startup before subscribing. Full payload reference: Active Order Updates →

Cancel orders

All cancellation endpoints require an EIP-712 wallet 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:
EndpointWhen to use
POST /orders/cancel/v2Cancel specific orders by hash
POST /orders/cancel/eventCancel all orders tied to a specific event
POST /orders/cancel/allCancel 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.
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,
})

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.
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
Always set up a heartbeat before going live. If your service goes down and misses a heartbeat, all open orders will be cancelled automatically.
To stop the heartbeat and prevent auto-cancellation, call POST /user/heartbeat/cancel. Full 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:
MarketSidetotalBetSizefillAmountExposure
Spread (hash A)Outcome 150 USDC050 USDC
Spread (hash A)Outcome 250 USDC050 USDC
Moneyline (hash B)Outcome 1100 USDC0100 USDC
Totals (hash C)Outcome 150 USDC050 USDC
Totals (hash C)Outcome 250 USDC050 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.
If the API finds that your balance is consistently below your total exposure requiring orders to be removed, your account may be temporarily restricted.
Tips for managing exposure:
  • Track your open exposure in real-time using the active_orders:{maker} WebSocket channel
  • 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

Market Making →

Overview of the maker role, spreads, and exposure management.

Posting Orders →

Constructing, signing, and submitting orders.

Real-time Data →

All WebSocket channels including orderbook and market signals.

Heartbeat API →

Full heartbeat endpoint reference.

Latency & Server Locations →

Expected response times and co-location recommendations.