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

WebSocket and Server-Sent Events both enable real-time communication, but they solve different problems. Learn when to use each and how Modern PetstoreAPI implements both protocols.

Ashley Innocent

Ashley Innocent

13 March 2026

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. A pet’s status changes from “available” to “adopted”—clients need to know immediately. Do you use WebSocket or Server-Sent Events (SSE)?

Most developers default to WebSocket because it’s “more powerful.” But SSE is often the better choice. It’s simpler, works over standard HTTP, and handles reconnection automatically. WebSocket adds complexity you might not need.

Modern PetstoreAPI implements both protocols. SSE for pet status updates and order notifications. WebSocket for live auction bidding and real-time chat. Each protocol serves different use cases.

💡
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.
button

In this guide, you’ll learn the differences between SSE and WebSocket, see real examples from Modern PetstoreAPI, and discover 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);
});

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"}

SSE Features

1. One-way communication

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

2. Built on HTTP

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

3. Automatic reconnection

If the connection drops, the browser automatically reconnects.

4. Event IDs for resume

Server can send event IDs. Client resumes from last received event:

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

id: 124
event: status-change
data: {"petId":"019b4127"}

If disconnected, 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

SSE Limitations

1. One-way only

Client can’t send messages through SSE. Need separate HTTP requests for client-to-server communication.

2. Text-only

SSE sends text. Binary data must be base64-encoded.

3. Browser connection limits

Browsers limit SSE connections per domain (typically 6). Not an issue for most apps.

4. No built-in compression

HTTP compression works, but no protocol-level compression 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
};

Server can send anytime:

{"type":"bid","userId":"user-456","amount":550}
{"type":"outbid","newAmount":550}

Client can send anytime:

{"type":"bid","amount":600}
{"type":"watch","petId":"019b4132"}

WebSocket Features

1. Bidirectional

Both client and server can send messages anytime. True two-way communication.

2. Low latency

Persistent connection. No HTTP overhead per message. Ideal for gaming, chat, live collaboration.

3. Binary support

Can send binary data directly. No base64 encoding needed.

4. Custom protocol

Uses ws:// or wss:// (secure). Not HTTP after initial handshake.

5. Frame-based

Messages are framed. Can send partial messages and reassemble.

WebSocket Limitations

1. Complex setup

Requires WebSocket server. More complex than HTTP endpoints.

2. Manual reconnection

No automatic reconnection. You must implement retry logic.

3. Proxy issues

Some corporate proxies block WebSocket. HTTP proxies don’t understand ws://.

4. Stateful

Server must track connections. Harder to scale than stateless HTTP.

5. No HTTP features

Can’t use 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

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);
});

Server implementation:

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();
  });
});

Use cases:

WebSocket for Live Auctions

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

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;
  }
};

Server implementation:

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
        }));
      });
    }
  });
});

Use cases:

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

2. Validate events:

3. Test scenarios:

Testing WebSocket

1. Create WebSocket connection:

wss://petstoreapi.com/auctions/019b4132

2. Send test messages:

{"type":"bid","amount":500}
{"type":"watch","petId":"019b4132"}

3. Validate responses:

4. Test scenarios:

When to Use Each

Use SSE When:

Examples:

Use WebSocket When:

Examples:

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 they’re designed for different scenarios. SSE excels at one-way server-to-client updates with automatic reconnection and HTTP compatibility. WebSocket shines for bidirectional, low-latency communication.

Modern PetstoreAPI shows how to use both protocols effectively. SSE for notifications and status updates. WebSocket for live auctions and chat. Choose based on your use case, not which protocol seems “better.”

Test your real-time APIs with Apidog to ensure both SSE and WebSocket implementations work correctly across different 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 applications, the difference is negligible. SSE is fast enough for notifications, feeds, and status updates.

How do you handle SSE reconnection?

The browser handles 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 through native 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 (typically 30-60 seconds), but the browser will automatically reconnect.

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.

Explore more

Socket.IO vs Native WebSocket: Which Should You Use?

Socket.IO vs Native WebSocket: Which Should You Use?

Socket.IO adds features like automatic reconnection and fallbacks, but Native WebSocket is simpler and faster. Learn when to use each and how Modern PetstoreAPI implements both.

13 March 2026

When Should You Use MQTT Instead of HTTP for APIs?

When Should You Use MQTT Instead of HTTP for APIs?

MQTT excels for IoT devices with limited bandwidth and unreliable networks. Learn when MQTT beats HTTP and how Modern PetstoreAPI uses MQTT for pet tracking devices and smart feeders.

13 March 2026

What Is the Model Context Protocol (MCP) and Why Does It Matter for APIs?

What Is the Model Context Protocol (MCP) and Why Does It Matter for APIs?

MCP lets AI assistants like Claude Desktop connect to your APIs securely. Learn how MCP works and how Modern PetstoreAPI implements MCP for AI-powered pet management.

13 March 2026

Practice API Design-first in Apidog

Discover an easier way to build and use APIs