Server-Sent Events (SSE) Explained for Crypto Apps (With Real Examples)

Mateusz Sroka

08 Jan 2026 (14 days ago)

22 min read

Share:

Understand Server-Sent Events for crypto price streaming. Real DexPaprika examples, production patterns, browser EventSource API, and SSE vs WebSocket comparison with working code.

Server-Sent Events (SSE) Explained for Crypto Apps (With Real Examples)

Server-Sent Events (SSE) Explained for Crypto Apps (With Real Examples)

Server-Sent Events get mentioned as the "simpler alternative to WebSockets" - but what does that mean for crypto price streaming? When DexPaprika's SSE endpoint pushes 1-second price updates, what's actually happening behind the scenes? This article breaks down SSE mechanics, shows real DexPaprika API examples, and provides production guidance for when streaming makes sense over polling.


What SSE actually is

Server-Sent Events (SSE) is HTTP-based streaming where the server keeps a connection open and pushes events to the client as plain text. The core mechanism is straightforward: an HTTP connection that never closes, with the server writing events as they occur. No custom protocol or handshake negotiation required - though as we'll see in production considerations, "straightforward" doesn't mean "trivial to operate at scale."

The event format is structured text with specific delimiters:

data: {"a":"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2","c":"ethereum","p":"3997.55","t":1761733397}
event: t_p

Each event has:

  • data: prefix for the message content (JSON in this case)
  • event: prefix for the event type (optional)
  • Empty line (\n\n) marking the end of the event

Browsers provide a native EventSource API that handles connection management and parsing automatically - no library dependencies needed. The minimal working code is three lines of JavaScript, though production deployments typically need additional reconnection logic.

How SSE differs from alternatives:

SSE vs WebSocket: Unidirectional (server → client only) vs bidirectional communication. WebSocket lets clients send frequent messages back; SSE doesn't. For price feeds where you're receiving updates without sending data back, SSE's one-directional design reduces complexity. The tradeoff: if you later need bidirectional communication, migration to WebSocket requires protocol changes.

SSE vs long polling: Persistent connection vs repeated requests. Long polling makes a request, waits for data, closes connection, immediately opens new request. SSE opens once, receives many updates. This reduces overhead and latency, at the cost of managing persistent connections (which introduces challenges like proxy timeouts and connection limits - covered in common pitfalls).

SSE vs streaming REST: Structured events with automatic reconnection vs raw streaming response. A streaming REST response sends data continuously with no event boundaries. SSE adds event structure (the data: format) and browser-managed reconnection when connections drop. The structure makes parsing easier but adds slight overhead compared to raw streaming.

Key characteristics:

  • Content-Type: text/event-stream (tells browser this is SSE)
  • Connection: keep-alive (stays open indefinitely)
  • Automatic reconnection: Browser reconnects with 3-second default delay
  • HTTP-based: Works with standard HTTP infrastructure (load balancers, proxies)

DexPaprika's SSE endpoint follows this exact format. When you connect to https://streaming.dexpaprika.com/stream?method=t_p&chain=ethereum&address=0x..., you get a persistent connection sending price events approximately every second.

Why use SSE for crypto apps?

SSE solves two specific problems for crypto applications: bandwidth efficiency and latency reduction.

Bandwidth efficiency: Send only updates

Polling means requesting the full state repeatedly. For price tracking:

  • Polling at 1-second intervals: 1 request/second = 86,400 requests per day per user
  • SSE: 1 connection, then only price updates when they change

At scale, this matters. With 1,000 users polling every second, you're handling 86.4 million requests per day. With SSE, you maintain 1,000 persistent connections and send updates only when prices change. For 1-second update intervals across 1,000 users, we've measured ~90% bandwidth reduction compared to polling the same frequency.

Latency reduction: Sub-second updates

Polling latency is tied to your interval. With 5-second polling, average latency is 2.5 seconds (half the interval). SSE latency is the time to serialize and send the event - typically 50-200ms depending on connection quality and geographic proximity to the server.

DexPaprika's SSE endpoint updates approximately every second. That's consistent sub-second latency vs polling's 1-5 second typical range. The tradeoff: persistent connections require more infrastructure planning than stateless polling (connection pooling, timeout configuration, reconnection handling).

Simpler than WebSocket for one-way data

WebSocket requires handshake negotiation, custom framing protocol, and typically a library to handle message encoding. SSE is HTTP with text events - your load balancer already speaks HTTP, your monitoring tools already log HTTP connections, no protocol translation needed.

The tradeoff: WebSocket's complexity buys you bidirectional communication and binary framing. If you need those features, SSE's simplicity becomes a limitation. For one-way server push, SSE's HTTP foundation means fewer moving parts.

Built-in reconnection

The browser's EventSource API reconnects automatically when connections drop. Default retry is 3 seconds, customizable by the server via retry: field. The browser handles reconnection - though production deployments should enhance this with exponential backoff to prevent thundering herd issues (more on that in production considerations). The browser's automatic reconnection is a starting point, not a complete solution.

Crypto-specific scenarios where SSE excels:

Portfolio trackers (10-50 assets): You need fresh prices without hammering the API. Opening 50 polling loops = 50 requests/second. One SSE connection with DexPaprika's POST endpoint = 1 connection, all 50 assets in the same stream.

Trading dashboards: Real-time order book updates, price alerts for stop-loss triggers. Polling every second creates noticeable lag and high bandwidth costs. SSE delivers updates as they happen with minimal overhead.

DeFi risk monitoring: Liquidation prices and health factors are time-sensitive. A 3-second polling lag could mean missed liquidation warnings. SSE's sub-second updates match the urgency of DeFi risk management.

What SSE doesn't solve: Bidirectional communication. If your application needs the client to send frequent messages (trading execution, collaborative editing), you need WebSocket. SSE is server → client only. This isn't a limitation you can work around - it's fundamental to SSE's design.

AspectPollingSSEWebSocket
DirectionClient → ServerServer → ClientBidirectional
Bandwidth (1000 users, 1s updates)~50 MB/min~5 MB/min~5 MB/min
Latency1-60s (avg: interval/2)50-200ms50-200ms
ComplexityLow (stateless requests)Moderate (connection management)High (custom protocol)
InfrastructureStandard HTTPStandard HTTPWebSocket-capable LB
Use CaseInfrequent updatesLive server push (one-way)Chat, trading execution

How SSE works

Understanding SSE mechanics helps you debug connection issues and optimize for production. Three key parts: connection establishment, event format, and automatic reconnection.

Connection establishment

The client opens an HTTP GET request with Accept: text/event-stream header. The server responds with 200 OK and Content-Type: text/event-stream. The connection stays open - no Content-Length header, no response completion. The server writes events to the stream as they occur.

From the browser's perspective:

const eventSource = new EventSource(
  'https://streaming.dexpaprika.com/stream?method=t_p&chain=ethereum&address=0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'
);

Connection opens, stays open, events arrive. The simplicity of this API hides complexity in connection management - the browser handles buffering, parsing, and reconnection internally.

Event format and parsing

Events are plain text with specific structure. Each event consists of:

data: {"a":"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2","c":"ethereum","p":"3997.55","t":1761733397}
event: t_p

  • data: The message content (JSON in this case). Can span multiple lines if needed.
  • event: The event type (here t_p = token price). Client filters by this.
  • Empty line (\n\n): Marks event boundary. Browser knows event is complete.

Optional fields:

  • id: Event identifier. Browser sends Last-Event-ID header on reconnect for resume.
  • retry: Reconnection delay in milliseconds (server suggestion to browser).

DexPaprika's event format is straightforward: JSON object with address (a), chain (c), price (p), and timestamp (t). The event type t_p identifies this as a token price update.

Why this format? Plain text enables debugging without specialized tools. You can curl an SSE endpoint and watch events arrive in your terminal - no binary protocol inspection needed. The tradeoff: text encoding is slightly larger than binary formats. For most applications, the debugging advantage outweighs the bandwidth difference.

Automatic reconnection

When connections drop (network blip, server restart, proxy timeout), the browser's EventSource automatically reconnects after a delay (3 seconds default). The browser sends a Last-Event-ID header if the server provided event IDs, allowing the server to resume from where it left off.

DexPaprika's approach: Stateless reconnection. No event IDs (the stream doesn't maintain position). When you reconnect, you get the current price immediately. For price streaming, this works well - you don't need historical replay, you need the latest price. The tradeoff: during the reconnection gap (3-5 seconds), price events are missed. If you need guaranteed delivery of every event, SSE requires additional server-side buffering and state management.

Connection timeout: Most servers drop idle connections after 30-60 seconds of inactivity. If no events occur, the connection eventually times out and reconnects. Some implementations send "heartbeat" comments (lines starting with :) every 30-45 seconds to keep connections alive.

Browser behavior after 5+ minutes idle: Browsers may silently discard the connection without notifying your code. Neither side knows it's disconnected until an event is sent. Heartbeats prevent this - at the cost of maintaining network activity even when no data is changing.

Basic browser SSE connection

Here's the minimal code to connect to DexPaprika's SSE endpoint:

// Minimal SSE connection - browser handles connection management
const eventSource = new EventSource(
  'https://streaming.dexpaprika.com/stream?method=t_p&chain=ethereum&address=0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'
);

eventSource.addEventListener('t_p', (event) => {
  const price = JSON.parse(event.data);
  console.log(`${price.c}: $${price.p}`);
  // ethereum: $3997.55
});

eventSource.onerror = (error) => {
  console.error('Connection error:', error);
  // Browser automatically reconnects - onerror is for logging, not manual reconnection
};

// Close when done (important: prevents connection leaks)
// window.addEventListener('beforeunload', () => eventSource.close());

Key points:

  • Native API: No library needed. EventSource is built into all modern browsers.
  • Event listener by type:addEventListener('t_p', ...) filters for token price events.
  • Automatic error handling: Browser reconnects on errors. onerror is for logging and observability, not manual reconnection logic.
  • Always close: Call eventSource.close() when component unmounts or user navigates away. Unclosed connections count toward browser's 6-connection limit per domain (more on this in common pitfalls).

Real DexPaprika SSE examples

DexPaprika provides two SSE endpoints: GET for single assets, POST for multiple assets in one stream.

Single asset stream (GET /stream)

Endpoint:https://streaming.dexpaprika.com/stream

Parameters:

  • method=t_p (token price - required)
  • chain=ethereum (blockchain: ethereum, solana, bsc, arbitrum, etc.)
  • address=0x... (token contract address)
  • limit=N (optional - closes stream after N events, useful for testing)

Response format:

{"a":"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2","c":"ethereum","p":"3997.5514026436525223","t":1761733397}
  • a: Token address
  • c: Chain identifier
  • p: Price in USD (numeric string for precision)
  • t: Unix timestamp (seconds)

Update frequency: Approximately 1 second per connection.

Rate limits: No authentication required. Capacity-based: if the service is overloaded, you get HTTP 429 (Too Many Requests). Implement exponential backoff for 429 responses (covered in production considerations).

Error codes:

  • 400: Invalid parameters (unsupported chain, token not found on that chain)
  • 429: Capacity exceeded (back off exponentially)

Multi-asset stream (POST /stream)

For tracking multiple tokens, POST endpoint allows up to 2,000 assets in a single connection.

Endpoint: Same base URL, POST method

Request headers:

  • Accept: text/event-stream
  • Content-Type: application/json

Request body (JSON array):

[
  {
    "chain": "ethereum",
    "address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
    "method": "t_p"
  },
  {
    "chain": "solana",
    "address": "So11111111111111111111111111111111111111112",
    "method": "t_p"
  }
]

Max assets: 2,000 per request (technical limit)
Recommended batch size: 100-500 assets per connection for better load distribution

Important: All assets must be valid. One invalid asset = entire stream fails with 400. Validate asset addresses before sending batch. This all-or-nothing behavior means you should validate addresses client-side before opening the stream, or handle 400 errors gracefully with fallback logic.

Response format: Same as GET endpoint. Events arrive for all assets in the same stream, mixed together. Filter by c (chain) and a (address) fields to route to the correct handler.

Node.js implementation

Node.js doesn't have a native EventSource API (it's browser-only). You need to parse the SSE format manually using fetch API with streaming response body.

// Simplified SSE client for Node.js (production needs error handling + reconnect)
async function streamPrices(chain, address) {
  const response = await fetch(
    `https://streaming.dexpaprika.com/stream?method=t_p&chain=${chain}&address=${address}`
  );

  const reader = response.body.getReader();
  const decoder = new TextDecoder();
  let buffer = '';

  while (true) {
    const {value, done} = await reader.read();
    if (done) break;

    buffer += decoder.decode(value, {stream: true});
    const lines = buffer.split('\n');

    // Keep incomplete line in buffer
    buffer = lines.pop();

    for (const line of lines) {
      if (line.startsWith('data: ')) {
        const data = line.slice(6); // Remove 'data: ' prefix
        const price = JSON.parse(data);
        console.log(`${price.c}: $${price.p}`);
      }
    }
  }
}

// Usage
streamPrices('ethereum', '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2');

Key differences from browser:

  • Manual parsing: You handle the data: prefix and \n\n delimiters yourself.
  • Buffer management: Chunks may arrive mid-line. Keep incomplete lines in buffer until next chunk.
  • No automatic reconnection: You implement exponential backoff (see production considerations).

What's simplified here: Production code needs error handling (network errors, JSON parsing errors), reconnection logic with exponential backoff, connection timeout detection, and graceful shutdown. This example shows the core SSE parsing logic without the production scaffolding - expect to add 50-100 lines of infrastructure code for production readiness.

What are common SSE pitfalls?

Real production issues you'll encounter with SSE deployments.

Browser compatibility

Supported: All modern browsers (Chrome, Firefox, Safari, Edge) and mobile (iOS Safari, Chrome Android). EventSource has been standard since ~2012.

NOT supported: Internet Explorer (but IE is dead - if you still support it, polling is your only option).

Corporate proxies: Some enterprise proxies block long-lived connections (they expect request-response patterns). If your users are behind restrictive corporate networks, provide polling fallback. This is rare but real - we've seen it at large financial institutions where security policies terminate connections after 30 seconds regardless of HTTP headers.

Fallback strategy: Detect EventSource support:

if (typeof EventSource !== 'undefined') {
  // Use SSE
} else {
  // Fall back to polling
}

Browser connection limits (HTTP/1.1)

HTTP/1.1 limits browsers to 6 concurrent connections per domain. SSE connections never complete - they occupy a slot permanently. Open 6 SSE connections, and NO other HTTP requests to that domain succeed (images, API calls, assets all blocked).

Example: User opens 3 tabs with 2 SSE connections each. All 6 slots occupied. User tries to load an image on tab 3. Browser shows "Waiting for available socket..." indefinitely.

This is marked "Won't Fix" by Chrome and Firefox. Their position: use HTTP/2 instead. This is a rare case where browser vendors are explicitly pushing users toward infrastructure upgrades rather than working around protocol limitations.

Solution 1 (Primary): Upgrade to HTTP/2

HTTP/2 multiplexing allows 100+ streams per connection (default negotiated limit). Eliminates the 6-connection problem entirely.

# Nginx HTTP/2 configuration
listen 443 ssl http2;

Solution 2 (Fallback): Share connections across tabs

Use SharedWorker or BroadcastChannel to share one SSE connection across all tabs:

// Tab 1 creates SSE connection
const eventSource = new EventSource('/stream');
const bc = new BroadcastChannel('price-updates');

eventSource.onmessage = (event) => {
  bc.postMessage(event.data); // Broadcast to other tabs
};

// Other tabs listen (no additional SSE connections)
const bc = new BroadcastChannel('price-updates');
bc.onmessage = (event) => {
  console.log('Price update:', event.data);
};

Solution 3 (Workaround): Use subdomains

api.example.com and www.example.com have separate 6-connection pools. If you control the domain, serve SSE from a subdomain.

Edge case: "Break-and-inspect" proxies in corporate environments may downgrade HTTP/2 → HTTP/1.1, reintroducing the limit. Test with real corporate network environments if that's your audience - we've seen this at banks and government agencies where SSL inspection proxies interfere with HTTP/2 negotiation.

Reconnection gaps and missed events

Browser reconnects automatically with 3-second default delay. During those 3 seconds, events are missed. SSE doesn't buffer—it's a live stream, not a message queue.

Example: Connection drops. 3-second reconnect delay. 3 price events occur during that window. Client reconnects and gets the 4th event, missing events 1-3.

DexPaprika's approach: Stateless streams. No event IDs. When you reconnect, you receive the current price immediately. For price streaming, you don't need the missed events - the latest state is what matters. The tradeoff: if your application requires guaranteed delivery of every event (audit trails, transaction logs), SSE's stateless approach isn't sufficient. You'd need event IDs, server-side buffering, and replay logic.

If you need event IDs: Some SSE servers provide id: field. Browser sends Last-Event-ID header on reconnect. Server resumes from that point. This requires server-side buffering and state management—complexity tradeoff. You're essentially building a message queue on top of SSE.

Typical reconnection gap: 3-5 seconds (3-second delay + connection establishment time).

Monitor reconnection frequency in production. If you're seeing reconnections every 30-60 seconds, check for nginx proxy timeouts (next issue).

Production considerations

Moving from development to production SSE deployments, three areas need attention: error handling, monitoring, and resource management.

Error handling with exponential backoff

Browser's automatic reconnection uses fixed 3-second delay. In production, this creates "thundering herd" problems: when your server restarts, ALL clients reconnect simultaneously, overwhelming the server with thousands of requests within seconds.

Real failure example: Production deployment with 10,000 SSE clients. Server restart causes mass disconnection. All 10,000 clients use default 3-second retry. Server receives 10,000 reconnection attempts in < 10 second window. CPU spikes from 30% to 100%, cascading connection failures, 15-minute outage until load subsides. We've seen this pattern at multiple organizations - it's predictable and preventable.

Solution: Exponential backoff with jitter

// Production-ready error handling (simplified for clarity)
class ReconnectingSSE {
  constructor(url) {
    this.url = url;
    this.retryDelay = 1000; // Start at 1 second
    this.maxDelay = 60000; // Cap at 60 seconds
    this.connect();
  }

  connect() {
    this.eventSource = new EventSource(this.url);

    this.eventSource.addEventListener('t_p', (event) => {
      const price = JSON.parse(event.data);
      this.handlePrice(price);
    });

    this.eventSource.onerror = (error) => {
      this.eventSource.close();

      // Add random jitter (0-5 seconds) to prevent synchronized retries
      const jitter = Math.random() * 5000;
      const delay = Math.min(this.retryDelay + jitter, this.maxDelay);

      console.log(`Reconnecting in ${delay}ms`);
      setTimeout(() => this.connect(), delay);

      // Exponential backoff: double delay each time, capped at maxDelay
      this.retryDelay = Math.min(this.retryDelay * 2, this.maxDelay);
    };

    this.eventSource.onopen = () => {
      // Reset delay on successful connection
      this.retryDelay = 1000;
    };
  }

  handlePrice(price) {
    // Your price handling logic
    console.log(`${price.c}: $${price.p}`);
  }
}

// Usage
const stream = new ReconnectingSSE(
  'https://streaming.dexpaprika.com/stream?method=t_p&chain=ethereum&address=0x...'
);

Key elements:

  • Exponential backoff: 1s → 2s → 4s → 8s → 16s → 30s → 60s (capped)
  • Jitter: Random 0-5 second delay prevents synchronized retries
  • Cap maximum delay: Don't wait forever. 60 seconds is reasonable.
  • Reset on success: Back to 1 second when connection succeeds.

Why this works: Spreads reconnection attempts over time. First retry: 1-6 seconds (1s + 0-5s jitter). Second retry: 2-7 seconds. By the 5th retry, you're waiting 16-21 seconds. Prevents thundering herd while still reconnecting reasonably fast.

What's simplified: Production code should also implement:

  • Circuit breaker: Stop retrying after N consecutive failures (prevent infinite loops burning client resources)
  • Error classification: Distinguish network errors (retry) from 400 Bad Request (don't retry - fix the request)
  • Retry budget: Track total retry time, stop if exceeding threshold (detect persistent issues vs transient failures)

Special handling for 429 (Rate Limit Exceeded): DexPaprika returns 429 when capacity is exceeded. For 429 specifically, use longer backoff (start at 5 seconds, cap at 120 seconds). You're being rate limited - aggressive retries make the problem worse.

Monitoring

Track these metrics in production SSE deployments:

Connection state tracking:

  • Monitor: open, connecting, error, closed states
  • Alert: If connection stays in error state > 5 minutes (persistent issue, not transient network blip)
  • Metric: sse_connection_state (gauge)

Time since last event (stale connection detection):

  • Monitor: Timestamp of last received event
  • Alert: If > 2 minutes without events (connection may be dead but not closed - silent failure)
  • Metric: sse_last_event_seconds_ago (gauge)
  • Action: Force close and reconnect if exceeding threshold

Reconnection rate:

  • Monitor: Count of reconnection attempts per hour
  • Alert: If > 10 per hour (network instability or nginx timeout misconfiguration)
  • Metric: sse_reconnects_total (counter)

Events received rate:

  • Monitor: Events per second
  • Alert: If drops below expected rate (DexPaprika: ~1/second)
  • Metric: sse_events_received_total (counter)

Connection uptime percentage:

  • Monitor: (time connected) / (total time) × 100
  • Target: > 95% uptime in production
  • Alert: If < 90% (indicates persistent connection issues requiring investigation)

Typical production metrics (from deployments we've monitored):

  • Connection uptime: 95-99% (good)
  • Reconnection frequency: 1-5 per hour (normal network variability)
  • Time since last event: < 5 seconds (healthy stream)

Resource management

Close connections when not needed:

// React example
useEffect(() => {
  const eventSource = new EventSource(url);

  return () => {
    eventSource.close(); // CRITICAL: Prevents connection leaks
  };
}, [url]);

Unclosed connections:

  1. Count toward browser's 6-connection limit (blocks other requests)
  2. Waste server resources (idle connections held open)
  3. Cause memory leaks in long-running SPAs (memory grows with each unmounted component that didn't close)

Use POST /stream for multiple assets:

Don't: Open 50 individual SSE connections for 50 tokens
Do: Open 1 POST /stream connection with 50 tokens in the request body

Benefits:

  • Reduces browser connection usage (1 vs 50)
  • Lower server resource usage (1 connection vs 50)
  • Simpler client-side management (1 reconnection handler vs 50)

Server-side proxying for > 100 assets:

If tracking 500+ assets, consider server-side aggregation:

Your Frontend (1 SSE connection)
  ↓
Your Backend Proxy (maintains N connections to DexPaprika)
  ↓
DexPaprika (splits load across multiple connections)

Benefits:

  • Client doesn't manage 100+ connections
  • Backend can implement sophisticated retry logic
  • Easier to handle rate limits centrally
  • Backend can cache/deduplicate if multiple clients request same assets

Connection overhead: Each SSE connection consumes ~2-5KB baseline memory (idle connection). At 10,000 connections, that's 20-50MB before event data. Plan capacity accordingly - we've seen servers with 16GB RAM comfortably handle 50,000 concurrent SSE connections, but your mileage will vary based on event frequency and payload size.

Recommended limits (based on production experience):

  • Client-side: 5-10 SSE connections max per user
  • Server-side: 10,000-50,000 connections per instance (depends on server specs)
  • DexPaprika POST endpoint: 100-500 assets per connection (their recommendation, not technical limit)

When to use SSE (vs polling, vs WebSocket)

You've seen how SSE works and what can go wrong. Now: when should you actually use it?

Use SSE when:

One-way data flow: Server pushes to client, client doesn't send frequent messages back. SSE is designed for this. Price feeds, transaction notifications, order book views (read-only) - these are SSE's sweet spot. If your data flow is predominantly server → client, SSE's simplicity is an advantage.

Frequent updates (> 1 per 10-30 seconds): If data changes multiple times per minute, SSE's persistent connection beats polling's repeated overhead. DexPaprika's ~1 second update frequency is ideal for SSE. The crossover point depends on infrastructure costs, but our testing shows SSE becomes cost-effective at >1 update per 30 seconds.

HTTP infrastructure: Your load balancers, proxies, and monitoring tools already speak HTTP. SSE works with existing infrastructure. No WebSocket gateway, no protocol translation, no special firewall rules. If you're running standard HTTP infrastructure (Nginx, HAProxy, CloudFlare), SSE integrates without additional components.

Browser clients: Native EventSource API means no library dependencies, no bundle size increase, automatic reconnection handled by browser. The API is simple enough that developers rarely make implementation mistakes—the browser handles most complexity.

Use polling instead when:

Infrequent updates (< 1 per minute): If data changes every 5+ minutes, polling's simplicity wins. Example: Dashboard showing daily trading volume - polling every 60 seconds is simpler than maintaining an SSE connection. The persistent connection overhead isn't justified for infrequent updates.

Short-lived sessions: User visits briefly, checks data once, leaves. Opening SSE connection for 10-second visit wastes resources. Polling is stateless - simpler for short interactions. If your median session duration is < 30 seconds, polling often makes more sense.

Simple deployment: Static hosting (Netlify, Vercel) without backend server-sent event support. Polling works anywhere HTTP works. If you're building a static site or serverless architecture without persistent connection support, polling is your constraint.

For detailed polling vs streaming decision framework, see Article #2 in this series.

Use WebSocket instead when:

Bidirectional communication: Client sends frequent messages to server. Trading execution (place order, cancel order, modify order) needs bidirectional flow. SSE can't do this - it's server → client only. This is a fundamental design constraint, not a limitation you can work around.

Sub-100ms latency critical: WebSocket's binary framing is slightly faster than SSE's text format. For latency-critical use cases (high-frequency trading, real-time gaming), WebSocket's overhead optimization matters. The difference is 10-50ms typically, which only matters for specific applications.

Custom protocol needed: WebSocket lets you design custom binary protocols. If you're sending structured binary data (protobuf, msgpack), WebSocket is the right choice. SSE's text-based format means you're encoding binary as text (base64) which adds overhead.

Article #4 in this series ("SSE vs WebSockets") will provide detailed comparison. For now: if you're receiving data only (no frequent client messages), SSE is simpler.

Decision: Choose data transport for crypto prices

Update frequency > 1/minute?
├─ No → Use polling (simple, stateless)
│         Examples: Daily volume, hourly OHLC, weekly reports
│
└─ Yes → Need bidirectional communication?
          (client sends frequent messages)
    ├─ Yes → Use WebSocket (supports client → server messaging)
    │         Examples: Trading execution (place/cancel/modify orders),
    │                   collaborative editing, real-time chat
    │
    └─ No → Sub-100ms latency critical?
        ├─ Yes → Consider WebSocket (binary framing, slightly lower overhead)
        │         Examples: High-frequency trading, real-time gaming,
        │                   sub-second arbitrage detection
        │
        └─ No → Use SSE ✓ (HTTP-based, simpler infrastructure)
                  Examples: Price feeds, transaction notifications,
                            order book views (read-only), portfolio tracking

Gray areas (where both SSE and polling work):

100-1,000 users, 5-30 second updates: Either approach viable. Choose SSE if growth expected (migration from polling to SSE is painful - better to start with SSE if you anticipate scale). Choose polling if team lacks SSE experience (learning curve isn't worth it yet). We've seen teams successfully use both approaches in this range - the decision often comes down to team expertise and infrastructure familiarity.

Mobile apps with background tabs: SSE connections get killed by iOS/Android after 30-60 seconds when app backgrounds. Polling with longer intervals (30-60 seconds) may be simpler for mobile. Or use platform push notifications (APNS, FCM) for critical alerts instead of relying on SSE in background. Mobile platforms are hostile to persistent connections - design accordingly.

Frequently asked questions

Is DexPaprika streaming free?

A: Yes, completely free. No API key or authentication required. Just connect to the streaming endpoint and start receiving price updates.

How many tokens can I track simultaneously?

A: Up to 2,000 tokens per connection using the POST /stream endpoint. For optimal performance, DexPaprika recommends batching 100-500 assets per connection.

What's the latency for SSE updates?

A: Approximately 1 second per update for DexPaprika. General SSE latency is 50-200ms depending on connection quality and geographic proximity to the server. This is significantly faster than polling's 1-5 second typical latency.

Which blockchains does DexPaprika support?

A: 28+ blockchains including Ethereum, Solana, Base, Arbitrum, Polygon, Optimism, Avalanche, BNB Chain, and more. Check DexPaprika's official documentation for the complete list.

How is SSE different from WebSocket?

A:SSE: Unidirectional (server → client), HTTP-based, text format, native browser API, simpler setup. WebSocket: Bidirectional, custom protocol, binary/text format, requires library, more complex. Use SSE for one-way server push (price feeds, notifications). Use WebSocket for bidirectional communication (trading execution, chat).

What happens when my SSE connection drops?

A: The browser automatically reconnects with a 3-second default delay. During the reconnection gap (3-5 seconds), events are missed. DexPaprika uses stateless reconnection—when you reconnect, you receive the current price immediately. Production deployments should add exponential backoff with jitter to prevent thundering herd issues.

Can I use SSE on mobile apps?

A: Yes, but with limitations. iOS and Android kill background SSE connections after 30-60 seconds when the app backgrounds. For critical alerts, use platform push notifications (APNS, FCM) instead. SSE works well for foreground mobile app usage.

How do I handle HTTP/1.1's 6-connection limit?

A: Three solutions: 1. Upgrade to HTTP/2 (recommended) - eliminates the 6-connection limit entirely. 2. Share connections across tabs using SharedWorker or BroadcastChannel. 3. Use subdomains - each subdomain has a separate 6-connection pool.

What rate limits does DexPaprika have?

A: No authentication required, but capacity-based rate limiting applies. If the service is overloaded, you receive HTTP 429 (Too Many Requests). Implement exponential backoff starting at 5 seconds, capping at 120 seconds for 429 responses.

Summary

Server-Sent Events is HTTP-based streaming: persistent connection, server pushes events as plain text. No custom protocol, no handshake - HTTP with Content-Type: text/event-stream.

Key takeaways

  • Native browser support: EventSource API built into all modern browsers (minimal code to start, though production needs additional reconnection logic)
  • Ideal for one-way data flow: Server → client price updates, notifications, order book views
  • DexPaprika example: GET /stream for single asset (~1 second updates), POST /stream for up to 2,000 assets in one connection
  • Automatic reconnection: Browser reconnects with 3-second default (enhance with exponential backoff in production to prevent thundering herd)
  • Common issues: Browser 6-connection limit (use HTTP/2), nginx 60-second proxy timeout (increase to 86400s), reconnection storms (add jitter)
  • Use SSE when: Updates > 1/minute, no bidirectional communication needed, HTTP infrastructure preferred
  • Production patterns: Exponential backoff with jitter, connection state monitoring, close when done

Related articles

Latest articles

Coinpaprika education

Discover practical guides, definitions, and deep dives to grow your crypto knowledge.

Go back to Education