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)?
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);
});
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 (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"}
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
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
};
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
- 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://orwss://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);
});
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();
});
});
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;
}
};
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
}));
});
}
});
});
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
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
2. Send test messages:
{"type":"bid","amount":500}
{"type":"watch","petId":"019b4132"}
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)