To receive real-time signals, send a subscription.request message after the connection is established:
{
"msg_type": "subscription.request",
"request_id": "my-sub-001",
"timestamp": "2024-01-01T12:00:00Z",
"payload": {
"action": "subscribe",
"channel": "strategy.signal",
"strategy_id": 12345678
}
}| Field | Type | Required | Description |
|---|---|---|---|
| action | string | Yes | "subscribe" or "unsubscribe" |
| channel | string | Yes | Channel to subscribe to. "strategy.signal" or "strategy.positions". |
| strategy_id | integer | Yes | The strategy to subscribe to. Must be a positive integer. |
| resume_from | string | No | ("strategy.signal" channel only) Last seq_id received. When set and greater than 0, the server replays missed signals before starting live delivery. Omit or set to 0 to receive only signals published after this subscription is acknowledged. See Gap Fill. |
| Field | Type | Required | Description |
|---|---|---|---|
| request_id | string | No | Client-assigned correlation ID (max 50 characters). The server echoes it in the subscription.ack envelope, useful when sending multiple subscriptions in quick succession. |
The timestamp field in your messages must be within 1 minute of UTC. Messages with a clock-skewed timestamp are rejected with INVALID_PARAMETER.
websocat -H "Authorization: Bearer YOUR_API_KEY" wss://api4-general.collective2.com/ws \
--text - <<< '{"msg_type":"subscription.request","timestamp":"2024-01-01T12:00:00Z","payload":{"action":"subscribe","channel":"strategy.signal","strategy_id":12345678}}'import asyncio
import json
import websockets
from datetime import datetime, timezone
API_KEY = "YOUR_API_KEY"
WS_URL = "wss://api4-general.collective2.com/ws"
STRATEGY_ID = 12345678
async def subscribe():
headers = {"Authorization": f"Bearer {API_KEY}"}
async with websockets.connect(WS_URL, additional_headers=headers) as ws:
# Wait for connection.connected
await ws.recv()
# Send subscription request
request = {
"msg_type": "subscription.request",
"request_id": "my-sub-001",
"timestamp": datetime.now(timezone.utc).isoformat(),
"payload": {
"action": "subscribe",
"channel": "strategy.signal",
"strategy_id": STRATEGY_ID
}
}
await ws.send(json.dumps(request))
# Wait for subscription.ack
ack = json.loads(await ws.recv())
print(f"Subscribed: {ack['msg_type']}")
asyncio.run(subscribe())The server responds with a subscription.ack that echoes your full payload and request_id:
{
"msg_type": "subscription.ack",
"id": "msg_a1b2c3d4e5f6a1b2c3d4e5f6a7b8",
"request_id": "my-sub-001",
"timestamp": "2024-01-01T12:00:00Z",
"v": 4,
"payload": {
"action": "subscribe",
"channel": "strategy.signal",
"strategy_id": 12345678,
"resume_from": null
}
}Once subscribed, position changes are pushed as strategy.positions messages. The message payload is PositionDTO
Once subscribed, new signals are pushed as strategy.signal messages:
{
"msg_type": "strategy.signal",
"id": "msg_a1b2c3d4e5f6a1b2c3d4e5f6a7b8",
"seq_id": 98765,
"timestamp": "2024-01-01T12:00:00Z",
"v": 4,
"payload": {
"Id": 98765,
"StrategyId": 12345678,
"SignalId": 456789,
"Side": "1",
"OpenClose": "O",
"OrderQuantity": 100,
"OrderType": "1",
"Stop": null,
"Limit": null,
"TIF": "Day",
"ParentSignalId": null,
"C2Symbol": {
"FullSymbol": "AAPL",
"SymbolType": "stock",
"Underlying": null,
"Expiry": null,
"PutOrCall": null,
"StrikePrice": null,
"Description": "Apple Inc."
},
"ExchangeSymbol": {
"Symbol": "AAPL",
"Currency": "USD",
"SecurityExchange": "DEFAULT",
"SecurityType": "CS",
"MaturityMonthYear": null,
"PutOrCall": null,
"StrikePrice": null,
"PriceMultiplier": 1
},
"PostedDate": "2024-01-01T12:00:00Z",
"OrderStatus": "0",
"FilledQuantity": 0,
"SignalType": 1
}
}Always store the seq_id from the last signal you receive. You will need it to resume after a disconnection — see Gap Fill.
The OrderStatus field in the payload indicates what kind of event the signal represents:
| OrderStatus` value | Meaning |
|---|---|
| "0" | Working — a new order has been placed and is active |
| "2" | Filled — the order has been fully filled |
| "4" | Canceled — the order has been canceled |
Cancel signals carry only the identity fields — Id, StrategyId, SignalId, SignalType, and OrderStatus. All order detail fields (Side, OpenClose, OrderQuantity, OrderType, Stop, Limit, TIF, C2Symbol, ExchangeSymbol, etc.) are null.
Fill signals mirror the original order signal but with OrderStatus: "2" and FilledQuantity set to the filled quantity.
Working signals are the full order structure shown in the example above.
Send a subscription.request with action: "unsubscribe" to stop receiving signals:
{
"msg_type": "subscription.request",
"timestamp": "2024-01-01T12:00:00Z",
"payload": {
"action": "unsubscribe",
"channel": "strategy.signal",
"strategy_id": 12345678
}
}A successful unsubscribe returns a subscription.ack with action: "unsubscribe" in the payload. Attempting to unsubscribe from a channel you are not subscribed to returns a NOT_FOUND error.
| Error | Cause |
|---|---|
| ALREADY_EXISTS | Already subscribed to this channel and strategy |
| NOT_FOUND | Tried to unsubscribe from a channel not currently subscribed |
| FORBIDDEN | Not authorized to receive signals for this strategy. You must own or be subscribed to the strategy to receive its signals. |
| TOO_MANY_SUBSCRIPTIONS | Channel limit reached on this connection |