DEV Community

Cover image for WebSocket vs Server-Sent Events: Which Is Better for Real-Time APIs?
Wanda
Wanda

Posted on • Originally published at apidog.com

WebSocket vs Server-Sent Events: Which Is Better for Real-Time APIs?

TL;DR

Use Server-Sent Events (SSE) for one-way server-to-client updates like notifications and live feeds. Use WebSocket for bidirectional communication like chat and gaming. SSE is simpler and works over HTTP. WebSocket is more complex but supports two-way messaging. Modern PetstoreAPI implements both for different real-time use cases.

Introduction

You need real-time updates in your API. For example, when a pet’s status changes from “available” to “adopted,” clients must be notified instantly. Should you use WebSocket or Server-Sent Events (SSE)?

Try Apidog today

WebSocket is often the default because it’s “more powerful,” but SSE can be a better fit: it’s simpler, works over standard HTTP, and automatically handles reconnection. WebSocket adds complexity that’s not always necessary.

Modern PetstoreAPI implements both: SSE for pet status updates and order notifications, WebSocket for live auction bidding and real-time chat. Each protocol supports different real-time scenarios.

💡 Tip: If you’re building or testing real-time APIs, Apidog supports both SSE and WebSocket testing. You can test event streams, validate message formats, and simulate reconnection scenarios.

In this guide, you’ll learn the differences between SSE and WebSocket, see practical examples from Modern PetstoreAPI, and know when to use each protocol.

What Is Server-Sent Events (SSE)?

SSE is an HTTP-based protocol for streaming events from server to client.

How SSE Works

The client opens a connection and receives events as they happen:

const eventSource = new EventSource('https://petstoreapi.com/v1/pets/notifications');

eventSource.onmessage = (event) => {
  const data = JSON.parse(event.data);
  console.log('Pet update:', data);
};

eventSource.addEventListener('adoption', (event) => {
  const data = JSON.parse(event.data);
  console.log('Pet adopted:', data.petId);
});
Enter fullscreen mode Exit fullscreen mode

Server sends events:

GET /v1/pets/notifications
Accept: text/event-stream

HTTP/1.1 200 OK
Content-Type: text/event-stream
Cache-Control: no-cache

event: adoption
data: {"petId":"019b4132","userId":"user-456"}

event: status-change
data: {"petId":"019b4127","status":"AVAILABLE"}
Enter fullscreen mode Exit fullscreen mode

SSE Features

1. One-way communication

Server pushes to client. Client can’t send messages back through the SSE connection (use HTTP requests for client-to-server).

2. Built on HTTP

Uses standard HTTP. Works through proxies, firewalls, and CDNs.

3. Automatic reconnection

If the connection drops, the browser reconnects automatically.

4. Event IDs for resume

Server can send event IDs so the client resumes from the last received event:

id: 123
event: adoption
data: {"petId":"019b4132"}

id: 124
event: status-change
data: {"petId":"019b4127"}
Enter fullscreen mode Exit fullscreen mode

If disconnected, the client sends Last-Event-ID: 124 header to resume.

5. Simple protocol

Text-based format. Easy to debug with curl:

curl -N -H "Accept: text/event-stream" \
  https://petstoreapi.com/v1/pets/notifications
Enter fullscreen mode Exit fullscreen mode

SSE Limitations

  • One-way only: Client can’t send messages via SSE (use HTTP requests).
  • Text-only: SSE sends text. Binary data must be base64-encoded.
  • Browser connection limits: Browsers limit SSE connections per domain (usually 6).
  • No built-in compression: HTTP compression works, but not protocol-level like WebSocket.

What Is WebSocket?

WebSocket is a full-duplex, bidirectional protocol over a persistent connection.

How WebSocket Works

Client and server can both send messages anytime:

const ws = new WebSocket('wss://petstoreapi.com/auctions/019b4132');

// Send message to server
ws.send(JSON.stringify({
  type: 'bid',
  amount: 500
}));

// Receive messages from server
ws.onmessage = (event) => {
  const data = JSON.parse(event.data);
  console.log('Auction update:', data);
};

ws.onclose = () => {
  console.log('Connection closed');
  // Manual reconnection logic needed
};
Enter fullscreen mode Exit fullscreen mode

Server can send anytime:

{"type":"bid","userId":"user-456","amount":550}
{"type":"outbid","newAmount":550}
Enter fullscreen mode Exit fullscreen mode

Client can send anytime:

{"type":"bid","amount":600}
{"type":"watch","petId":"019b4132"}
Enter fullscreen mode Exit fullscreen mode

WebSocket Features

  • Bidirectional: Both client and server can send messages at any time.
  • Low latency: Persistent connection, minimal overhead. Great for gaming, chat, live collaboration.
  • Binary support: Send binary data directly (no base64 encoding).
  • Custom protocol: Uses ws:// or wss:// after HTTP handshake.
  • Frame-based: Messages are framed; supports partial messages.

WebSocket Limitations

  • Complex setup: Requires a WebSocket server, more complex than HTTP endpoints.
  • Manual reconnection: No automatic reconnection—implement retry logic yourself.
  • Proxy issues: Some proxies block WebSocket. HTTP proxies don’t support ws://.
  • Stateful: Server must track connections—harder to scale than stateless HTTP.
  • No HTTP features: No HTTP caching, status codes, or standard headers after handshake.

Side-by-Side Comparison

Feature SSE WebSocket
Direction Server → Client Bidirectional
Protocol HTTP Custom (ws://)
Reconnection Automatic Manual
Browser Support All modern All modern
Proxy-friendly Yes Sometimes
Complexity Simple Complex
Binary Data No (text only) Yes
Latency Low Very low
Scalability High (stateless) Medium (stateful)
Use Case Notifications, feeds Chat, gaming, collaboration

How Modern PetstoreAPI Uses Both

Modern PetstoreAPI implements both SSE and WebSocket for different scenarios.

SSE for Pet Updates

Endpoint: GET /v1/pets/notifications

Client example:

const events = new EventSource(
  'https://petstoreapi.com/v1/pets/notifications?userId=user-456'
);

events.addEventListener('adoption', (e) => {
  const data = JSON.parse(e.data);
  showNotification(`${data.petName} was adopted!`);
});

events.addEventListener('status-change', (e) => {
  const data = JSON.parse(e.data);
  updatePetStatus(data.petId, data.status);
});
Enter fullscreen mode Exit fullscreen mode

Server example (Node.js/Express):

app.get('/v1/pets/notifications', (req, res) => {
  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');

  const userId = req.query.userId;

  // Subscribe to pet updates
  const subscription = petUpdates.subscribe(userId, (event) => {
    res.write(`event: ${event.type}\n`);
    res.write(`data: ${JSON.stringify(event.data)}\n\n`);
  });

  req.on('close', () => {
    subscription.unsubscribe();
  });
});
Enter fullscreen mode Exit fullscreen mode

Use cases:

  • Pet status changes (available → adopted)
  • Order notifications (placed, shipped, delivered)
  • Inventory updates
  • Price changes

WebSocket for Live Auctions

Endpoint: wss://petstoreapi.com/auctions/{auctionId}

Client example:

const ws = new WebSocket('wss://petstoreapi.com/auctions/019b4132');

// Place bid
function placeBid(amount) {
  ws.send(JSON.stringify({
    type: 'bid',
    amount
  }));
}

// Receive updates
ws.onmessage = (event) => {
  const msg = JSON.parse(event.data);

  switch (msg.type) {
    case 'bid':
      updateCurrentBid(msg.amount, msg.userId);
      break;
    case 'outbid':
      showOutbidNotification(msg.newAmount);
      break;
    case 'auction-end':
      showAuctionResult(msg.winner);
      break;
  }
};
Enter fullscreen mode Exit fullscreen mode

Server example (Node.js/ws):

wss.on('connection', (ws, req) => {
  const auctionId = req.params.auctionId;
  const auction = auctions.get(auctionId);

  ws.on('message', (data) => {
    const msg = JSON.parse(data);

    if (msg.type === 'bid') {
      const result = auction.placeBid(msg.userId, msg.amount);

      // Broadcast to all participants
      auction.participants.forEach(participant => {
        participant.send(JSON.stringify({
          type: 'bid',
          userId: msg.userId,
          amount: msg.amount
        }));
      });
    }
  });
});
Enter fullscreen mode Exit fullscreen mode

Use cases:

  • Live auction bidding
  • Real-time chat with support
  • Collaborative pet care planning
  • Live inventory updates during sales

Testing Real-Time APIs with Apidog

Apidog supports testing both SSE and WebSocket APIs.

Testing SSE

1. Create SSE request:

GET https://petstoreapi.com/v1/pets/notifications
Accept: text/event-stream
Enter fullscreen mode Exit fullscreen mode

2. Validate events:

  • Check event types
  • Validate JSON payloads
  • Test reconnection behavior
  • Verify event IDs

3. Test scenarios:

  • Connection drops
  • Server restarts
  • Event ordering
  • Resume from last event

Testing WebSocket

1. Create WebSocket connection:

wss://petstoreapi.com/auctions/019b4132
Enter fullscreen mode Exit fullscreen mode

2. Send test messages:

{"type":"bid","amount":500}
{"type":"watch","petId":"019b4132"}
Enter fullscreen mode Exit fullscreen mode

3. Validate responses:

  • Check message formats
  • Test bidirectional flow
  • Verify connection handling
  • Test error scenarios

4. Test scenarios:

  • Multiple concurrent connections
  • Message ordering
  • Connection timeouts
  • Reconnection logic

When to Use Each

Use SSE When:

  • One-way updates: Server pushes to client only.
  • Simple setup: Use standard HTTP infrastructure.
  • Automatic reconnection: Don’t want to write retry logic.
  • Proxy-friendly: Need to work through firewalls/proxies.
  • Notifications: Status updates, alerts, live feeds.

Examples:

  • Pet adoption notifications
  • Order status updates
  • Inventory changes
  • Price alerts
  • News feeds

Use WebSocket When:

  • Bidirectional: Both client and server send messages.
  • Low latency critical: Gaming, real-time collaboration.
  • Binary data: Send images, audio, video.
  • Custom protocol: Need full control over message format.
  • High message frequency: Hundreds of messages per second.

Examples:

  • Live auction bidding
  • Real-time chat
  • Multiplayer games
  • Collaborative editing
  • Live video streaming

Don’t Use WebSocket Just Because:

  • ❌ “It’s more advanced”—complexity without benefit
  • ❌ “Everyone uses it”—SSE is often simpler
  • ❌ “It’s faster”—SSE is fast enough for most use cases
  • ❌ “It’s bidirectional”—do you actually need bidirectional?

Conclusion

SSE and WebSocket both enable real-time communication but serve different needs. SSE is ideal for one-way server-to-client updates with automatic reconnection and HTTP compatibility. WebSocket is best for bidirectional, low-latency messaging.

Modern PetstoreAPI demonstrates using both: SSE for notifications and updates, WebSocket for live auctions and chat. Choose based on your actual use case.

Test your real-time APIs with Apidog to ensure your SSE and WebSocket implementations are robust across all scenarios.

FAQ

Can SSE work through corporate firewalls?

Yes. SSE uses standard HTTP, so it works through HTTP proxies and firewalls. WebSocket uses a custom protocol that some proxies block.

Is WebSocket faster than SSE?

WebSocket has slightly lower latency (no HTTP overhead per message), but for most apps, the difference is negligible. SSE is fast enough for notifications, feeds, and status updates.

How do you handle SSE reconnection?

Browsers handle SSE reconnection automatically. Send event IDs from the server, and the client will resume from the last received event using the Last-Event-ID header.

Can you use SSE with mobile apps?

Yes. iOS and Android support SSE via HTTP clients or libraries. SSE works anywhere HTTP works.

What’s the maximum SSE connection time?

No hard limit. SSE connections can stay open indefinitely. Some proxies or load balancers may have timeouts (usually 30–60 seconds), but the browser reconnects automatically.

Can WebSocket send binary data?

Yes. WebSocket supports both text and binary frames. You can send images, audio, or any binary data without base64 encoding.

How many SSE connections can a browser have?

Browsers limit SSE connections per domain (typically 6). This is rarely an issue—most apps only need 1–2 SSE connections.

Do you need a special server for SSE?

No. Any HTTP server can handle SSE. Just set the correct headers (Content-Type: text/event-stream) and keep the connection open.

Top comments (0)