Advanced
Keep-Alive: Cutting Connection Overhead
Every new HTTPS connection costs a TCP handshake plus a TLS handshake — often 40-150ms before your first byte is even sent. For high-frequency workloads, reusing a single persistent connection eliminates this overhead entirely.
Why it matters
TCP handshake
Three round trips before any data flows. On a transatlantic path, that's 200ms+ of pure setup.
TLS handshake
Another round trip for TLS 1.3 (two for TLS 1.2), plus certificate validation — typically 30-80ms.
TCP slow start
A fresh connection ramps up congestion window slowly. A persistent connection delivers your payload at full speed immediately.
On a single transaction this matters less. On the hundredth transaction per minute, a persistent connection vs. a fresh one is the difference between landing on the current slot or the next one.
How BoltTx supports Keep-Alive
Every BoltTx endpoint supports HTTP/1.1 Keep-Alive and HTTP/2 multiplexing. Connections stay open for up to 60 seconds of idle time before the server closes them. To keep the connection warm across longer gaps, send a cheap probe to the health endpoint every 30-45 seconds:
Health probe endpoint
/v1/healthcurl https://bolttx.io/v1/health
# -> 200 OK "ok"The health endpoint is unauthenticated, unrated, and free — it does not count toward your TPS quota or tip accounting. Use it freely.
Per-language setup
In every runtime, the goal is the same: create the HTTP client once, reuse it for every transaction. Never build a new client per request.
Node.js / Bun — native fetch
Modern fetch implementations reuse connections automatically via a global agent. Just use the same module-level reference for every call.
// bolttx-client.ts — created once at module load
const API_KEY = process.env.BOLTTX_API_KEY!;
const ENDPOINT = "https://bolttx.io/v1/send";
export async function sendTx(signedBase64: string) {
// fetch reuses the global undici Agent — no per-call setup cost
const res = await fetch(ENDPOINT, {
method: "POST",
headers: {
"Authorization": `Bearer ${API_KEY}`,
"Content-Type": "application/json",
"Connection": "keep-alive",
},
body: JSON.stringify({ transaction: signedBase64 }),
});
return res.json();
}
// Optional: warm probe every 30s for bursty workloads
setInterval(() => fetch("https://bolttx.io/v1/health"), 30_000);Python — requests.Session
Create one Session object at module load and reuse it. requests.get/post without a session opens a new connection every call.
import requests, threading, time
# One session for the whole process
_session = requests.Session()
_session.headers.update({
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
})
def send_tx(signed_b64: str):
return _session.post(
"https://bolttx.io/v1/send",
json={"transaction": signed_b64},
timeout=5,
).json()
# Optional warm probe
def _keepalive():
while True:
_session.get("https://bolttx.io/v1/health", timeout=5)
time.sleep(30)
threading.Thread(target=_keepalive, daemon=True).start()Rust — reqwest::Client
Clone the Client — internally it's an Arc, so clones share the same connection pool. Never call Client::new() in a hot path.
use reqwest::Client;
use once_cell::sync::Lazy;
// Build once, clone cheaply — shared connection pool.
pub static HTTP: Lazy<Client> = Lazy::new(|| {
Client::builder()
.pool_idle_timeout(std::time::Duration::from_secs(60))
.pool_max_idle_per_host(32)
.http2_prior_knowledge()
.build()
.expect("reqwest client")
});
pub async fn send_tx(signed_b64: &str) -> reqwest::Result<serde_json::Value> {
HTTP.post("https://bolttx.io/v1/send")
.bearer_auth(&*API_KEY)
.json(&serde_json::json!({ "transaction": signed_b64 }))
.send()
.await?
.json()
.await
}Go — http.Client with Transport
Reuse a single http.Client (or the default http.DefaultClient). Go's default Transport pools connections automatically.
package bolttx
import (
"bytes"
"encoding/json"
"net/http"
"time"
)
// Package-level client — reuse for every call.
var client = &http.Client{
Timeout: 5 * time.Second,
Transport: &http.Transport{
MaxIdleConns: 64,
MaxIdleConnsPerHost: 32,
IdleConnTimeout: 60 * time.Second,
ForceAttemptHTTP2: true,
},
}
func SendTx(signedB64 string) (map[string]any, error) {
body, _ := json.Marshal(map[string]string{"transaction": signedB64})
req, _ := http.NewRequest("POST", "https://bolttx.io/v1/send", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+apiKey)
req.Header.Set("Content-Type", "application/json")
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var out map[string]any
return out, json.NewDecoder(resp.Body).Decode(&out)
}Verifying it's working
Measure end-to-end latency of your first request versus the tenth request in quick succession. With Keep-Alive working correctly, the difference between them should be dominated by network RTT (single-digit milliseconds), not connection setup (tens to hundreds of milliseconds). If the tenth request is still slow, your client is opening fresh connections every time.
Common mistakes
- •Creating a new fetch/requests/reqwest client inside your send function — this defeats all pooling.
- •Using short-lived serverless functions (e.g., some edge runtimes) that cannot retain connections across invocations — consider a warm-worker architecture or accept the overhead.
- •Aggressive load balancers or proxies between you and BoltTx that terminate Keep-Alive — test the end-to-end path, not just your client's configuration.