Back to Blog
IoT Engineering

WebSocket vs MQTT vs Server-Sent Events: Real-Time IoT Protocol Deep Dive

WebSocket, MQTT, and SSE all promise real-time communication, but they solve different problems. Here is exactly when to use each in an IoT stack — with benchmarks and code.

April 22, 2024
12 min read
WebSocketMQTTSSEReal-Time

WebSocket vs MQTT vs Server-Sent Events: Real-Time IoT Protocol Deep Dive

Every IoT system needs real-time communication, but "real-time" means different things at different layers of your stack. Your temperature sensor talking to the cloud has radically different requirements than your browser dashboard talking to your backend. Picking the wrong protocol at any layer means wasted bandwidth, battery drain, or a dashboard that lags by five seconds when a machine alarm fires.

This guide cuts through the confusion. We'll compare WebSocket, MQTT, and SSE on the dimensions that actually matter in production IoT — not theoretical benchmarks, but the tradeoffs we navigate in real deployments.

The Core Distinction: Who Talks to Whom

Before comparing protocols, establish the communication model at each layer:

| Layer | Sender | Receiver | Pattern | |---|---|---|---| | Device → Cloud | ESP32/STM32 | AWS IoT Core / Broker | Publish (mostly) | | Cloud → Device | Backend / Rules | ESP32/STM32 | Subscribe (commands) | | Backend → Browser | Node.js backend | React dashboard | Push | | Browser → Backend | React dashboard | Node.js | Request / command |

MQTT owns the device layer. WebSocket and SSE compete for the browser layer. Understanding that distinction saves weeks of architectural regret.

MQTT: Purpose-Built for Constrained Devices

MQTT was designed in 1999 for satellite SCADA links — high latency, low bandwidth, unreliable connections. That heritage makes it perfect for embedded IoT.

Why MQTT wins on the device side:

  • 2-byte fixed header — minimum packet overhead, critical on NB-IoT or LoRa links
  • Persistent sessions — broker queues messages for devices that go offline (QoS 1/2)
  • Last Will and Testament (LWT) — device publishes a death notice automatically if connection drops
  • Native pub/sub — fan-out to thousands of subscribers with no extra code
  • QoS levels — fire-and-forget for telemetry, exactly-once for commands
  • // ESP32: MQTT telemetry with LWT
    void setupMQTT() {
      client.setServer(MQTT_BROKER, 8883);

    // Last Will: published automatically if we disconnect ungracefully client.connect( DEVICE_ID, nullptr, nullptr, // username/password "devices/sensor01/status", // LWT topic 1, // LWT QoS true, // LWT retain "{"online": false}" // LWT payload );

    // Publish online status client.publish("devices/sensor01/status", "{"online": true}", true); // retained

    // Subscribe to command topic client.subscribe("devices/sensor01/cmd", 1); }

    void publishTelemetry(float temp, float humidity) { char payload[128]; snprintf(payload, sizeof(payload), "{"t":%.2f,"h":%.2f,"ts":%lu}", temp, humidity, millis() ); // QoS 0 for telemetry — fire and forget, lowest overhead client.publish("devices/sensor01/telemetry", payload, 0); }

    MQTT limitations for the browser: Most browsers cannot open raw TCP connections. MQTT-over-WebSocket exists, but you lose half the advantage — you're running MQTT framing inside WebSocket framing inside TLS. For browser clients, SSE or WebSocket is the better abstraction.

    WebSocket: Full-Duplex for Interactive Dashboards

    WebSocket upgrades an HTTP connection to a persistent, full-duplex TCP channel. Both client and server can send messages at any time with minimal overhead after the handshake.

    When to choose WebSocket:

  • Dashboard needs to send commands to devices (bidirectional required)
  • Sub-100ms round trips matter (live control interfaces, gaming-style IoT)
  • You need binary frames (streaming audio/video from a device)
  • Your backend already uses Socket.io or a similar framework
  • // Node.js backend: bridge MQTT ↔ WebSocket
    import { WebSocketServer } from 'ws'
    import mqtt from 'mqtt'

    const mqttClient = mqtt.connect('mqtts://your-broker:8883', { cert: fs.readFileSync('backend.crt'), key: fs.readFileSync('backend.key'), ca: fs.readFileSync('rootCA.pem'), })

    const wss = new WebSocketServer({ port: 8080 })

    // Track which WS clients are watching which devices const subscriptions = new Map>()

    mqttClient.on('message', (topic, payload) => { // topic: "devices/sensor01/telemetry" const deviceId = topic.split('/')[1] const watchers = subscriptions.get(deviceId)

    if (watchers) { const message = JSON.stringify({ deviceId, data: JSON.parse(payload.toString()), receivedAt: Date.now(), }) watchers.forEach((ws) => { if (ws.readyState === WebSocket.OPEN) ws.send(message) }) } })

    wss.on('connection', (ws) => { ws.on('message', (raw) => { const msg = JSON.parse(raw.toString())

    if (msg.type === 'subscribe') { // Client wants live data for a device if (!subscriptions.has(msg.deviceId)) { subscriptions.set(msg.deviceId, new Set()) mqttClient.subscribe(devices/${msg.deviceId}/telemetry) } subscriptions.get(msg.deviceId)!.add(ws) }

    if (msg.type === 'command') { // Client sending a command to a device — forward via MQTT mqttClient.publish( devices/${msg.deviceId}/cmd, JSON.stringify(msg.payload), { qos: 1 } ) } })

    ws.on('close', () => { // Clean up subscriptions for this client subscriptions.forEach((watchers, deviceId) => { watchers.delete(ws) if (watchers.size === 0) { subscriptions.delete(deviceId) mqttClient.unsubscribe(devices/${deviceId}/telemetry) } }) }) })

    WebSocket overhead: The opening handshake is HTTP (several hundred bytes). After that, each frame has 2–14 bytes of overhead. For high-frequency telemetry streams, this is negligible.

    Server-Sent Events: The Underrated Option

    SSE is an HTTP/1.1 feature where the server holds the response open and streams events indefinitely. The browser's EventSource API handles reconnection automatically.

    When SSE beats WebSocket:

  • Dashboard is read-only (no commands to devices)
  • Your backend is behind a standard HTTP load balancer
  • You want automatic reconnection with Last-Event-ID replay
  • HTTP/2 multiplexing matters (SSE benefits more than WS from HTTP/2)
  • // Next.js API route: SSE endpoint for live device data
    import type { NextRequest } from 'next/server'

    export async function GET(req: NextRequest) { const deviceId = req.nextUrl.searchParams.get('deviceId')

    const stream = new TransformStream() const writer = stream.writable.getWriter() const encoder = new TextEncoder()

    const sendEvent = (event: string, data: unknown) => { writer.write( encoder.encode(`event: ${event} data: ${JSON.stringify(data)}

    `) ) }

    // Subscribe to MQTT via your internal broker client const unsubscribe = mqttBridge.subscribe( devices/${deviceId}/telemetry, (payload) => { sendEvent('telemetry', payload) } )

    req.signal.addEventListener('abort', () => { unsubscribe() writer.close() })

    return new Response(stream.readable, { headers: { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', Connection: 'keep-alive', }, }) }

    SSE limitations: Unidirectional only — browser cannot send data over the SSE connection. For commands, you issue a separate HTTP POST. This is actually a feature for many dashboards — it forces clean separation between read (SSE) and write (REST).

    Protocol Comparison Table

    | Dimension | MQTT | WebSocket | SSE | |---|---|---|---| | Transport | TCP (or TCP+WS) | TCP (HTTP upgrade) | HTTP/1.1 or HTTP/2 | | Direction | Bidirectional | Bidirectional | Server → Client only | | Browser native | No (needs lib) | Yes | Yes (EventSource) | | Reconnection | Client must implement | Client must implement | Automatic | | Message ordering | QoS-dependent | Ordered | Ordered | | Overhead per message | 2 bytes minimum | 2–14 bytes | ~6 bytes (framing) | | Best for | IoT devices | Interactive dashboards | Read-only dashboards | | Load balancer friendly | Needs TCP pass-through | Needs WS support | Yes (standard HTTP) |

    The Architecture We Actually Deploy

    In production, we use all three — at different layers:

    ESP32 ──MQTT/TLS──► AWS IoT Core
                             │
                        IoT Rules Engine
                             │
                        Node.js Backend ◄──── REST (commands from dashboard)
                             │
                        SSE or WebSocket ──► React Dashboard
    

  • MQTT between every device and the broker. Always. No exceptions.
  • SSE for monitoring-style dashboards (fleet overview, sensor timelines).
  • WebSocket when the dashboard sends commands in real time (industrial control panels, remote configuration).
  • The mistake we see most often: teams use WebSocket for everything, including the device connection, because they're comfortable with it. On a 4G-connected sensor reporting every 10 seconds, WebSocket works fine. On an NB-IoT sensor on a 200kbps link reporting every 30 seconds, the WebSocket handshake overhead adds meaningful latency and the lack of QoS means you drop readings during brief outages. MQTT handles both gracefully.

    Need help? [Contact Code Caracal](/contact) — we've shipped these systems for clients across 15+ countries.

    Written by CodeCaracal Engineering

    We write from production experience — every technique in our articles has been deployed to real clients. No academic theory.

    More Articles

    Business · 12 min read

    IoT Device Compliance: FCC, CE, and Product Certification Guide for Hardware Startups

    Business · 11 min read

    What to Look for When Hiring an IoT Development Partner: 8 Critical Criteria

    Business · 11 min read

    IoT MVP to Production: Realistic Timeline and Budget for Hardware Startups

    Business · 11 min read

    IoT Development Agency vs Building In-House: A Decision Framework for Founders

    IoT Dashboard · 13 min read

    Next.js IoT Analytics Dashboard: From Sensor Data to Production App

    Business · 11 min read

    How Much Does It Cost to Build an IoT Product in 2024? A Realistic Breakdown

    IoT Dashboard · 11 min read

    IoT Dashboard UX: Design Principles for Industrial Monitoring Interfaces

    IoT Dashboard · 12 min read

    Node.js WebSocket Server: The Real-Time Backend for IoT Dashboards

    Cloud & DevOps · 12 min read

    Containerizing IoT Backend Services with Docker: From Dev to Production

    IoT Dashboard · 14 min read

    Grafana + InfluxDB IoT Monitoring: Complete Production Setup Guide

    IoT Dashboard · 12 min read

    Building Real-Time IoT Dashboards with React and Recharts

    Cloud & DevOps · 13 min read

    CI/CD for Embedded Firmware: Automated Build, Test, and OTA Release Pipeline

    Mobile Development · 12 min read

    Flutter Offline-First IoT Apps: Hive + Sync Architecture That Works in the Field

    Cloud & DevOps · 14 min read

    Terraform for IoT Infrastructure: Provisioning AWS IoT Core, Lambda, and InfluxDB as Code

    Mobile Development · 10 min read

    Flutter IoT Alerts: Firebase Push Notifications for Device Events

    Cloud & DevOps · 12 min read

    Deploying IoT Backends on AWS: ECS Fargate vs Lambda vs EC2 Decision Guide

    Mobile Development · 11 min read

    Flutter + MQTT: Building Production IoT Mobile Apps That Scale

    Mobile Development · 13 min read

    Flutter BLE: Building a Bluetooth IoT Controller App from Scratch

    Cloud & DevOps · 13 min read

    AWS IoT Core vs Azure IoT Hub vs Google Cloud IoT: 2024 Honest Comparison

    IoT Engineering · 13 min read

    Kafka vs RabbitMQ for IoT: Choosing the Right Message Queue for High-Volume Telemetry

    IoT Engineering · 14 min read

    IoT System Testing: Unit, Integration, Hardware-in-the-Loop, and End-to-End

    IoT Engineering · 14 min read

    Predictive Maintenance with IoT Sensor Data: From Threshold to Machine Learning

    Embedded Systems · 14 min read

    IoT Bootloader Design: Secure Boot, A/B Partitions, and Reliable OTA Recovery

    IoT Engineering · 14 min read

    Multi-Tenant IoT Platform Architecture: Isolation, Scaling, and Data Partitioning

    Embedded Systems · 14 min read

    Memory Management in Embedded Firmware: Avoiding Heap Fragmentation and Stack Overflows

    IoT Engineering · 13 min read

    IoT Cost Optimization: How We Cut AWS IoT Bills by 60% Without Sacrificing Reliability

    IoT Engineering · 12 min read

    Edge Computing in IoT: When to Process On-Device vs In the Cloud

    IoT Engineering · 13 min read

    Digital Twins for IoT: Building a Virtual Mirror of Your Physical Devices

    Embedded Systems · 14 min read

    ESP32 Deep Sleep Mastery: Cutting Power Consumption from 240mA to 10µA

    IoT Engineering · 10 min read

    MQTT QoS 0, 1, and 2 Explained: Choosing the Right Level for IoT

    IoT Engineering · 14 min read

    IoT Monitoring and Observability: Metrics, Logs, and Distributed Tracing

    Embedded Systems · 14 min read

    Debugging Embedded Firmware: JTAG, GDB, Logic Analyzers, and Serial Tracing

    Embedded Systems · 13 min read

    STM32 HAL vs Low-Level Drivers: When the Abstraction Costs You Too Much

    IoT Engineering · 13 min read

    IoT Data Pipeline: From Raw Sensor Reading to Live Dashboard in Under 100ms

    IoT Engineering · 13 min read

    Zero-Touch IoT Device Provisioning: Scaling from 10 to 100,000 Devices

    Embedded Systems · 13 min read

    UART vs SPI vs I2C: Choosing the Right Protocol for Sensor Integration

    IoT Engineering · 12 min read

    Real-Time IoT Alerting: From Simple Thresholds to ML Anomaly Detection

    Embedded Systems · 12 min read

    ESP32 Partition Table: Designing Flash Layout for Production Firmware

    IoT Engineering · 12 min read

    IoT Architecture Patterns: Hub-and-Spoke, Mesh, and Edge-Cloud Hybrid

    Embedded Systems · 13 min read

    IoT Battery Life Optimization: Engineering Devices That Last Years on a Single Charge

    IoT Engineering · 13 min read

    Time-Series Databases for IoT: InfluxDB vs TimescaleDB vs AWS Timestream

    Security · 14 min read

    Zero-Trust Security for Embedded IoT: Why Your Devices Are Probably Vulnerable

    Embedded Systems · 14 min read

    FreeRTOS on ESP32: Task Scheduling, Queues, and Resource Management for IoT

    IoT Engineering · 12 min read

    Building a Production IoT Gateway with Raspberry Pi and Node.js

    Embedded Systems · 13 min read

    ESP32 vs STM32: Choosing the Right Microcontroller for Your IoT Project

    Mobile Development · 10 min read

    Flutter + WebSocket: Building Real-Time IoT Dashboards That Don't Stutter

    IoT Engineering · 13 min read

    IoT Fleet Management at Scale: AWS IoT Core Device Registry and Provisioning

    IoT Engineering · 11 min read

    MQTT vs HTTP for IoT: Which Protocol Wins in Production?

    IoT Engineering · 12 min read

    ESP32 → MQTT → AWS IoT Core: The Production-Grade Architecture Guide

    Let's Build Together

    Got an IoT challenge?
    We've shipped it.

    Whether you need a fleet to track, a factory to monitor, or a farm to automate — our team has done it before and we'd love to build it with you. Typical response time: under 24 hours.

    No upfront commitment99.9% uptime SLANDA on requestFixed-price options