This document provides complete documentation for the Matchbook WebSocket API.
| Environment | URL |
|---|---|
| Production | wss://ws.matchbook.taunais.com/v1/stream |
| Devnet | wss://ws.devnet.matchbook.taunais.com/v1/stream |
For user-specific channels (e.g., order updates):
const ws = new WebSocket('wss://ws.matchbook.taunais.com/v1/stream?api_key=YOUR_KEY');All messages are JSON with a type field:
interface Message {
type: string;
[key: string]: any;
}Subscribe to a channel.
{
"type": "subscribe",
"channel": "book",
"market": "7dLVkUfBVfCGkFhSXDCq1ukM9usathSgS716t643iFGF",
"depth": 20
}Channels:
| Channel | Description | Auth Required |
|---|---|---|
book |
Order book updates | No |
trades |
Trade stream | No |
ticker |
Price ticker | No |
orders |
User order updates | Yes |
Channel-specific parameters:
| Channel | Parameter | Type | Description |
|---|---|---|---|
book |
depth |
integer | Number of levels (default: 20) |
orders |
owner |
string | Wallet address to monitor |
Unsubscribe from a channel.
{
"type": "unsubscribe",
"channel": "book",
"market": "7dLVkUfBVfCGkFhSXDCq1ukM9usathSgS716t643iFGF"
}Send a heartbeat.
{
"type": "ping",
"timestamp": 1706640000000
}Confirmation of subscription.
{
"type": "subscribed",
"channel": "book",
"market": "7dLVkUfBVfCGkFhSXDCq1ukM9usathSgS716t643iFGF"
}Confirmation of unsubscription.
{
"type": "unsubscribed",
"channel": "book",
"market": "7dLVkUfBVfCGkFhSXDCq1ukM9usathSgS716t643iFGF"
}Response to ping.
{
"type": "pong",
"timestamp": 1706640000000
}Error message.
{
"type": "error",
"code": "INVALID_MARKET",
"message": "Market not found",
"request_id": "abc123"
}Sent immediately after subscribing to the book channel.
{
"type": "book_snapshot",
"market": "7dLVkUfBVfCGkFhSXDCq1ukM9usathSgS716t643iFGF",
"slot": 234567890,
"sequence": 12345678,
"bids": [
["105.50", "100.5"],
["105.40", "250.0"],
["105.30", "500.0"]
],
"asks": [
["105.60", "75.0"],
["105.70", "200.0"],
["105.80", "350.0"]
]
}Fields:
| Field | Type | Description |
|---|---|---|
market |
string | Market address |
slot |
integer | Solana slot number |
sequence |
integer | Sequence number for ordering |
bids |
array | Bid levels as [price, quantity] |
asks |
array | Ask levels as [price, quantity] |
Incremental updates after the snapshot.
{
"type": "book_update",
"market": "7dLVkUfBVfCGkFhSXDCq1ukM9usathSgS716t643iFGF",
"slot": 234567891,
"sequence": 12345679,
"bids": [
["105.50", "150.0"],
["105.45", "50.0"]
],
"asks": [
["105.60", "0"]
]
}Notes:
- Quantity of
"0"means the price level was removed - Only changed levels are included
- Apply updates in sequence order
New trade executed.
{
"type": "trade",
"market": "7dLVkUfBVfCGkFhSXDCq1ukM9usathSgS716t643iFGF",
"id": "abc123",
"price": "105.55",
"quantity": "5.0",
"side": "buy",
"timestamp": "2026-01-30T12:00:01.234Z"
}Fields:
| Field | Type | Description |
|---|---|---|
id |
string | Trade ID |
price |
string | Trade price |
quantity |
string | Trade quantity |
side |
string | Taker side: buy or sell |
timestamp |
string | Trade timestamp (ISO 8601) |
Price and volume updates.
{
"type": "ticker",
"market": "7dLVkUfBVfCGkFhSXDCq1ukM9usathSgS716t643iFGF",
"best_bid": "105.50",
"best_ask": "105.60",
"last_price": "105.55",
"volume_24h": "1234567.89",
"price_change_24h": "2.34",
"high_24h": "108.00",
"low_24h": "102.00",
"timestamp": "2026-01-30T12:00:00.000Z"
}Fields:
| Field | Type | Description |
|---|---|---|
best_bid |
string | Best bid price |
best_ask |
string | Best ask price |
last_price |
string | Last trade price |
volume_24h |
string | 24-hour volume |
price_change_24h |
string | 24-hour price change (%) |
high_24h |
string | 24-hour high |
low_24h |
string | 24-hour low |
{
"type": "subscribe",
"channel": "orders",
"owner": "ABC123..."
}Updates to user's orders.
{
"type": "order_update",
"market": "7dLVkUfBVfCGkFhSXDCq1ukM9usathSgS716t643iFGF",
"order_id": "340282366920938463463374607431768211455",
"client_order_id": 12345,
"status": "partial_fill",
"side": "bid",
"price": "105.00",
"original_quantity": "100.0",
"filled_quantity": "25.0",
"remaining_quantity": "75.0",
"average_price": "105.50",
"timestamp": "2026-01-30T12:00:01.234Z"
}Order Status Values:
| Status | Description |
|---|---|
open |
Order is active on the book |
partial_fill |
Order partially filled |
filled |
Order completely filled |
cancelled |
Order cancelled by user |
expired |
Order expired (IOC/FOK) |
Individual fill on user's order.
{
"type": "fill",
"market": "7dLVkUfBVfCGkFhSXDCq1ukM9usathSgS716t643iFGF",
"order_id": "340282366920938463463374607431768211455",
"client_order_id": 12345,
"trade_id": "xyz789",
"price": "105.50",
"quantity": "10.0",
"role": "maker",
"fee": "0.10550",
"fee_token": "USDC",
"timestamp": "2026-01-30T12:00:01.234Z"
}Send a ping message every 30 seconds to keep the connection alive:
setInterval(() => {
ws.send(JSON.stringify({ type: 'ping', timestamp: Date.now() }));
}, 30000);The server will disconnect after 60 seconds of inactivity.
On disconnect, implement exponential backoff:
let reconnectDelay = 1000;
const maxDelay = 30000;
function reconnect() {
setTimeout(() => {
ws = new WebSocket(url);
ws.onopen = () => {
reconnectDelay = 1000; // Reset on success
resubscribe();
};
ws.onclose = () => {
reconnectDelay = Math.min(reconnectDelay * 2, maxDelay);
reconnect();
};
}, reconnectDelay);
}Book updates include sequence numbers. If you receive an out-of-order message:
- Unsubscribe from the channel
- Resubscribe to get a fresh snapshot
let expectedSequence = null;
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
if (msg.type === 'book_snapshot') {
expectedSequence = msg.sequence;
} else if (msg.type === 'book_update') {
if (msg.sequence !== expectedSequence + 1) {
// Sequence gap detected, resubscribe
ws.send(JSON.stringify({ type: 'unsubscribe', channel: 'book', market }));
ws.send(JSON.stringify({ type: 'subscribe', channel: 'book', market }));
return;
}
expectedSequence = msg.sequence;
}
};interface OrderBook {
bids: Map<string, string>;
asks: Map<string, string>;
sequence: number;
}
const orderbooks = new Map<string, OrderBook>();
function handleMessage(msg: any) {
switch (msg.type) {
case 'book_snapshot':
orderbooks.set(msg.market, {
bids: new Map(msg.bids),
asks: new Map(msg.asks),
sequence: msg.sequence,
});
break;
case 'book_update':
const book = orderbooks.get(msg.market);
if (!book || msg.sequence !== book.sequence + 1) {
// Resubscribe
return;
}
for (const [price, qty] of msg.bids) {
if (qty === '0') {
book.bids.delete(price);
} else {
book.bids.set(price, qty);
}
}
for (const [price, qty] of msg.asks) {
if (qty === '0') {
book.asks.delete(price);
} else {
book.asks.set(price, qty);
}
}
book.sequence = msg.sequence;
break;
}
}| Code | Description |
|---|---|
INVALID_MESSAGE |
Malformed JSON or missing fields |
INVALID_CHANNEL |
Unknown channel name |
INVALID_MARKET |
Market not found |
UNAUTHORIZED |
Authentication required |
SUBSCRIPTION_LIMIT |
Too many subscriptions |
RATE_LIMIT |
Too many messages |
- API Reference - REST API documentation
- SDK Guide - SDK usage examples
- Getting Started - Integration guide