Skip to main content
The StratAlerts WebSocket API gives you a persistent, bidirectional connection to the same real-time data stream that powers the scanner. Instead of polling REST endpoints for the latest state, you open one connection and subscribe to the channels you need — price quotes, TFC state changes, in-force alerts, and simultaneous break alerts — then receive events pushed to you as they fire. Use the WebSocket API when your integration needs live data; use the REST API when you need historical snapshots or one-time lookups.

Endpoint

All WebSocket connections go to a single endpoint:
wss://app.stratalerts.com/ws/market/v1

Authentication

You authenticate at connection time by passing your API key in the HTTP upgrade request. The server validates the key before completing the WebSocket handshake — no separate auth message is needed after connecting. Pass your key using either of these headers:
HeaderFormat
AuthorizationBearer YOUR_API_KEY
X-API-KeyYOUR_API_KEY
Your key must have the ws:connect scope in addition to any channel-specific scopes. If the key is missing or invalid, the connection is rejected with close code 4401. If the key lacks the required scopes, it is rejected with close code 4403.
API keys are managed in your account at app.stratalerts.com. Each key is issued with a specific set of scopes — if you cannot connect or subscribe to a channel, check that your key includes the required scope.

One connection per account

Each account may have only one active WebSocket connection at a time. If you open a second connection while an existing one is live, the server evicts the older connection by closing it with code 4409 before completing the new handshake. The new connection then proceeds normally. Design your client to handle close code 4409 as a signal that it was replaced — typically this means you should not attempt to reconnect immediately, since a newer instance of your application is already connected.

Message envelope

Every message the server sends — including acknowledgments and data events — uses the same JSON envelope:
{
  "type": "event_type_here",
  "ts": "2026-04-10T14:35:00.123456+00:00",
  "seq": "42",
  "data": {}
}
FieldTypeDescription
typestringEvent type (e.g. quote, alert.in_force, subscribed)
tsstringISO 8601 server timestamp for this message
seqstringMonotonically increasing integer string; increments for every message sent on this connection
dataobjectEvent-specific payload
The seq field is a string representation of an integer that starts at 1 and increments with each message. You can use it to detect dropped messages if you are logging or buffering events.

Subscribing to channels

After the connection is established, send a subscribe message to begin receiving events from one or more channels:
{
  "op": "subscribe",
  "topics": [
    { "channel": "quotes", "symbols": ["AAPL", "TSLA"] },
    { "channel": "states", "symbols": ["AAPL"] },
    { "channel": "alerts.in_force" },
    { "channel": "alerts.simultaneous_breaks" }
  ]
}
The server responds with a subscribed acknowledgment listing the channels it accepted:
{
  "type": "subscribed",
  "ts": "2026-04-10T14:35:00.123456+00:00",
  "seq": "1",
  "data": {
    "channels": ["quotes", "alerts.in_force"]
  }
}
Channels that require a symbols list (quotes, states) are subscribed per symbol. Channels that are account-wide (alerts.in_force, alerts.simultaneous_breaks) do not take a symbols list — once subscribed, you receive all events globally.
If your key is missing the scope for a channel, that channel is silently omitted from the acknowledgment. Check the channels array in the response to confirm which subscriptions were accepted.

Unsubscribing

Send the same message format with "op": "unsubscribe" to stop receiving events from specific channels or symbols:
{
  "op": "unsubscribe",
  "topics": [
    { "channel": "quotes", "symbols": ["TSLA"] }
  ]
}
The server responds with an unsubscribed acknowledgment in the same envelope format.

Close codes

CodeMeaning
4401No API key provided or key could not be resolved
4403API key is valid but the account is not entitled or the key is missing the ws:connect scope
4409Connection evicted — a new connection was opened for this account and replaced this one

Reconnection

WebSocket connections can drop due to network interruptions, server restarts, or idle timeouts. Implement reconnection with exponential backoff in your client, and re-subscribe to all channels after each successful reconnect. A basic backoff strategy:
  • Start with a 1-second delay after the first disconnect
  • Double the delay on each failed reconnect attempt
  • Cap the delay at 60 seconds
  • Reset the delay counter after a successful reconnect
Do not reconnect immediately in a tight loop. Rapid reconnection attempts can exhaust your connection budget and delay recovery. Always use backoff.

Complete examples

The examples below show a full connect → subscribe → receive loop. Replace YOUR_API_KEY with your actual key.
import asyncio
import json
import websockets

API_KEY = "YOUR_API_KEY"
WS_URL = "wss://app.stratalerts.com/ws/market/v1"


async def connect_stratalerts():
    headers = {"Authorization": f"Bearer {API_KEY}"}

    async with websockets.connect(WS_URL, additional_headers=headers) as ws:
        # Subscribe to quotes for two symbols and all in-force alerts
        await ws.send(json.dumps({
            "op": "subscribe",
            "topics": [
                {"channel": "quotes", "symbols": ["AAPL", "SPY"]},
                {"channel": "alerts.in_force"},
            ]
        }))

        # Receive messages until the connection closes
        async for raw in ws:
            message = json.loads(raw)
            event_type = message.get("type")
            data = message.get("data", {})

            if event_type == "subscribed":
                print(f"Subscribed to: {data.get('channels')}")
            elif event_type == "quote":
                print(f"Quote  {data['symbol']}  ${data['price']}  vol {data['volume']}")
            elif event_type == "alert.in_force":
                print(f"Alert  {data}")


async def main():
    backoff = 1
    while True:
        try:
            await connect_stratalerts()
            backoff = 1  # reset on clean disconnect
        except websockets.ConnectionClosedError as exc:
            if exc.code == 4409:
                print("Connection evicted by a newer session — not reconnecting.")
                break
            print(f"Disconnected (code={exc.code}), retrying in {backoff}s...")
            await asyncio.sleep(backoff)
            backoff = min(backoff * 2, 60)
        except Exception as exc:
            print(f"Error: {exc}, retrying in {backoff}s...")
            await asyncio.sleep(backoff)
            backoff = min(backoff * 2, 60)


asyncio.run(main())

Channels

Full reference for all four channels: required scopes, symbol subscriptions, and example event payloads.

Authentication

How to obtain an API key, understand scopes, and pass credentials in requests.