Skip to content

Websocket Messages

List of messages subscriptions that are available on the Websocket

During authentication, a client can obtain multiple short-lived auth tokens by calling the /api/3/zm/rest/auth/token endpoint. Each received token can then be used to establish a WebSocket connection:

wss://trade-uk.sandbox.zodiamarkets.com/zm/ws/ws-client?token=<token>&sessionId=<sessionId>

sessionId is optional and names the session this connection joins. Supply it if you use autoSubscribe on orders; it is what makes an order outcome recoverable across a reconnect. Omit it otherwise. Full rules — format, refusal, and the effect of reconnecting under a live sessionId — are on Order Sessions and Recovery →.


Each WebSocket connection for a given client is independent. Connections have their own price subscriptions and can send and receive order requests separately.

Key Points:

  • Each connection maintains its own price subscriptions
  • Quote IDs can be shared across connections (as long as they are not expired)
  • A request’s response is returned to the connection that made it
  • Exception — orders sent with autoSubscribe: true. Their outcome belongs to the session, not the connection, so it can be delivered to a different connection of yours, or redelivered on a later one. It is never delivered to another counterparty.

Concept Behavior
Price subscriptions Bound to the connection that created them. Lost on disconnect.
QuoteIDs Remain valid after disconnect. Can be used from any authenticated connection while the quote is still active.
Order responses (default) Delivered only to the connection that submitted the order. Lost if it disconnects first.
Order responses (autoSubscribe: true) Delivered to the session. Redelivered on reconnect under the same sessionId until you send orderUnsubscribe.

The examples below use Connection A and Connection B to represent separate WebSocket connections from the same client. Each scenario states the result for both delivery models; where only one line is given, both behave the same way.

Connection A: subscribe → receives quoteID, price stream active
Connection A: disconnects
Connection A: reconnects

Result:

  • Price stream: Stopped. Client must re-subscribe.
  • QuoteID: Still usable. Can be submitted on the new connection, if the quote remains active.

Scenario 2: Order Submitted on Different Connection

Section titled “Scenario 2: Order Submitted on Different Connection”
Connection A: subscribe → receives quoteID
Connection B: connects, submits order using quoteID from Connection A

Result:

  • Default delivery: Delivered to Connection B only (the submitting connection).
  • autoSubscribe: true: PENDING to Connection B; the terminal to whichever connection holds Connection B’s session when it arrives.

Scenario 3: Submitting Connection Disconnects Before Response

Section titled “Scenario 3: Submitting Connection Disconnects Before Response”
Connection A: subscribe → receives quoteID
Connection B: submits order using quoteID
Connection B: disconnects before response

Result:

  • Default delivery: Lost. Neither connection receives it. Recover it with Read Order State →, or by retrying the order with the same idempotencyKey.
  • autoSubscribe: true: Not lost. The outcome is recorded and delivered to the session — pushed live if another connection already holds that sessionId, otherwise redelivered when you reconnect under it.

Scenario 4: Subscription Connection Disconnects After Order Submission

Section titled “Scenario 4: Subscription Connection Disconnects After Order Submission”
Connection A: subscribe → receives quoteID
Connection B: submits order using quoteID
Connection A: disconnects

Result:

  • Order response: Delivered to Connection B, under both delivery models.

Scenario 5: Reconnecting Under the Same Session

Section titled “Scenario 5: Reconnecting Under the Same Session”
Connection A: connects with ?sessionId=desk-1, submits order with autoSubscribe: true
Connection A: receives PENDING, then disconnects
Connection B: connects with ?sessionId=desk-1

Result:

  • Order response: Delivered to Connection B — either pushed live if the terminal arrives while B is connected, or redelivered on connect if it was recorded while nobody held the session.
  • Price stream: Stopped. Connection B must re-subscribe; price subscriptions are not part of a session.
  • Had Connection A still been live, connecting B under the same sessionId would have closed A.

The following types of subscription methods are supported:

Subscription Type Description Requirements Details
Normal Price subscriptions Execute trade using own funds and delivered to own account/wallet Account With ZM with Authorized Traders access to RFS Price Channel→
Beneficiary Price Subscription Execute trade using own funds but delivered to third party wallet address Third Party Beneficiary registered with ZM with delivery wallet whitelisted and verified. Beneficiary Price Channel→
Sender Price Subscription Execute trade using third party crypto funds but delivered to own account/wallet Third Party Sender registered with ZM with collection (sending) wallet whitelisted and verified. Sender Price Channel→



# Example: Managing multiple WebSocket connections
class WebSocketManager:
def __init__(self):
self.connections = {}
self.subscriptions = {}
def add_connection(self, name, ws):
"""Add a new WebSocket connection"""
self.connections[name] = ws
self.subscriptions[name] = []
def subscribe(self, connection_name, instrument):
"""Subscribe to prices on specific connection"""
ws = self.connections[connection_name]
ws.send(json.dumps({
'messageType': 'subscribe',
'instrument': instrument,
...
}))
self.subscriptions[connection_name].append(instrument)
def submit_order(self, connection_name, quote_id):
"""Submit order on specific connection"""
ws = self.connections[connection_name]
ws.send(json.dumps({
'messageType': 'order',
'quoteId': quote_id,
...
}))
# Order response will come back on this connection
# Usage
manager = WebSocketManager()
manager.add_connection('pricing', ws_connection_1)
manager.add_connection('trading', ws_connection_2)
# Subscribe on pricing connection
manager.subscribe('pricing', 'BTC.USD')
# Submit order on trading connection (can use quoteID from pricing)
manager.submit_order('trading', quote_id_from_pricing_connection)
def handle_disconnect(connection_name):
"""Handle connection disconnect"""
# Price subscriptions are lost
subscriptions = self.subscriptions[connection_name]
# Reconnect
new_ws = reconnect()
self.connections[connection_name] = new_ws
# Re-subscribe to all previous subscriptions
for instrument in subscriptions:
self.subscribe(connection_name, instrument)
# Note: QuoteIDs from before disconnect are still valid
# Can continue using them if quotes are still active
def submit_order_safely(ws, quote_id):
"""Submit order and wait for response"""
order_submitted = threading.Event()
order_response = None
def on_message(msg):
nonlocal order_response
if msg['messageType'] == 'order':
order_response = msg
order_submitted.set()
# Submit order
ws.send(json.dumps({
'messageType': 'order',
'quoteId': quote_id,
...
}))
# Wait for response (with timeout)
if order_submitted.wait(timeout=10):
return order_response
else:
# Timeout - order may still execute
# Check order status via REST API
return None

  • 🔄 Price subscriptions do not persist across disconnections - always re-subscribe after reconnecting
  • 📌 Order subscriptions do persist. An order sent with autoSubscribe: true is redelivered on every reconnect under the same sessionId until you send orderUnsubscribe
  • 🎫 Quote IDs remain valid even after the subscribing connection disconnects
  • 📨 Order responses go to the submitting connection - not the connection that subscribed to prices. With autoSubscribe: true they go to the session instead, so correlate on transactionId
  • ⚠️ Orders may execute even if the connection drops before receiving a response - recover the outcome, never re-place the order