Measure listing WebSocket delivery from your bot server
Measure how long messages from the New Listings Feed WebSocket API take to reach the server where your bot runs. This guide captures Full event publication-to-client delivery with a Node.js receiver, records the applied plan delay and explains how clock accuracy affects the result.
What this test measures
This guide measures the time between the Full endpoint setting sent_time_us while preparing a message and your application receiving it. The timestamp is recorded after the configured plan delay, before the write to your socket completes.
The result is publication-to-client delivery. It includes server queue and socket-write time, transport, client scheduling and clock offset. It does not isolate network latency or measure original announcement publication to detection.
For a listing bot, measure the event your system can act on. A raw notice and an enriched event contain different amounts of information. The bot and sniping guide explains how to choose a comparable input before running this test.
Connect with a free key
Get a FREE key through the WebSocket page. Allow about a minute for a new key to activate, then use it as the bearer token for wss://ws.newlistings.pro/v2/full. This guide uses the v2 full schema.
With STARTER or PRO, use wss://tokyo.newlistings.pro/v2/full, wss://ny.newlistings.pro/v2/full or wss://seoul.newlistings.pro/v2/full. In the script below, set context.endpoint_url to your selected Full URL and NLF_API_KEY to your paid key. Keep the receiving server, plan and client workload the same when comparing arrivals.
The first READY message reports your serving endpoint, subscription.plan and subscription.delay_ms. Save those values so each result identifies the connection you actually tested.
FREE has a 3-second configured delay before sent_time_us is stamped for publication. Use it to test your bot integration and measure delivery after that wait. To evaluate our fastest delivery, run a separate capture with PRO, which has zero configured delay. Keep the applied plan and delay with every result; zero configured delay does not mean zero network latency.
Fields, units and the calculation
For live /v2/full events, calculate observed_publication_to_receive_ms = (received_us - sent_time_us) / 1000. Capture receive time before parsing JSON, logging or doing other work. This measures delivery to your application, including buffering and scheduling before the message handler runs.
Fast sets sent_time_us before its configured plan delay. On Fast, the same subtraction includes that delay. Compare the same endpoint and plan when using this measurement.
Scroll horizontally to compare all columns.
| Field | Unit | Meaning |
|---|---|---|
| sent_time_us | Unix epoch microseconds | Full publication timestamp, recorded while preparing serialization after the configured plan delay. It does not mark exchange publication or a completed write to your socket. |
| received_us | Unix epoch microseconds | Your client timestamp at the start of the message handler, before JSON parsing. This is a field created by the example, not sent by the feed. |
| detected_time_us | Unix epoch microseconds | The recorded detection timestamp. It is not proof of first exchange availability and is not the starting point for this delivery calculation. |
| subscription.delay_ms | Milliseconds | The configured publish delay reported in READY. Keep it with each run; the calculation above excludes the wait before event publication. |
| endpoint / subscription.plan | Text | The serving endpoint and applied plan from READY. Record your receiver region separately. |
| received_mono_ns | Local monotonic nanoseconds | A local monotonic timestamp, saved as a decimal string. Use it for local intervals; its origin is unrelated to Unix epoch timestamps. |
Check the clocks before trusting the number
The IETF one-way delay standard explains that clock synchronization error and clock resolution affect a timestamp subtraction. If your clock is ahead of ours, the observed delay is inflated; if it is behind, the delay is understated. A negative result needs a clock or timestamp check. Keep it in the raw log.
Check synchronization before and after the run. On a Linux host using chrony, save chronyc tracking and chronyc sources -v. Record system offset, root delay, root dispersion, reference time and synchronization status. These describe your clock; they do not establish the error of our server clock. If the combined uncertainty is unknown, label the result an uncorrected observation.
The Node.js example combines performance.timeOrigin with performance.now() to estimate epoch time. It captures process.hrtime.bigint() separately for local intervals. Microsecond units do not guarantee microsecond accuracy. Synchronize the clock before starting Node, and restart the capture after a clock correction.
Run a five-minute Node.js capture
Use Node.js on the host where you intend to consume the feed. Install ws, set NLF_API_KEY to your free key and NLF_RECEIVER_REGION to your host location. Save the script as measure-feed.cjs, then run node measure-feed.cjs > feed-measurement.jsonl. Use a new output filename for each run.
The script captures five minutes after connection, or stops when you press Ctrl+C or the connection closes. It records the applied plan and delay without logging your key. Check the end record for counts and the stop reason. Repeat across sessions to collect more events; if a window contains no announcements, run another capture.
This example makes one connection, with no automatic reconnect or replay. It measures when the Node.js message handler starts. Earlier logging and other work can delay later handlers, so use the same client and workload across runs.
// npm install ws
// Set NLF_API_KEY and NLF_RECEIVER_REGION in your environment.
// Run: node measure-feed.cjs > feed-measurement.jsonl
const WebSocket = require("ws");
const { performance } = require("node:perf_hooks");
if (!process.env.NLF_API_KEY || !process.env.NLF_RECEIVER_REGION) {
throw new Error("Set NLF_API_KEY and NLF_RECEIVER_REGION first.");
}
const context = {
endpoint_url: "wss://ws.newlistings.pro/v2/full",
receiver_region: process.env.NLF_RECEIVER_REGION,
};
const counts = { events: 0, unmeasured: 0, negative: 0 };
const record = row => console.log(JSON.stringify(row));
let timer;
let finished = false;
const ws = new WebSocket(context.endpoint_url, {
headers: { authorization: `Bearer ${process.env.NLF_API_KEY}` },
handshakeTimeout: 10000,
});
function finish(reason, closeCode = null) {
if (finished) return;
finished = true;
clearTimeout(timer);
record({ kind: "end", utc: new Date().toISOString(), reason,
close_code: closeCode, ...counts });
ws.terminate();
}
ws.on("open", () => {
record({ kind: "start", ...context, utc: new Date().toISOString(),
node_version: process.version });
timer = setTimeout(() => finish("five-minute window complete"), 300000);
});
ws.on("message", raw => {
const receivedUs = Math.round(
(performance.timeOrigin + performance.now()) * 1000
);
const receivedMonoNs = process.hrtime.bigint().toString();
if (finished) return;
let event;
try { event = JSON.parse(raw.toString()); } catch { event = null; }
if (event?.code === "READY") {
context.serving_endpoint = event.endpoint;
context.plan = event.subscription?.plan;
context.delay_ms = event.subscription?.delay_ms;
record({ kind: "ready", ...context });
return;
}
const sentUs = event?.sent_time_us;
if (!Number.isSafeInteger(sentUs) || sentUs < 1e15 || sentUs >= 1e16) {
counts.unmeasured++;
record({ kind: "unmeasured", received_us: receivedUs,
code: event?.code ?? null });
return;
}
const deltaMs = (receivedUs - sentUs) / 1000;
counts.events++;
counts.negative += Number(deltaMs < 0);
record({ kind: "event", ...context, id: event.id, url: event.url,
sent_time_us: sentUs, received_us: receivedUs,
received_mono_ns: receivedMonoNs,
observed_publication_to_receive_ms: deltaMs,
clock_check_required: deltaMs < 0 });
});
ws.on("close", code => finish("connection closed", code));
ws.on("error", () => finish("connection error"));
process.once("SIGINT", () => finish("user stopped"));Read your result
This shortened record is synthetic, only to show the arithmetic. It is not a New Listings Feed benchmark. The delay value is illustrative; your READY message supplies the actual setting.
The two timestamps differ by 2,500 microseconds, giving an observed 2.5 milliseconds after event publication. That value is uncorrected for clock error and excludes the configured wait before event publication. Build your summary from the complete capture.
{
"kind": "event",
"receiver_region": "Tokyo",
"plan": "feed_free",
"delay_ms": 3000,
"sent_time_us": 1789171200000000,
"received_us": 1789171200002500,
"observed_publication_to_receive_ms": 2.5,
"clock_check_required": false
}Keep enough evidence to reproduce your result
Report the UTC start and end, endpoint, receiver region, applied plan and delay, client version, clock checks and event count. Keep the original timestamp rows. State any filters and the number of rows they remove.
For each consistent setup, report the median, p95, p99 and maximum with the event count and connection duration. Include the clock-check flags, unmeasured-frame count and any disconnects so the capture is reproducible. Use a larger sample for meaningful percentiles; this short test measures delivery rather than service uptime.
To compare listing feeds for your bot, match the same official announcement URL and event type on one receiver. Capture both arrival times with the same monotonic clock. Record each plan and endpoint, and compare raw notices with raw notices or parsed events with parsed events. Repeat across announcements and sessions. Our publication timestamp alone cannot establish who delivered an announcement first.
Check the event as well as the timing
Check the exchange, classification and ticker symbols your listing bot needs alongside the timing results. Review the source URL and available original title. On paid keys, also inspect any token enrichment. New Listings Feed provides the event data; you build and run the execution system.
Keep reading
- Crypto listing WebSocket coverage across 24 exchanges
- Binance new listings and Alpha alerts over WebSocket
- Ultra-fast Upbit listings WebSocket for bots and snipers.
- Bithumb listing WebSocket API for spot listings and delistings
- Robinhood asset additions and spot listings
- Coinbase new listings and roadmap alerts over WebSocket
- WebSocket performance
- Onchain data for crypto listing and sniping bots
- The fastest crypto listing WebSocket, measured from your server
- The fastest listing feed for crypto sniper bots
- Listing announcement WebSockets and market-data WebSockets
- Crypto listing alerts on Telegram and WebSocket
- Receive crypto listing alerts in Node.js
- New Listings Feed vs other listing services
- Crypto alerts for listings, prices, whales and news
- Super-fast Binance Alpha listing WebSocket for bots and snipers