For the complete documentation index, see llms.txt. This page is also available as Markdown.

Heartbeats

Reya's WebSocket APIs (both Info and Order Entry) use identical heartbeat mechanics. This page is the single source of truth for connection liveness and close codes on both surfaces.

For most clients, no action is required. Any standards-compliant WebSocket library handles heartbeats automatically at the protocol layer: the server issues protocol-level pings, the client library replies with pongs, and the connection stays alive with no application-level code — no ping handler, no pong to send, no timer to track. The one exception is clients behind aggressive network middleboxes; see Behind a strict middlebox?.

The Two Layers

There are two distinct "ping/pong" concepts in a WebSocket connection, and confusing them is a common source of integration bugs.

Layer 1 — Protocol-level heartbeat (RFC 6455 control frames)

This is the mechanism Reya uses to detect dead connections. It runs below the application layer (RFC 6455 §5.5.2): the server sends a PING control frame (opcode 0x9), and every standards-compliant client library automatically replies with a PONG control frame (opcode 0xA). If the client is dead (TCP disconnected, process crashed, network gone), no PONG arrives and the server closes the connection after the idle timeout.

This layer is invisible to your application code — your onmessage / on_message handler is never invoked for protocol pings or pongs.

Layer 2 — Application-level JSON ping (optional)

A separate, optional feature: clients may send {"type":"ping"} and the server replies {"type":"pong"}. This does not keep the connection alive — Layer 1 already does that. Use Layer 2 only for measuring application-level round-trip latency, correlating a probe to its reply via the id field, or asserting the backend is processing application messages (not just that the TCP socket is up). Otherwise ignore it. See The Optional Application-Level Probe.

Heartbeats are not Cancel-on-Disconnect

Neither layer is Cancel-on-Disconnect (COD). Liveness is governed by the protocol-level heartbeat and idle timeout here; COD is a separate, account-scoped armed timer refreshed only by sending cancelAllAfter — order-entry traffic, pings, and the socket itself do not touch it, and a dropped or idle socket does not fire COD. See Cancel-on-Disconnect.

Heartbeat and idle timeout

Both WebSocket APIs send protocol-level pings. Your WebSocket library should reply with pongs automatically.

A connection closes after 120 seconds without inbound traffic, including protocol pong replies. Keep heartbeat handling enabled and reconnect with backoff if the connection closes.

Behind a Strict Middlebox?

Some networks (corporate firewalls, mobile carriers, aggressive load balancers) drop "idle" TCP connections after ~30–60 seconds. Reya's server-sent pings are paced for the server's 120-second timeout, not for the most aggressive middleboxes. If you hit spurious disconnects through such a network, configure your client to send its own protocol-level pings at a shorter interval — one line of config in every common library:

Python (websocket-client):

Python (websockets, async):

Node (ws):

Rust (tokio-tungstenite):

Browser (vanilla WebSocket): The browser handles protocol-level pings internally and exposes no API to send them from JavaScript. If a browser-based client gets dropped by a middlebox, the only knob is application-level: send a {"type":"ping"} JSON frame periodically (see below).

The "20 seconds" figure is conservative — fast enough for almost any middlebox, light enough to be invisible in traffic. Adjust if your environment requires something more aggressive.

The Optional Application-Level Probe

If you need Layer 2, here's the wire format. Client sends:

  • type: required, literal "ping".

  • id: optional free-form string, echoed back on the reply for correlation; omit it for a plain round-trip check.

Server replies:

The id is echoed back only if the client sent one. Active servers do not supply a timestamp; measure round-trip latency from your own ping send time. Canonical Info AsyncAPI 3.6.2 still permits an optional timestamp on ping and pong, while Order Entry does not, so portable clients should use only type and id and must not treat the optional field as a server-time source. Layer 2 is purely additive — it runs in parallel with Layer 1 and doesn't disable it.

Do not send {"type":"pong"} from your client. Client-initiated JSON pong frames are invalid and receive an "Invalid type" / UNKNOWN_TYPE error. The only pong direction is server→client, in reply to a client {"type":"ping"}.

What Happens When the Server Closes the Connection

The server can close a connection for several reasons; you'll see different close codes depending on cause:

Close code
Meaning
Recommended client action

1000

Normal closure (you or the server called close cleanly)

Reconnect if you still want a connection

1001

Order Entry service restarting

Reconnect after a short backoff; reconcile any unanswered request before retrying

1012

Info connection must be re-established

Reconnect and re-subscribe for a fresh snapshot; treat the reason string as advisory

1013

Info client is not keeping up with updates

Reconnect and re-subscribe; make sure the consumer can keep up before resuming

4029

Inbound message or IP connection limit reached

Follow the close reason's guidance in Rate Limits; reconcile unanswered Order Entry requests

1006 / 1011 / others

Idle timeout, server error, abrupt closure

Reconnect with exponential backoff

If the connection is still desired, reconnect with backoff. On Info, replay subscriptions and rebuild from the new snapshots. On Order Entry, reconcile any unanswered request against REST/Info state before retrying; acceptance by the matching engine and on-chain settlement are separate outcomes.

For the surface-specific post-reconnect steps, see Reconnection Pattern on the Info WebSocket reference and Reconnection Pattern on the Order Entry WebSocket reference.

Service restarts

During a service restart:

  • Info WebSocket: connections close with code 1012. Reconnect and re-subscribe for fresh snapshots.

  • Order Entry WebSocket: new requests may receive SERVER_SHUTTING_DOWN, and connections close with code 1001. Requests already sent may have completed; reconcile any unanswered request before resubmitting it.

Both codes are soft reconnect signals, but their recovery state differs: Info rebuilds subscriptions from snapshots; Order Entry reconciles request outcomes.

Last updated