Skip to content

WebSocket API

VALR provides real-time WebSocket APIs for streaming account updates and market data. WebSocket connections offer lower latency than polling REST endpoints and enable bidirectional communication for order placement.

Channels

VALR operates two WebSocket channels, each serving a distinct purpose:

Account Channel

wss://api.valr.com/ws/account

The Account channel provides authenticated, account-specific events and commands:

  • Balance updates — real-time notification when any currency balance changes
  • Order lifecycle events — order status changes, processing results, and failures
  • Trade notifications — new trades on your account
  • Transaction history — deposits, withdrawals, fees, and rewards
  • Order commands — place, cancel, and modify orders via WebSocket for lower latency

🔐 Authentication Required

The Account channel requires authentication. All events and commands require a valid API key.

Trade Channel

wss://api.valr.com/ws/trade

The Trade channel provides real-time market data:

  • Order book updates — aggregated order book with price levels
  • New trades — live trade feed with price, quantity, and taker side
  • Market summaries — bid/ask prices, volume, and 24-hour statistics

💡 No Auth Needed

The Trade channel supports unauthenticated connections. You can receive market data without API credentials. Note that unauthenticated connections are automatically disconnected after 15 minutes.

Authentication

WebSocket connections are authenticated using HMAC-based API key credentials. Two methods are available:

Header-based Authentication

Authentication is performed by passing HTTP headers during the WebSocket handshake. The following headers are required:

HeaderDescription
X-VALR-API-KEYYour API key
X-VALR-SIGNATUREHMAC-SHA512 signature
X-VALR-TIMESTAMPUnix timestamp in milliseconds

The signature is computed as:

Signature = HMAC-SHA512(API_SECRET, TIMESTAMP + "GET" + "/ws/account")

NodeJS example:

javascript
const crypto = require('crypto');
const WebSocket = require('ws');

function signRequest(apiSecret, timestamp, verb, path) {
    return crypto
        .createHmac('sha512', apiSecret)
        .update(timestamp + verb + path)
        .digest('hex');
}

function getAuthHeaders(apiKey, apiSecret, path) {
    const timestamp = Date.now().toString();
    const signature = signRequest(apiSecret, timestamp, 'GET', path);
    return {
        'X-VALR-API-KEY': apiKey,
        'X-VALR-SIGNATURE': signature,
        'X-VALR-TIMESTAMP': timestamp,
    };
}

const headers = getAuthHeaders('YOUR_API_KEY', 'YOUR_API_SECRET', '/ws/account');
const ws = new WebSocket('wss://api.valr.com/ws/account', { headers });

ws.on('open', () => {
    console.log('Connected and authenticated');
});

ws.on('message', (data) => {
    console.log('Received:', JSON.parse(data));
});

In-band Authentication (Alternative)

Alternatively, you can connect first and then send an authentication message in-band:

json
{
    "type": "AUTHENTICATE",
    "payload": {
        "apiKey": "YOUR_API_KEY",
        "signature": "SIGNATURE",
        "timestamp": 1234567890123
    }
}

The signature is computed the same way as for header-based authentication.

For detailed authentication instructions and code examples, see the Authentication guide.

Ping-Pong Keepalive

To maintain an active connection, clients must send a PING message at least every 30 seconds. VALR will respond with a PONG message.

Send:

json
{
    "type": "PING"
}

Response:

json
{
    "type": "PONG"
}

TIP

On the Trade WebSocket, the PONG response body will be "NO_SUBSCRIPTIONS" if you are not currently subscribed to any message types.

Subscribing to Events

Some events on the Account channel are automatically delivered upon connection. Others require explicit subscription, as do all Trade channel events.

To subscribe, send a message with the event names you wish to receive:

json
{
    "type": "SUBSCRIBE",
    "subscriptions": [
        { "event": "ORDER_STATUS_UPDATE" },
        {
            "event": "AGGREGATED_ORDERBOOK_UPDATE",
            "pairs": ["BTCZAR", "ETHZAR"]
        }
    ]
}

The optional pairs filter limits Trade channel events to specific currency pairs. If omitted, you receive events for all pairs.

Unsubscribing

Account channel — send an UNSUBSCRIBE message:

json
{
    "type": "UNSUBSCRIBE",
    "requests": [
        { "event": "ORDER_STATUS_UPDATE" }
    ]
}

Trade channel — send a SUBSCRIBE message with an empty pairs array for the event you wish to unsubscribe from:

json
{
    "type": "SUBSCRIBE",
    "subscriptions": [
        {
            "event": "AGGREGATED_ORDERBOOK_UPDATE",
            "pairs": []
        }
    ]
}

Message Format

All server-to-client messages follow an envelope structure:

Account channel:

json
{
    "type": "EVENT_TYPE",
    "data": { ... }
}

Trade channel:

json
{
    "type": "EVENT_TYPE",
    "currencyPairSymbol": "BTCZAR",
    "data": { ... }
}