Infrastructure

WebSocket Streaming Comes to AllenHark Relay: Persistent Connections for Transactions and Bundles

May 3, 2026AllenHark Team

WebSocket Streaming Comes to AllenHark Relay

We just shipped WebSocket support across all four AllenHark Relay regions. You can now open a single persistent connection to any region and stream transactions — or full atomic bundles — for as long as you like. No new handshake per request, no TCP slow-start every time, no hand-rolled QUIC client just to keep latency tight.

This post is about why we built it, what it changes, and how to use it.

The Three Submission Paths You Now Have

AllenHark Relay has always exposed two ways to submit a transaction:

  • HTTPS at https://{region}.relay.allenhark.com/v1/sendTx — one TCP+TLS handshake per transaction. Easy to integrate. Bad latency profile when you're sending continuously.
  • QUIC at {ip}:4433 — persistent stake-weighted protocol with multiplexed streams. Lowest latency in production, but a real Rust/Go client is essentially required, and browsers can't speak it.

WebSockets fill the gap. As of today:

ProtocolEndpointBest for
HTTPShttps://{region}.relay.allenhark.com/v1/sendTxSimple integrations, infrequent sends
WSS (tx)wss://{region}.relay.allenhark.com/v1/stream/txContinuous streaming, browsers, language-agnostic
WSS (bundle)wss://{region}.relay.allenhark.com/v1/stream/bundleAtomic multi-tx bundles
QUIC{ip}:4433Absolute lowest latency, native Rust/Go clients

{region} is fra, ams, ny, or tyo. relay.allenhark.com is a synonym for Frankfurt.

Why a Persistent Socket Matters

A new HTTPS request to a Solana relay isn't free. You're paying:

  1. TCP handshake — one round-trip
  2. TLS handshake — one or two more round-trips
  3. HTTP request framing — headers parsed every time
  4. Connection close — fin, ack, retransmit windows discarded

On a good link, that's 30-80ms before your first byte of transaction data even gets read. On a bad link or a cold connection, it's 200ms+. For a sniper bot or arbitrage system that's submitting hundreds of transactions a minute, that handshake tax compounds into real money.

A WebSocket connection pays the handshake cost once. After that, every transaction is just a JSON frame on a hot, kept-alive socket. Round-trip times drop to whatever the underlying network gives you — 5-20ms in-region, sometimes less.

It's the same insight that makes our QUIC endpoint fast. WebSockets just bring it to anyone who can speak HTTPS.

Browser-Native, SDK-Friendly

Every modern programming language has a first-class WebSocket client. The browser ships with one. Python has websockets. Node has ws. Rust has tokio-tungstenite. Go has gorilla/websocket. They all work with our endpoint without any custom protocol code.

// Browser — yes, really. No proxy, no custom client.
const ws = new WebSocket("wss://fra.relay.allenhark.com/v1/stream/tx?api-key=YOUR_KEY");
ws.onopen = () => ws.send(JSON.stringify({ id: "1", tx: BASE64_TX }));
ws.onmessage = (e) => console.log(JSON.parse(e.data));
// → { "id": "1", "status": "accepted", "request_id": "...", "signature": "..." }

That's it. You're now streaming transactions to a Solana relay from a webpage. No CORS workarounds, no service worker, no hosted backend just to bridge a custom protocol.

For Python:

import asyncio, json, websockets

async def submit(tx_b64):
    async with websockets.connect(
        "wss://fra.relay.allenhark.com/v1/stream/tx",
        additional_headers={"x-api-key": "YOUR_KEY"},
    ) as ws:
        for i, tx in enumerate(tx_b64):
            await ws.send(json.dumps({"id": str(i), "tx": tx}))
            print(await ws.recv())

Bundles: The Other Half of the Update

Bundles — atomic groups of up to 5 transactions that land together or not at all — were not previously supported by AllenHark Relay at all. The new WebSocket bundle stream changes that.

const ws = new WebSocket("wss://fra.relay.allenhark.com/v1/stream/bundle?api-key=YOUR_KEY");

ws.onopen = () => ws.send(JSON.stringify({
  id:  "b1",
  txs: [BASE64_TX_1, BASE64_TX_2, BASE64_TX_3], // up to 5 txs
}));

ws.onmessage = (e) => {
  const r = JSON.parse(e.data);
  // { "id": "b1", "status": "accepted", "bundle_id": "...", "signatures": ["..."] }
};

Bundle convention: any single transaction in the bundle paying the standard 0.001 SOL tip covers the entire group. Bundles are forwarded only through bundle-aware paths so atomicity is preserved end-to-end — they either land together or not at all.

That makes the bundle stream the right tool for things like:

  • Atomic backruns that must execute immediately after a target swap
  • Multi-leg arbitrage where partial execution would leave you exposed
  • Token launches that need a creator instruction and a snipe to land in lockstep
  • NFT mints with a payment + claim that must succeed or fail together

Many In-Flight Requests, One Connection

Each WebSocket message you send is processed in its own task on the relay. You don't have to wait for the response to one transaction before sending the next — fire as many as your rate limit allows, and the responses come back tagged with the id field you supplied so you can correlate them on your end.

for (const tx of allMyTxs) {
  ws.send(JSON.stringify({ id: tx.localId, tx: tx.b64 }));
}
// Responses arrive out of order, identified by id.

This matters for high-frequency strategies. A naive request/response loop limits you to 1 / round-trip-time transactions per second. Pipelined sends over a WebSocket can push that into the thousands.

Rate-limit tokens are still consumed per message — but a rate-limit rejection just rejects that one frame. Your connection stays open and the next message goes through normally.

What You Should Expect for Latency

From a co-located client we measure 3–6ms from ws.send() to the response frame for a single transaction — well inside a Solana slot window. Add whatever the network leg is from wherever you're actually running. The handshake tax is paid once at connect and never again.

QUIC on {ip}:4433 is still slightly faster at the margin (0-RTT reconnects, stream-level multiplexing). But for almost every real-world client — anything that has to run in a browser, anything written in a language without a polished QUIC client — WebSockets are now the default.

Streams are kept alive aggressively, so a connection you opened at boot will stay hot through quiet periods. There's no idle-timeout you need to design around.

Pick the Region Closest to You

RegionHostname
Frankfurtfra.relay.allenhark.com (or relay.allenhark.com)
Amsterdamams.relay.allenhark.com
New Yorkny.relay.allenhark.com
Tokyotyo.relay.allenhark.com

All four regions speak both wss:// and the bundle stream. Pick the one closest to your client and you'll get the lowest one-way latency.

What We Deliberately Didn't Do

  • No new auth model. WebSocket auth is the same x-api-key header (or ?api-key= query) you already use for HTTPS. Existing keys work without changes.
  • No new tip rules. 0.001 SOL minimum to a registered tip wallet, same as HTTP and QUIC. Bundles inherit the same threshold — any single tx in the bundle paying it is enough.
  • No incompatible behavior. A transaction submitted over WebSocket goes through exactly the same validation, tip checks, and broadcast pipeline as one submitted over HTTPS or QUIC. Only the transport changes.

Try It

The endpoints are live across all four regions right now. Open a connection, send a transaction, watch it land:

wscat -c "wss://relay.allenhark.com/v1/stream/tx?api-key=YOUR_KEY"
> {"id":"1","tx":"<your base64 transaction>"}
< {"id":"1","status":"accepted","request_id":"...","signature":"..."}

Sign up for an API key (optional — public submissions still work without one) and start streaming. If you're moving from the HTTPS endpoint, replace one URL and one verb. That's the whole migration.

If you need bundles, atomic execution, or just don't want to pay the TLS handshake tax on every transaction anymore, the WebSocket stream is the answer.