Skip to main content
The maker WebSocket lets you price RFQs without running a public server. You keep one connection open. We push each quote round to you, and you answer on the same connection. A quote round is the auction for one RFQ (request for quote). Every registered pricer can quote on it. The socket replaces the HTTP server that webhooks need. You need no TLS certificate, no public hostname, and no signature checks. You authenticate once, when you connect. The timing is the same as for webhooks. Each auction lasts 3 seconds. When you win, you get a 1-second last look: a final chance to confirm or reject the trade. If you don’t answer in time, we reject the trade.

Connect

Send your OAuth 2.0 access token as a bearer token on the upgrade request. It’s the same market-maker token the REST API uses. See Authentication.
The token identifies your trader, so your messages carry no pricer ID and no signature.

Register a pricer before you connect

We refuse the upgrade if your trader has no pricer registration. Register once with POST /rfq/pricer and the body {}. A socket-only pricer needs no webhook and gets no shared secret. The registration outlives every connection, so you don’t register again when you reconnect. See Registration.

Why the upgrade fails

We check your token and your registration before the upgrade. A failure comes back as a plain HTTP status.

Channels

A channel is a stream of events you subscribe to. There are two. We send rfq events to every pricer. We send quotes events only to you.
Subscribe to both channels to trade. Without quotes you never see quote_accepted, so we reject every trade you win.

How a round works

1

We open a round

rfq_created arrives on rfq with the legs, the minimum stake, and the deadline.
2

You answer

Send create_quote before expires_at, or send decline to pass.
3

The auction closes

rfq_closed arrives. Stop pricing that round.
4

You confirm a win

If you win, quote_accepted arrives on quotes. Send confirm within 1 second.
5

The trade fills or doesn't

After you confirm, quote_executed or quote_unfilled arrives on quotes.

Message format

Every message is a JSON text frame with two keys: event names the message, and data holds its payload. We answer every message you send with one ack or one error. Every frame we send also carries timestamp, the time we emitted it. Other times are named fields in data. For example, expires_at is when the auction closes, and executed_at is when the trade filled. We send decimal amounts like price, max_wager, and wager as JSON strings. Send yours as strings too.

Messages you send

data is the channel name as a string, not an object. We ack with subscribed.
string
required
"rfq" or "quotes".
We ack with unsubscribed.
string
required
"rfq" or "quotes".
We ack with quote_submitted.
UUID string
required
The round, from rfq_created.
decimal string
required
A decimal probability between 0 and 1. "0.45" means a $0.45 stake per $1.00 payout. We round it to the nearest 0.001.
decimal string
required
The largest taker stake this quote covers, in Novig Cash. It must be above zero.
string
Your own reference for the quote, up to 256 bytes. We return it to you as external_flag. Leave it out if you don’t need one.
You get one quote per round, and your first quote stands. We drop a second quote for the same rfq_id and ack it with quote_superseded. To change your price, wait for the next round.
We ack with quote_declined.
UUID string
required
The round, from rfq_created.
Declining is optional. A round you don’t answer closes on its own.A decline tells us you saw the round and passed. Silence looks the same as a pricer that is down or lagging.
We ack with confirm_recorded.
UUID string
required
The round, from quote_accepted.
UUID string
required
Our ID for your quote, from quote_accepted. It’s not the quote_id you sent on create_quote.
boolean
required
true fills the trade at your quoted price. false rejects it.
You have 1 second, and silence rejects the trade. A late answer, or a dropped connection before you answer, rejects it just as false does.
Your first answer counts. A second answer gets CONFIRM_ALREADY_DECIDED, or NO_CONFIRM_WINDOW once the window has closed.

Events we send

You get a channel’s events after you subscribe to it.
Send your quote or decline before expires_at.
string
required
"rfq_created".
ISO-8601 string
required
When we emitted the frame.
UUID string
required
The round. Send it on every message about this round.
array of UUID strings
required
The legs, in the order the taker asked for them. GET /rfq/executions sorts the same legs by outcome ID. See Universe model.
ISO-8601 string
required
When the auction closes, 3 seconds after we opened the round.
decimal string
required
The smallest stake a quote must cover: the taker’s minimum or our minimum of $10.00, whichever is higher. We drop a quote whose max_wager is below it, with reason below_min_wager.
Stop pricing the round. We answer a create_quote that arrives after this with RFQ_CLOSED.
string
required
"rfq_closed".
ISO-8601 string
required
When we emitted the frame.
UUID string
required
The round that closed.
Every pricer sees this event, so it names no one.
string
required
"rfq_executed".
ISO-8601 string
required
When we emitted the frame.
UUID string
required
The round.
decimal string
required
The amount staked.
decimal string
required
The execution price.
ISO-8601 string
required
When the trade filled.
We recorded your quote when the auction closed.
string
required
"quote_created".
ISO-8601 string
required
When we emitted the frame.
UUID string
required
Our ID for your quote.
UUID string
required
The round.
UUID string
required
Your pricer.
The taker is trading against your quote. Answer with confirm.
string
required
"quote_accepted".
ISO-8601 string
required
When we emitted the frame.
UUID string
required
Our ID for your quote. Send this one back on confirm.
UUID string
required
The round.
UUID string
required
Your pricer.
decimal string
required
The taker’s stake. It’s never above the max_wager you quoted.
decimal string
required
The price you quoted.
string or null
required
The quote_id you sent on create_quote, or null if you sent none.
This event names your pricer, which rfq_executed leaves out.
string
required
"quote_executed".
ISO-8601 string
required
When we emitted the frame.
UUID string
required
Our ID for your quote.
UUID string
required
The round.
UUID string
required
Your pricer.
decimal string
required
The amount staked.
decimal string
required
The execution price.
ISO-8601 string
required
When the trade filled.
You confirmed, but the trade didn’t fill. Release anything you reserved for it.A quote you confirm ends in either quote_executed or quote_unfilled.
string
required
"quote_unfilled".
ISO-8601 string
required
When we emitted the frame.
UUID string
required
Our ID for your quote.
UUID string
required
The round.
UUID string
required
Your pricer.
string
required
Why the trade didn’t fill.
We send this only when you can fix the cause. We don’t tell you when you lose on price.
string
required
"quote_dropped".
ISO-8601 string
required
When we emitted the frame.
UUID string
required
Our ID for your quote.
UUID string
required
The round.
UUID string
required
Your pricer.
string
required
below_min_wager, unbacked, or own_rfq. The first two add one field with the number you must beat.
The round’s minimum stake is the taker’s minimum or ours, whichever is higher.

Acks

An ack confirms that we handled your message.
Answers your subscribe.
string
required
"subscribed".
ISO-8601 string
required
When we emitted the frame.
string
required
"rfq" or "quotes".
Answers your unsubscribe.
string
required
"unsubscribed".
ISO-8601 string
required
When we emitted the frame.
string
required
"rfq" or "quotes".
Answers your create_quote.
string
required
"quote_submitted".
ISO-8601 string
required
When we emitted the frame.
UUID string
required
The round your message named.
Answers your create_quote.
string
required
"quote_superseded".
ISO-8601 string
required
When we emitted the frame.
UUID string
required
The round your message named.
Answers your decline.
string
required
"quote_declined".
ISO-8601 string
required
When we emitted the frame.
UUID string
required
The round your message named.
Answers your confirm.
string
required
"confirm_recorded".
ISO-8601 string
required
When we emitted the frame.
UUID string
required
The round your message named.
UUID string
required
The quote your answer decided.

Errors

We answer a message we can’t handle with an error frame.
string
required
"error".
ISO-8601 string
required
When we emitted the frame.
string
required
A stable code. Match on this, not on message.
string
required
A human-readable description.
string
Reserved for the channel a failed message named. We don’t send it today.
UUID string
The round the failed message named. We leave it out when we can’t parse your message.
An error never closes the connection. Handle it and keep trading. Don’t reconnect because of an error. We check that you may quote on every action, not only when you connect. If we remove or restrict your pricer, you keep the connection. Your next create_quote, decline, or confirm gets FORBIDDEN.

Timing and limits

Actions are create_quote and decline. confirm is exempt from the rate limit. We drop only event frames when your queue is full. We never drop acks or errors.

Keep the connection alive

We send a WebSocket Ping every 15 s. Answer with a Pong. Most clients do this for you. If no Pong arrives before our next Ping, we close the connection. There’s no JSON ping message and no idle timeout. A connection that answers every Ping stays open.

Reconnect

Subscribe again after every reconnect. Subscriptions belong to the connection, so a new one gets no events until you subscribe.
We don’t replay or backfill. A new connection starts at the live edge, so you lose anything we sent while you were away. Get missed executions from the executions listing. We close a connection without a status code, so don’t read meaning into the close frame. Reconnect with backoff, then subscribe again. If you read too slowly, we drop event frames instead of waiting for you. Read promptly, or you’ll miss quote rounds.

Use webhooks and the socket together

A webhook and the socket are independent. You can register a webhookUrl, connect the socket, or do both. With both, each round reaches you twice. Your first quote wins, and we drop the other. If the dropped quote came over the socket, we ack it with quote_superseded. A round your webhook won is confirmed over HTTP. A round your socket won arrives as quote_accepted. Handle confirms on both legs, or run only one. To make the socket your only path, leave out webhookUrl on your first registration. On an existing registration, leaving it out keeps your webhook. See Registration.