EODHD offers one of the best ways for investors, developers, and data analysts to incorporate real-time finance data for US and European stocks, 1100+ Forex pairs and 1000+ Digital Currencies into their decision-making projects with a delay of less than 50ms via WebSockets. For US stocks our real-time data API supports pre-market and post-market hours (from 4 am to 8 pm ET; the session is computed in the America/New_York timezone, so those clock times hold all year regardless of daylight saving); European stocks follow the trading hours of each instrument’s home venue.
The 50 is a limit on tickers, not on connections. Your subscription streams 50 symbols at a time, and that allowance belongs to your API token: it is counted across every WebSocket you have open, so spreading the same symbols over more sockets does not buy you more of them. The ceiling itself can be raised in the user dashboard.
So subscribe the way the protocol expects: open one connection and send a single subscribe message listing your symbols, comma-separated, up to your limit — see Subscribe / Unsubscribe. Opening 50 connections with one symbol each returns exactly the same 50 symbols, spends 50 of the 64 concurrent connections a token may hold, and leaves you 50 sockets to reconnect and resubscribe after every drop. Use a separate connection per market or stream — US trades, US quotes, European trades, European quotes, forex, crypto — never per symbol.
An example. Suppose you need 37 US stocks, 10 crypto pairs and 3 forex pairs — 50 symbols, exactly the default allowance. That is three connections, because those are three different endpoints: /ws/us, /ws/crypto and /ws/forex. Each one gets a single subscribe message with its own list, and together they spend your 50. One caveat while you plan the split: the allowance counts subscriptions, not distinct tickers — asking for quotes on those same 37 stocks through /ws/us-quote costs another 37 slots, not a free duplicate.
For global markets and all exchanges around the world we also offer Live (Delayed) data with 15min delay.
What is WebSocket protocol
WebSockets is a communication protocol that provides full-duplex communication channels over a single TCP connection. It enables real-time communication between a client and a server, allowing them to exchange messages continuously without the overhead of repeatedly establishing new connections. In the case of financial data providers, Websockets API provides real-time stock market information with minimal delay.
Because real-time WebSocket streaming is resource-intensive, it is offered on paid plans rather than a free tier.
What you get (Data availability)
US stocks (full list of tickers available)
• Trade stream (last price, size, conditions, etc.)
• Quote stream (bid/ask, sizes)
• Extended hours supported (pre‑ and post‑market)
European stocks — 18 markets, among them London, XETRA, the Euronext venues, the Nordic exchanges, Madrid, Milan, Vienna and Warsaw
• Trade stream, the same message shape as the US one
• Quote stream, a consolidated best bid and offer across the three Cboe Europe books
• Tickers in the usual EODHD form, e.g. GSK.LSE, SAP.XETRA, ASML.AS — unlike US tickers, which carry no suffix at all
FOREX (full list of available currency pairs)
• Bid/ask + day change/difference
• Tickers like EURUSD, AUDUSD, etc.
Digital Currencies (Full list of available crypto pairs)
• Last price, quantity, day change/difference
• Tickers like ETH-USD, BTC-USD
Lists of available tickers and pairs for Real-Time feeds can be retrieved in JSON format via this API endpoint, providing the full list of exchange or asset class components (use “US” for US stocks, “CC” for cryptocurrencies, “FOREX” for currency pairs). European symbols are listed per exchange with the same endpoint — among the codes the real-time feed carries are LSE, XETRA, AS, PA, BR, LS, MI, MC, ST, CO, HE, OL, VI and WAR — and there the exchange list is the wider universe, so check a name against the stream as well.
Derived from the equity streams, for the US and Europe alike
• Minute bars — a rolling one-minute OHLCV bar per subscribed symbol, over WebSocket or over REST
• Trading status — halts, pauses and auctions as the venue itself reports them
Demo access is available with API key “demo” for twelve symbols: AAPL, MSFT, AMZN, TSLA, INTC, MCD, VTI, SWPPX, EURUSD, USDEUR, BTC-USD and ETH-USD. Any other symbol is answered with a 422 on subscribe, and the demo key covers no European tickers — the European streams need a paid token.
Data source and what the data represents
Real-time US and European equities are delivered directly from Cboe Global Cloud, our official exchange data source. Because this is a direct-from-exchange feed rather than an aggregated one, it is worth being precise about what it contains.
US equities
US trades and quotes come from Cboe EDGX, one of Cboe’s four US equity exchanges. Trades are executions that occurred on EDGX — this is not a consolidated (SIP) tape, so it does not include executions from other exchanges, or off-exchange and FINRA TRF prints. Quotes are the EDGX top of book with sizes, not a national best bid and offer aggregated across venues. Pre-market and post-market trades are delivered and flagged through the market status field.
On a normal trading session the US stream carries about 12,700 symbols. Measured over a longer window the count is slightly higher — around 13,100 across 21 trading days — because a symbol that does not print on a given day is simply absent from that day’s stream.
European equities
European trades and quotes come from Cboe Europe, across its BXE, CXE and DXE order books, covering UK-listed names and continental large-caps over a single endpoint. Trading hours follow each instrument’s home venue, so the last trade for a symbol may lag once that venue enters its closing auction while quotes continue elsewhere.
Coverage spans 18 European markets and about 9,400 symbols on a normal session — London, XETRA, the Euronext venues, the Nordic exchanges, Madrid, Milan, Vienna and Warsaw among them. As with the US stream, a longer window gives a slightly higher count.
European quotes differ from the US ones in one important way: they are a consolidated best bid and offer across all three books. The best price wins, and where two books show the same price their sizes are added, so the size you see is the depth available at that price across the venues we carry.
Updates & Latency
- Streaming updates pushed in real time over a persistent WebSocket connection.
- Typical transport latency < 50 ms from our gateway to your client (excluding network distance).
- Market status flag indicates open, closed, or extended hours for stocks.
Endpoints
Open the connection over wss:// — that is the only scheme we serve. A plain ws:// request is answered with a permanent redirect to HTTPS, which most WebSocket clients report as a failed handshake rather than following it.
Equities (Trades and Quotes):
wss://ws.eodhistoricaldata.com/ws/us?api_token=YOUR_API_KEY
wss://ws.eodhistoricaldata.com/ws/us-quote?api_token=YOUR_API_KEY
European Equities (Trades and Quotes):
wss://ws.eodhistoricaldata.com/ws/eu?api_token=YOUR_API_KEY
wss://ws.eodhistoricaldata.com/ws/eu-quote?api_token=YOUR_API_KEY
Minute Bars (US and Europe):
wss://ws.eodhistoricaldata.com/ws/us-candles?api_token=YOUR_API_KEY
wss://ws.eodhistoricaldata.com/ws/eu-candles?api_token=YOUR_API_KEY
Trading Status (US and Europe):
wss://ws.eodhistoricaldata.com/ws/us-status?api_token=YOUR_API_KEY
wss://ws.eodhistoricaldata.com/ws/eu-status?api_token=YOUR_API_KEY
Forex
wss://ws.eodhistoricaldata.com/ws/forex?api_token=YOUR_API_KEY
Crypto
wss://ws.eodhistoricaldata.com/ws/crypto?api_token=YOUR_API_KEY
Subscribe / Unsubscribe
After the socket is open, send JSON commands.
Symbol format differs by market, which is the single most common reason a subscribe returns nothing. US tickers carry no exchange suffix; European ones do.
- US stocks — the plain ticker, AAPL or MSFT. Adding a suffix, as in AAPL.US, is accepted and then streams nothing.
- European stocks — TICKER.EXCHANGE in the usual EODHD form, GSK.LSE or SAP.XETRA. The native Cboe symbol works as well: subscribe to GSKl and you are subscribed to GSK.LSE.
- Forex — the pair with no separator, EURUSD. Crypto — dash-separated, BTC-USD.
Symbols are case-sensitive: aapl and GSKL return nothing where AAPL and GSKl work. Whatever spelling you send, every message comes back with the canonical EODHD ticker in the s field — GSKl arrives as GSK.LSE — so key your state on the s value you receive, not on the string you sent.
Connection and status messages: on a successful connection the server first sends an authorization message. If you subscribe to a symbol your plan does not allow, for example a non-demo ticker on the demo key, the server replies with a status message instead of data.
{"status_code": 200, "message": "Authorized"}
{"status_code": 422, "message": "Only limited symbols allowed for demo"}
Subscribe:
{"action": "subscribe", "symbols": "ETH-USD"}
Multiple symbols (comma‑separated):
{"action": "subscribe", "symbols": "AAPL,TSLA"}
Unsubscribe:
{"action": "unsubscribe", "symbols": "ETH-USD,BTC-USD"}
Response Schemas
Trades (endpoint: /ws/us):
{
"s": "AAPL", // ticker
"p": 309.19, // last trade price
"c": [], // trade conditions (array) - empty on this feed, see note
"v": 2, // trade size (shares)
"dp": false, // dark pool flag - always false on this feed, see note
"ms": "extended-hours", // market status: open | closed | extended-hours
"t": 1787671313868 // epoch ms, UTC
}
Two fields need a word of explanation. The conditions field c is an array, and on this feed it is always empty: the exchange top-of-book source does not carry per-trade sale conditions. The field is kept so the message shape stays identical across all our equity streams. The dark-pool flag dp is always false, because this is a single lit-exchange feed with no off-exchange or FINRA TRF prints in it.
Quotes (endpoint: /ws/us-quote):
{
"s": "AAPL", // ticker
"ap": 317.297, // ask price
"as": 160, // ask size
"bp": 316.988, // bid price
"bs": 40, // bid size
"t": 1784115291977 // epoch ms
}
European Equities (endpoints: /ws/eu and /ws/eu-quote):
European equities use exactly the same schemas as US equities — the trade stream matches /ws/us and the quote stream matches /ws/us-quote. Only the symbol format differs. Example trade message:
{
"s": "GSK.LSE", // ticker (TICKER.EXCHANGE)
"p": 1911, // last trade price, in the venue's quoting units
"c": [], // trade conditions (array) - empty on this feed
"v": 155, // trade size (shares)
"dp": false, // dark pool flag - always false on this feed
"ms": "open", // market status: open | closed
"t": 1787671304945 // epoch ms, UTC
}
Prices are JSON numbers and may arrive with no decimal part when the venue quotes in whole units, as above. Do not assume a fractional component.
Minute Bars (endpoints: /ws/us-candles and /ws/eu-candles):
A rolling one-minute OHLCV bar per subscribed symbol, built from the trade stream.
{
"s": "AAPL", // ticker
"i": "1m", // bar interval
"t": 1787671260000, // bar OPEN time, epoch ms UTC
"o": 309.25, // open
"h": 309.26, // high
"l": 309.19, // low
"c": 309.19, // close
"v": 1101 // volume in shares, accumulated within the bar
}
The field c means two different things depending on the stream: on the trade streams it is the conditions array, and on the minute-bar streams it is the bar close price. The current bar is also re-sent every time it changes, so the same bar-open timestamp arrives repeatedly with a growing volume until the minute rolls over. Treat the newest message for a given timestamp as authoritative rather than adding them together.
Trading Status (endpoints: /ws/us-status and /ws/eu-status):
Trading-status and halt messages for a subscribed symbol. This is a separate stream from the market status field ms carried inside each trade: ms tells you which session the market is in, while this stream reports the venue’s own status for the individual instrument.
{
"s": "AAPL", // ticker
"h": "T", // trading status code from the venue
"r": "0", // status reason code
"t": 1787731200000 // epoch ms, UTC
}
While an instrument is trading normally you will see the status T, with an empty reason on the European stream and 0 on the US one. Outside trading hours the status is C for closed — that is the ordinary state of the market between sessions, not a halt. Any other code is issued by the venue when an instrument is halted, paused or in an auction, and these are the venue’s own values rather than something we translate — so treat a status that is neither T nor C as a signal to stop relying on the last price until it returns to T.
As with the other streams, you receive one message per symbol immediately on subscribing, so you can read the current status without waiting for it to change.
FOREX (endpoint: /ws/forex):
{
"s": "EURUSD", // symbol
"a": 1.1419, // ask
"b": 1.1416, // bid
"dc": "0.3005", // daily change, % (string)
"dd": "0.0034", // daily difference, price units (string)
"ppms": true, // pre/post market status flag
"t": 1784115288241 // epoch ms
}
Crypto (endpoint: /ws/crypto):
{
"s": "ETH-USD", // symbol
"p": "1881.0931", // last price (string)
"q": "1.9e-7", // trade quantity (string, may use exponent notation)
"dc": "5.6041", // daily change, % (string)
"dd": "105.4177", // daily difference, price units (string)
"t": 1784115286805 // epoch ms
}
Symbol Limits & Usage Notes
Concurrent subscriptions: 50 symbols by default, counted per API token and shared across every WebSocket you have open — not per connection. Opening more sockets buys you no more symbols, and the same ticker on two different streams spends two slots. The ceiling itself can be raised in the user dashboard, for an additional monthly charge. Connections are budgeted separately — see Connection limits below.
Tickers:
- US Stocks: use plain tickers like AAPL, MSFT — no exchange suffix, and upper case
- European Stocks: use TICKER.EXCHANGE, e.g., GSK.LSE, SAP.XETRA, ASML.AS — the native Cboe symbol is accepted too, in its own case (GSKl works, GSKL does not), and the data comes back under the EODHD ticker either way
- Forex: no separator, e.g., EURUSD
- Crypto: dash‑separated, e.g., BTC-USD
Resubscribe on reconnect. A new connection starts with an empty subscription set, so re-send your current list every time the socket comes back.
Batch your subscribes. One subscribe message carrying a comma-separated list is the expected form; a separate message per symbol works but spends a round-trip each.
Symbols that never arrive
On a paid token, a symbol that is unknown, or correct-looking but in the wrong format or the wrong case for the market, is accepted silently — no error is returned and the symbol simply never streams. If one symbol arrives and others stay quiet, check the format of the quiet ones first. The most common cause is an exchange suffix on a US ticker, or a missing one on a European ticker. On the demo key the behaviour is different: anything outside the demo list is refused with a 422 rather than ignored.
Silence has a second, legitimate cause on the European streams. The feed carries about 9,400 European names, which is a subset of everything listed on those markets, so a correctly spelled ticker on a covered exchange can still be absent — NOVO-B.CO streams while ORSTED.CO does not, and PKO.WAR streams while PZU.WAR does not. Confirm a name against the stream before you build a watchlist around it.
One input mistake does produce an error: sending the symbols value as a JSON array instead of a comma-separated string is rejected with a 422.
European tickers containing a hyphen
European tickers that contain a hyphen — mostly Nordic dual-class shares such as ERIC-B.ST or NOVO-B.CO — work under their normal EODHD name. The un-hyphenated form, for example ERICB.ST, is also accepted as an alias, so client code written against either spelling keeps working. Whichever form you subscribe with, every message comes back carrying the canonical hyphenated ticker in the s field — subscribe to ERICB.ST and the data arrives as ERIC-B.ST. Key your state on the s value you receive rather than on the string you sent, or the two will not match. Note that some hyphenated tickers are not carried on the venues behind this feed at all and return nothing in either form.
Connection limits
You may hold up to 64 concurrent connections per API token. That budget is shared across every market — trades, quotes, minute bars, forex and crypto sockets all count toward the same total. Opening one beyond it is refused with a 429 before the WebSocket opens.
Opening a separate connection per stream is expected and supported. The cap exists so one runaway client cannot degrade the feed for everyone, so if you legitimately need more, contact support rather than retrying in a loop.
Minute Bars over REST
Closed one-minute bars for a single symbol are also available over plain HTTPS. This is the straightforward way to fill a gap after a dropped connection, rather than waiting for the streams to rebuild.
GET https://ws.eodhistoricaldata.com/history?market=us&symbol=AAPL&api_token=YOUR_API_KEY
The response is a JSON array of closed bars, oldest first, in the same shape as the minute-bar streams:
[
{"s":"AAPL","i":"1m","t":1787701620000,"o":309.32,"h":309.32,"l":309.31,"c":309.31,"v":223},
{"s":"AAPL","i":"1m","t":1787701680000,"o":309.31,"h":309.36,"l":309.29,"c":309.36,"v":418}
]
The market parameter takes us or eu, and symbol takes the same form you would subscribe with. Only minutes in which the symbol actually traded produce a bar, so for a less active name the list is sparse rather than one bar per consecutive minute. Forex and crypto have no minute bars and return an empty array, as does an unknown market or symbol — this endpoint reports an empty result rather than an error for those.
Errors and status codes
Error frames come in two shapes, and the difference matters if you write one parser for both. An authentication failure uses the key status. Every other frame uses status_code.
Authentication, before the connection opens
{"status":422,"message":"Server error"} // api_token missing
{"status":403,"message":"Server error"} // token not recognised, or no real-time entitlement
These are returned as the HTTP response to the handshake, so the WebSocket never opens and nothing arrives on the socket. Browsers deliberately hide the details of a failed WebSocket handshake, so a browser client sees only a generic connection error — to tell a missing token from an unentitled one, read the HTTP status from a server-side client.
After the connection is open
| Code | Message | Cause |
|---|---|---|
| 404 | No market ‘…’ found | The endpoint path is not one of the markets listed above. |
| 422 | Invalid request | The command was not valid JSON. |
| 422 | action and symbols should be passed in a command | One of the two required keys is missing. |
| 422 | Action and symbols should be string | Wrong types — most often symbols sent as a JSON array instead of a comma-separated string. |
| 422 | Unknown action. Possible actions: subscribe, unsubscribe | The action value is not recognised. |
| 422 | Symbols limit reached | The subscription would exceed your plan’s symbol limit. |
| 422 | Only limited symbols allowed for demo | A demo token asked for a symbol outside the demo list. |
| 429 | Too many connections (token) | The 64-connection budget for this token is full. |
| 500 | Internal error. Try again later | Server-side failure. |
Examples (copy‑paste ready)
Open connection for trades:
wss://ws.eodhistoricaldata.com/ws/us?api_token=demo
Then send:
{"action": "subscribe", "symbols": "AMZN,TSLA"}
Open connection for Forex pairs:
wss://ws.eodhistoricaldata.com/ws/forex?api_token=demo
Then send:
{"action": "subscribe", "symbols": "EURUSD,USDEUR"}
Open connection for Crypto:
wss://ws.eodhistoricaldata.com/ws/crypto?api_token=demo
Then send:
{"action": "subscribe", "symbols": "ETH-USD,BTC-USD"}
Tools for Testing
Chrome: Simple WebSocket Client extension (open a socket, paste open URL, send JSON quote).
EODHD Chrome extension: free mini ticker window for live Stocks/FX/Crypto. Perfect to see our real-time data feed. No commands needed. Install from here.
Postman (or Insomnia) – point-and-click:
- New → WebSocket Request
- URL: wss://ws.eodhistoricaldata.com/ws/crypto?api_token=demo
- Connect → send: {“action”:”subscribe”,”symbols”:”BTC-USD”}
Python (minimal script):
import asyncio, websockets, json
URL = "wss://ws.eodhistoricaldata.com/ws/crypto?api_token=demo"
async def main():
async with websockets.connect(URL) as ws:
await ws.send(json.dumps({"action":"subscribe","symbols":"BTC-USD"}))
while True:
print(await ws.recv())
asyncio.run(main())
Mac Os Terminal – websocat (via Homebrew):
Install websocat:
brew install websocat
One-liner command:
printf '{"action":"subscribe","symbols":"BTC-USD"}\n' | \
websocat "wss://ws.eodhistoricaldata.com/ws/crypto?api_token=demo"
Quick Comparison with Live (Delayed) API and Intraday API
| Aspect | Real-Time (WebSockets) | Live (Delayed) | Intraday Historical |
|---|---|---|---|
| Transport | WebSocket (push) | HTTPS REST (pull) | HTTPS REST (pull) |
| Latency / Freshness | ~live (<50 ms transport) | Stocks: 15–20 min delay; Currencies: ~1 min | Finalized ~2–3 h after US after-hours close |
| Data types | Trades & quotes; minute bars; trading status; FX and crypto ticks | Latest OHLCV snapshot (1-min updates) | OHLCV bars at 1m / 5m / 1h |
| Time ranges | n/a (streaming) | n/a (snapshot feed) | 1m: 120 d · 5m: 600 d · 1h: 7200 d |
| Markets & assets* | US stocks (pre/post supported), European stocks (18 markets), Forex & Digital Currencies | US & Global Stocks, Forex & Digital Currencies | US & Global Stocks, Forex & Digital Currencies |
| Best for | Dashboards, signals, market-making tools | Quote tickers, watchlists, lightweight UIs | Backtests, analytics, charting |
* Get the full list of covered tickers.