cTrader APIWebSocket streams
For AI agents: this page is also available as Markdown at /docs/ctrader/websocket.md. The index of every docs page is /docs/llms.txt.
cTrader WebSocket API
Stream live cTrader data over a single WebSocket per account: prices, OHLC bar closes, account updates, open positions, trade events, pending orders, and connection status. The topics and frames are the ones MetaTrader accounts stream, and the trades you missed are replayed after a reconnect.
How streaming works on cTrader
- Events from cTrader. Trade, modify and order events come from cTrader's execution events as they happen, one trade event per filled deal.
- Nothing lost on a reconnect. Resubscribe to trades with since and the deals made while you were away are replayed before live events resume. A replayed deal of a position still open carries its current stops; one of a closed position has none, because cTrader does not store them on a deal.
- Account profit is net. The account topic's profit is cTrader's floating P/L after the entry commission, so it can be lower than the sum of the positions' profit.
- Degraded events. If the bridge cannot enrich an event in time, it is sent with degraded true: identifiers, symbol, type, volume and price are reliable, profit and the other details are zeroed. Reconcile with /OrderHistory.
wss://api.fxsocket.com/ctrader/{account_id}/ws?api_key=fxs_live_...One streaming socket per account. Authentication is via the api_key query parameter, the only method available on WebSocket connections. Send JSON control frames to subscribe and unsubscribe; on subscribe the server first sends one current snapshot (account / positions / orders / prices), marked "snapshot": true, before streaming changes. The snapshot can arrive just after the first live frame. Non-browser clients: the upgrade requires HTTP/1.1. The server pings every 5 seconds and closes a connection that hasn't answered for 30 seconds; every WebSocket library answers pings on its own, so this only affects a client that stops reading its socket. Seven topics are available:
prices: Per-symbol prices as cTrader pushes them (requires symbol). A repeat of the same bid and ask is not sent.bars: Closed bars per symbol + timeframe (requires symbol and timeframe). A bar frame goes out with the first price of the next bar, so it always holds the bar that just closed, never the forming one. data.time is the bar's open time in UTC. Without a new price the frame waits, so the last bar before the weekend arrives when the market reopens. Each bar is sent once and never corrected; its tickVolume can be a few ticks short of the final count, which /PriceHistory has. A reconnect can leave gaps; /PriceHistory fills them.account: Account snapshot, then changes within about 50 ms of a trade, and at least once a second. profit here is cTrader's net floating P/L, which already deducts the commission charged on entry, so it can differ from the sum of the positions' profit.positions: Open positions snapshot, then changes within about 50 ms, and at least once a second.trades: A trade event per executed deal as it happens: deal, order, position, symbol, type, entry (In, Out or InOut), volume, price, profit, commission, swap, magic, comment, time (with milliseconds) and timeUtc, sl and tp, positionVolume (still open on the position after this deal) and degraded. A modify event on the same topic reports an open position whose stop loss or take profit moved. Optional since (UTC, as the frames print it) replays the deals you missed first, see below.orders: Pending orders: a snapshot with the /PendingOrders rows, then an order event per step of an order's life. event is placed, modified, deleted, expired or triggered; the rest is the order as of that step, plus position (for triggered, the position its fill opened).terminal: Connection status, roughly once per second.
{"action": "subscribe", "topic": "prices", "symbol": "EURUSD"}
{"action": "subscribe", "topic": "bars", "symbol": "EURUSD", "timeframe": "M5"}
{"action": "subscribe", "topic": "account"}
{"action": "subscribe", "topic": "orders"}
{"action": "subscribe", "topic": "trades", "since": "2026-11-10T22:30:00.000Z"}
{"action": "unsubscribe", "topic": "prices", "symbol": "EURUSD"}server → client messagesEvery server message is a JSON object with a type. Data messages mirror the REST shapes; control replies confirm your frames. Ignore types you don't know, so new ones can be added without breaking your client. Every live frame carries seq, numbered per topic, and ts, the moment it was sent (Unix milliseconds, UTC). A gap in seq means frames were dropped for you; a slow consumer gets dropped messages plus a warning that names the topic (and symbol or timeframe) rather than a stalled socket. Snapshots and replayed frames have no seq.
{"type": "tick", "symbol": "EURUSD", "seq": 1, "ts": 1794350318944, "data": {"symbol": "EURUSD", "bid": 1.11662, "ask": 1.11685, "time": "2026-11-10T22:38:25.133Z", "last": 0.0, "volume": 0}}
{"type": "bar", "symbol": "EURUSD", "timeframe": "M5", "seq": 12, "ts": 1794350400004, "data": {"time": "2026-11-10T22:35:00.000Z", "open": 1.11655, "high": 1.11689, "low": 1.11650, "close": 1.11662, "tickVolume": 412, "realVolume": 0, "spread": 0}}
{"type": "account", "seq": 300, "ts": 1794350319002, "data": { ... }}
{"type": "positions", "snapshot": true, "ts": 1794350318798, "data": [ ... ]}
{"type": "trade", "seq": 1, "ts": 1794350326939, "data": {"deal": 343051307, "order": 328018035, "position": 302262716, "symbol": "EURUSD", "type": "Buy", "entry": "In", "volume": 0.01, "price": 1.11685, "commission": -0.05, "sl": 0.0, "tp": 0.0, "positionVolume": 0.01, "time": "2026-11-10T22:38:46.909Z", "timeUtc": "2026-11-10T22:38:46.909Z", ...}}
{"type": "modify", "seq": 2, "ts": 1794350327140, "data": {"position": 302262716, "symbol": "EURUSD", "type": "Buy", "volume": 0.01, "price": 1.11685, "sl": 1.11, "tp": 1.125, ...}}
{"type": "orders", "snapshot": true, "ts": 1794350318800, "data": [ ... ]}
{"type": "order", "seq": 3, "ts": 1794350467512, "data": {"event": "placed", "ticket": 328022021, "symbol": "EURUSD", "type": "BuyLimit", "price": 1.05, "position": 0, ...}}
{"type": "terminal", "seq": 52, "ts": 1794350319000, "data": {"connected": true, "tradeAllowed": true, "serverTime": "2026-11-10T22:38:39.000Z"}}
{"type": "warning", "topic": "prices", "symbol": "EURUSD", "dropped": 12, "ts": 1794350320000}
{"type": "subscribed" | "unsubscribed" | "error" | "replayed", ...}trades replay (since)Subscribing to trades with since first replays the deals at or after that moment, then switches to live events, so a client that was disconnected doesn't lose the deals in between. since is a UTC time as the frames print it, at most 24 hours back. The replay sends the newest 1000 deals at most, oldest first, as trade events marked "replay": true, then a replayed frame with their count (truncated is true when the limit cut it short), then live events. Replayed events of a position that is still open carry its current sl and tp; those of a closed position carry 0, since cTrader does not store stops on a deal. A deal right at the switch-over can arrive twice, so dedupe by deal. After a reconnect, subscribe again with the time of the last trade event you processed. The official SDKs do this for you.
{"type": "trade", "replay": true, "ts": 1794350401002, "data": { ... }}
{"type": "trade", "replay": true, "ts": 1794350401002, "data": { ... }}
{"type": "replayed", "topic": "trades", "count": 2, "truncated": false}
{"type": "trade", "seq": 1, "ts": 1794350405117, "data": { ... }}/ws-testerA built-in browser tester is served per account at https://api.fxsocket.com/ctrader/{account_id}/ws-tester?api_key=fxs_live_... (handy for poking at the stream before writing code). The page is API-key gated, and since browsers can't attach headers the key must ride in the URL (it will appear in your browser history). Open yours from the request builder on the overview page.