Valley LogoBETA
Real-timeWebSocket Streaming

WebSocket Streaming

The Valley WebSocket API lets you subscribe to live GSE market data pushed directly to your client. Instead of polling REST endpoints every few seconds you open one persistent connection and receive updates the moment data changes.

Endpoint wss://api.usevalley.xyz/v1/ws


Authentication

Pass your API key in the Socket.IO handshake auth object. The connection is rejected immediately with a 401 error event if the key is missing or invalid.

js
1import { io } from "socket.io-client";
2
3const socket = io("wss://api.usevalley.xyz", {
4  path: "/v1/ws",
5  transports: ["websocket"],
6  auth: { apiKey: "live_your_key_here" },
7});

Alternatively you may send it as a header (useful in server-to-server contexts):

js
1const socket = io("wss://api.usevalley.xyz", {
2  path: "/v1/ws",
3  transports: ["websocket"],
4  extraHeaders: { "x-api-key": "live_your_key_here" },
5});

Subscribing to a channel

Emit a subscribe event with a channel string. The server will:

  1. Immediately emit one payload on that channel so your UI can render right away.
  2. Continue pushing updates on the same channel at the intervals listed below.
js
1socket.emit("subscribe", { channel: "market:snapshot" });
2
3socket.on("market:snapshot", (payload) => {
4  console.log(payload.data.gainers);
5});

To stop receiving updates emit unsubscribe:

js
1socket.emit("unsubscribe", { channel: "market:snapshot" });

Available channels

ChannelPush intervalDescription
market:snapshot30 sTop gainers, losers, most traded, breadth
quote:<SYMBOL>20 sFull price snapshot for one stock
ticks:<SYMBOL>15 sIntraday tick stream for one stock

Symbols are case-insensitive — quote:MTNGH and quote:mtngh are equivalent.


Payload shape

Every push uses the same { data, meta } envelope as the REST API, plus a channel field in meta:

json
1{
2  "data": { ... },
3  "meta": {
4    "timestamp": "2026-03-03T11:00:00Z",
5    "version": "1.0",
6    "exchange": "GSE",
7    "channel": "quote:MTNGH"
8  }
9}

Channel examples

market:snapshot

json
1{
2  "data": {
3    "gainers": [
4      { "symbol": "MTNGH", "display_price": 2.10, "change_pct": 2.44 }
5    ],
6    "losers": [
7      { "symbol": "EGH", "display_price": 7.02, "change_pct": -1.12 }
8    ],
9    "most_traded": [
10      { "symbol": "GCB", "total_trade_volume": 3200000 }
11    ],
12    "breadth": {
13      "advancing": 12,
14      "declining": 5,
15      "unchanged": 3,
16      "total": 20
17    }
18  },
19  "meta": { "timestamp": "2026-03-03T11:30:00Z", "version": "1.0", "exchange": "GSE", "channel": "market:snapshot" }
20}

quote:<SYMBOL>

json
1{
2  "data": {
3    "latest": {
4      "symbol": "MTNGH",
5      "display_price": 2.10,
6      "bid_price": 2.09,
7      "ask_price": 2.11,
8      "open_price": 2.05,
9      "high_price": 2.12,
10      "low_price": 2.03,
11      "close_price": 2.10,
12      "total_trade_volume": 1250000
13    },
14    "metrics": {
15      "yearHigh": 2.45,
16      "yearLow": 1.72,
17      "avgVolume30d": 980000,
18      "previousClose": 2.05
19    },
20    "company": {
21      "company_name": "Scancom PLC (MTN Ghana)",
22      "sector": "Telecommunications"
23    }
24  },
25  "meta": { "timestamp": "2026-03-03T11:30:20Z", "version": "1.0", "exchange": "GSE", "channel": "quote:MTNGH" }
26}

ticks:<SYMBOL>

json
1{
2  "data": [
3    {
4      "timestamp": "2026-03-03T09:30:00Z",
5      "last_trade_price": 2.05,
6      "bid_price": 2.04,
7      "ask_price": 2.06,
8      "volume": 12500
9    },
10    {
11      "timestamp": "2026-03-03T09:31:00Z",
12      "last_trade_price": 2.07,
13      "bid_price": 2.06,
14      "ask_price": 2.08,
15      "volume": 8200
16    }
17  ],
18  "meta": { "timestamp": "2026-03-03T11:30:15Z", "version": "1.0", "exchange": "GSE", "channel": "ticks:MTNGH" }
19}

Complete example

js
1import { io } from "socket.io-client";
2
3const socket = io("wss://api.usevalley.xyz", {
4  path:       "/v1/ws",
5  transports: ["websocket"],
6  auth:       { apiKey: "live_your_key_here" },
7});
8
9socket.on("connect", () => {
10  console.log("Connected:", socket.id);
11
12  // Subscribe to live market breadth
13  socket.emit("subscribe", { channel: "market:snapshot" });
14
15  // Subscribe to MTN Ghana live quote
16  socket.emit("subscribe", { channel: "quote:MTNGH" });
17});
18
19socket.on("market:snapshot", ({ data }) => {
20  console.log("Advancing:", data.breadth.advancing);
21});
22
23socket.on("quote:MTNGH", ({ data }) => {
24  console.log("MTN price:", data.latest.display_price);
25});
26
27socket.on("error", (err) => {
28  console.error("WS error:", err.message);
29});
30
31socket.on("disconnect", (reason) => {
32  console.warn("Disconnected:", reason);
33});

Error events

The server emits an error event (and then disconnects) for auth failures:

CodeMessage
401Missing API key
401Invalid or revoked API key

Channel-level errors (bad payload, unknown channel) are emitted as a 400 error event without disconnecting.


Notes

  • Only WebSocket transport is supported — long-polling is disabled.
  • Rate limits are not enforced per-message on the WebSocket. A single authenticated connection counts toward your API key's connection quota.
  • The WebSocket server runs on the same port as the REST API (4000 in development, 443 in production).
  • During off-market hours (outside 09:30–15:00 GMT), tick channels will push an empty array. Quote channels still push the last known prices.