SSE vs WebSockets em 2026: qual usar em cada caso?

Profile picture of Equipe Arvucore

Equipe Arvucore

September 22, 2025 · Atualizado em August 26, 2026

16 min read

Se você só precisa empurrar dados do servidor para o browser, use Server-Sent Events: eles rodam sobre HTTP puro, reconectam sozinhos, atravessam proxies e plataformas serverless, e dão conta de notificações, dashboards e streaming de tokens de LLM. Use WebSockets quando o cliente também precisa enviar muitas mensagens pequenas com baixa latência, ou quando você precisa de frames binários, como em chat, edição colaborativa e jogos. Polling e long polling continuam sendo fallbacks, não primeira escolha.

Como SSE e WebSockets funcionam no nível do protocolo

Server-Sent Events são HTTP comum. O browser envia um GET, o servidor responde com Content-Type: text/event-stream e nunca fecha a resposta. Os eventos são linhas de texto UTF-8: data:, event: e id: opcionais, separados por uma linha em branco. Qualquer coisa capaz de fazer streaming de uma resposta HTTP consegue servir SSE.

No HTTP/1.1 isso tem um custo conhecido: os browsers limitam as conexões por origem a seis. Cada EventSource aberto ocupa uma delas, então uma página com vários streams, ou um usuário com várias abas, pode esgotar o orçamento e travar as requisições normais. Sobre HTTP/2 o problema desaparece: cada stream SSE é um stream multiplexado dentro de uma única conexão TCP, e o limite por origem fica na casa de uma centena de streams, negociado com o servidor. Em 2026 o limite morde principalmente em desenvolvimento local e atrás de proxies legados que rebaixam para HTTP/1.1.

WebSockets começam como HTTP e deixam de ser HTTP. O cliente envia um GET com Upgrade: websocket e Connection: Upgrade; o servidor responde 101 Switching Protocols, e a partir daí o socket TCP carrega frames WebSocket (RFC 6455): texto ou binário, com opcodes, fragmentação, mascaramento do cliente para o servidor e frames de controle ping/pong. Sobre HTTP/2 existe um mecanismo separado (RFC 8441, o método CONNECT estendido) que tunela um WebSocket dentro de um stream HTTP/2, mas o suporte em servidores, proxies e bibliotecas é inconsistente, então a maioria das implantações ainda roda WebSockets em uma conexão HTTP/1.1 dedicada.

Tudo que vem depois reage a essa diferença: um stream SSE é uma resposta HTTP longa; um WebSocket é um socket opaco que as ferramentas de HTTP já não entendem.

Reconexão, ordenação e Last-Event-ID

É aqui que o SSE justifica a fama de simples. O EventSource reconecta automaticamente quando a conexão cai, usando o intervalo retry: que o servidor pode definir. Se o servidor marca os eventos com id:, o browser devolve o último como header Last-Event-ID na reconexão. O servidor reenvia o que o cliente perdeu a partir de um log, de um Redis Stream ou de um offset do Kafka. Retomada faz parte da spec.

WebSockets não têm nada disso. Quando o socket fecha, o objeto WebSocket está morto. Seu código precisa detectar o fechamento, aplicar backoff com jitter para que milhares de clientes não reconectem no mesmo instante, reautenticar, reassinar e reconciliar o estado. Se você precisa de "me dê o que eu perdi", projeta isso por conta própria com números de sequência ou um ID de sessão retomável.

Duas observações para os dois transportes. Conexões ociosas morrem: operadoras, tabelas de NAT e load balancers derrubam sockets em silêncio por dezenas de segundos a alguns minutos, então envie um heartbeat (um comentário SSE : ping ou um frame ping do WebSocket) dentro dessa janela. E o EventSource tenta de novo para sempre, por design, então um erro permanente como um 401 precisa ser tratado explicitamente.

Proxies, load balancers e CDNs

SSE parece uma resposta HTTP lenta. Todo proxy L7, ingress e CDN a encaminha, mas vários fazem buffer das respostas por padrão, o que transforma um stream em nada até o buffer encher. Desative o buffering por rota (X-Accel-Buffering: no no Nginx, o equivalente no seu ingress ou CDN), desative a compressão no stream ou use um compressor que faça flush, e aumente os timeouts de ociosidade e leitura para esse caminho. A maioria dos incidentes de "SSE não funciona atrás do nosso proxy" é uma dessas três configurações.

WebSockets precisam que o proxy respeite o handshake Upgrade e deixe o socket passar. Proxies modernos, load balancers de nuvem e CDNs fazem isso, muitas vezes com condições: planos específicos, timeouts de ociosidade que você não pode aumentar, ou nenhum suporte em um middlebox corporativo mais antigo. Redes restritivas ainda podem bloquear wss://, e é por isso que existem fallbacks de long polling. Algumas CDNs cobram conexões WebSocket de forma diferente das requisições HTTP. Passthrough L4 evita quase tudo isso, mas abre mão do roteamento e da observabilidade L7.

HTTP/3 e QUIC mudam pouco aqui por enquanto: SSE funciona sobre HTTP/3 como qualquer resposta, enquanto WebSocket sobre HTTP/3 (RFC 9220) ainda raramente está implantado de ponta a ponta.

Autenticação: cookies vs headers

Nenhuma das duas APIs do browser permite definir um header Authorization. Isso surpreende equipes com um setup limpo de bearer token para a API REST que depois descobrem que o canal em tempo real não consegue reaproveitá-lo.

Suas opções:

  • Cookies. Tanto o EventSource (com withCredentials: true para cross-origin) quanto o WebSocket enviam cookies. É o caminho mais simples quando o front-end é same-site. Defina SameSite, Secure e HttpOnly, e valide o header Origin no servidor, porque WebSockets não são cobertos pelo CORS.
  • Token na query string. Funciona nos dois, mas o token vai parar em logs de acesso, logs de proxy e histórico do browser. Use um ticket de curta duração e propósito único emitido por um POST autenticado, não o token de sessão de longa duração.
  • Token na primeira mensagem (só WebSocket). Abra o socket, envie { "type": "auth", "token": "..." } e feche se ele não chegar em um segundo. É padrão na prática; ainda assim, a conexão existe por um breve momento sem autenticação.
  • Substituir o EventSource por fetch. Um fetch com streaming via response.body.getReader() aceita qualquer header, funciona com AbortController e consegue interpretar text/event-stream com poucas linhas de código ou uma biblioteca pequena. Você perde a reconexão automática e o Last-Event-ID, que precisa reimplementar. É o padrão que a maioria das interfaces de chat com LLM usa hoje.

O header Sec-WebSocket-Protocol às vezes é abusado para carregar um token; funciona, mas amarra a autenticação a um campo pensado para negociação de subprotocolo. Para tokens e sessões em geral, veja autenticação moderna com OAuth 2.0, JWT e zero trust.

Escala e fan-out: pub/sub, Redis, sticky sessions

Um único nó consegue manter de dezenas a centenas de milhares de conexões ociosas, dependendo do runtime, da memória por conexão e do TLS. O problema nunca é a quantidade; é que uma mensagem produzida no nó A precisa chegar a um cliente conectado no nó B.

A resposta padrão é a mesma para os dois transportes: mantenha os nós de conexão sem estado e coloque uma malha de pub/sub atrás deles. Redis Pub/Sub ou Redis Streams, NATS ou Kafka carregam os eventos; cada nó assina os canais que interessam aos seus clientes e escreve nos sockets locais. Redis Streams e Kafka também dão o log de replay de que o Last-Event-ID precisa. Projete o modelo de tópicos cedo; assinaturas sem limite são a causa usual de crescimento de memória.

Onde os transportes diferem:

  • SSE balanceia como qualquer requisição HTTP. Qualquer nó pode atender qualquer cliente, e uma reconexão pode cair em qualquer lugar, porque o estado de retomada viaja no Last-Event-ID. Sticky sessions são opcionais.
  • WebSockets têm estado por natureza. Se o servidor mantém estado por conexão (assinaturas, presença, cursores) em memória, uma reconexão precisa voltar ao mesmo nó, o que significa sticky sessions, ou esse estado precisa ser externalizado. Gateways gerenciados e frameworks com backplane em Redis existem principalmente para resolver isso.
  • Backpressure é uma preocupação do WebSocket nas duas direções e do SSE em uma. Limite as filas por conexão, agrupe atualizações para clientes lentos, e descarte ou degrade em vez de deixar o bufferedAmount crescer.

Deploys são um evento de escala disfarçado: um rollout fecha todas as conexões do nó, e dezenas de milhares de clientes reconectam de uma vez. Drene com cuidado, espalhe os restarts e garanta que o backoff do cliente tenha jitter.

Serverless, browsers e mobile

Serverless. Funções têm escopo de requisição e limite de tempo. WebSockets não conseguem viver dentro de uma função normal; você precisa de um gateway gerenciado que segure o socket e invoque funções por mensagem, mais um store para os IDs de conexão. SSE encaixa melhor: uma função de streaming ou de edge retorna um ReadableStream e o mantém aberto até o limite da plataforma, normalmente minutos, com o cliente reconectando em seguida. Primitivas no estilo actor, como Durable Objects, seguram WebSockets entre requisições onde você precisa. Veja computação serverless para o modelo de custo que torna conexões longas caras na cobrança por invocação.

Browsers. As duas APIs são suportadas em todos os browsers atuais, desktop e mobile. O EventSource é só texto e só GET; o WebSocket suporta binário e não tem essas restrições.

Apps mobile. As plataformas nativas suspendem apps em segundo plano e fecham seus sockets. Nenhum transporte sobrevive a isso; tudo que precisa alcançar um usuário em segundo plano ou offline passa por APNs, FCM ou Web Push, com o canal ao vivo usado só em primeiro plano. Bibliotecas de WebSocket são maduras no iOS e no Android; os clientes SSE são mais enxutos, e muitas equipes usam uma requisição HTTP com streaming. O custo de bateria é ditado pelos despertares do rádio, então um heartbeat de 20 segundos pesa mais do que a escolha do protocolo.

Desvantagens do WebSocket: os custos, explicitamente

WebSockets são poderosos e, com frequência, o padrão errado. Os custos específicos:

  1. Sem reconexão ou retomada embutida. Toda equipe reimplementa backoff, reassinatura e recuperação de mensagens perdidas.
  2. Sem headers customizados a partir do browser. Auth é por cookies, query string ou handshake na primeira mensagem.
  3. Conexões com estado. Sticky sessions ou um store de estado externo; deploys blue-green e rolling ficam mais difíceis.
  4. Encaixe ruim com serverless. Precisa de um gateway dedicado e de um registro de conexões; a cobrança por minuto de conexão acumula.
  5. Fora do ferramental HTTP. Sem cache HTTP, sem compressão padrão a menos que permessage-deflate seja negociado, sem log de requisição por mensagem, e observabilidade exige instrumentação customizada.
  6. Atrito com proxies e redes. O Upgrade precisa ser respeitado de ponta a ponta; algumas redes corporativas e planos de CDN bloqueiam ou limitam.
  7. Sem proteção do CORS. Sequestro de WebSocket cross-site é real se você depende de cookies sem checar o Origin.
  8. Protocolo customizado por cima. Framing de mensagens, versionamento e semântica de erro são seus para definir e manter compatíveis com versões anteriores.
  9. Head-of-line blocking em uma única conexão TCP. Um frame grande atrasa tudo que vem atrás; não há priorização de streams.
  10. Superfície operacional maior. Limites de conexão, file descriptors, CPU de TLS e testes de carga exigem ferramentas que entendam o protocolo.

Nada disso é desqualificante para chat ou colaboração. É a conta da bidirecionalidade; pague só quando for usá-la.

Polling e long polling: os fallbacks

Short polling dispara uma requisição num timer. É universalmente compatível, cacheável e a resposta certa para dados que mudam a cada poucos minutos. A uma requisição por segundo por cliente vira a opção mais cara de todas, com latência limitada pelo intervalo.

Long polling mantém a requisição aberta até chegar dado ou estourar um timeout, e então o cliente pede de novo. Aproxima push sobre HTTP puro e funciona atrás de quase tudo. O custo é um round trip HTTP completo por mensagem, uma lacuna entre respostas em que eventos se perdem a menos que você carregue um cursor, e os mesmos limites de conexão do HTTP/1.1 que o SSE, sem a reconexão definida pela spec.

Em 2026 long polling é um fallback para redes que quebram tanto SSE quanto WebSockets, não um design principal. Se a sua biblioteca ainda o usa por padrão, verifique o motivo.

Tabela comparativa: SSE vs WebSockets vs long polling

Critério Server-Sent Events WebSockets Long polling
Direção Servidor para cliente (cliente usa requisições normais) Full duplex Iniciado pelo cliente, servidor segura
Transporte Stream de resposta HTTP/1.1, HTTP/2, HTTP/3 Socket TCP com upgrade; túnel HTTP/2 raramente implantado Requisições HTTP simples
Reconexão Automática, embutida no EventSource Manual Manual (a cada requisição)
Retomada após queda Last-Event-ID, definido na spec Números de sequência customizados Cursor customizado
Suporte a binário Não (texto, base64 se necessário) Sim, frames nativos Sim (corpo da resposta)
Auth no browser Cookies, ticket na query ou fetch com headers Cookies, ticket na query, primeira mensagem Qualquer header
Amigável a proxy e CDN Alta; desative buffering e aumente timeouts Média; precisa de suporte a Upgrade de ponta a ponta A mais alta
Limite por origem no HTTP/1.1 6 conexões; resolvido pelo HTTP/2 Conexão separada para cada um 6 conexões
Modelo de escala Nós sem estado mais pub/sub, sem stickiness Pub/sub mais sticky sessions ou estado externo Sem estado
Encaixe com serverless Bom em funções de streaming ou edge Exige gateway gerenciado Bom
Overhead por mensagem Baixo (poucos bytes por evento) O menor (2 a 14 bytes de framing) Alto (HTTP completo por mensagem)
Melhores casos de uso Notificações, dashboards, feeds, streaming de LLM Chat, edição colaborativa, jogos, trading Fallback, atualizações de baixa frequência

Código: cliente EventSource, endpoint SSE em Node, echo WebSocket

Um endpoint SSE mínimo em Node, sem frameworks. Repare nos headers que impedem os proxies de fazer buffer e no id: que habilita a retomada.

// server.js — Node 20+, sem dependências
import http from "node:http";

http.createServer((req, res) => {
  if (req.url !== "/events") { res.writeHead(404).end(); return; }

  res.writeHead(200, {
    "Content-Type": "text/event-stream",
    "Cache-Control": "no-cache, no-transform",
    "Connection": "keep-alive",
    "X-Accel-Buffering": "no",
  });

  let id = Number(req.headers["last-event-id"] ?? 0);
  const timer = setInterval(() => {
    id += 1;
    res.write(`id: ${id}\nevent: tick\ndata: ${JSON.stringify({ t: Date.now() })}\n\n`);
  }, 1000);
  const heartbeat = setInterval(() => res.write(": ping\n\n"), 15000);

  req.on("close", () => { clearInterval(timer); clearInterval(heartbeat); });
}).listen(3000);

O lado do browser são poucas linhas, e a reconexão vem de graça.

const es = new EventSource("/events", { withCredentials: true });
es.addEventListener("tick", (e) => console.log(JSON.parse(e.data)));
es.onerror = () => console.warn("disconnected, browser will retry");

Um echo WebSocket mínimo com o pacote ws. Compare o que o cliente precisa fazer quando o socket cai.

// ws-server.js
import { WebSocketServer } from "ws";

const wss = new WebSocketServer({ port: 3001 });
wss.on("connection", (socket, req) => {
  if (req.headers.origin !== "https://app.example.com") { socket.close(1008); return; }
  socket.on("message", (data) => socket.send(data));
});
// client
let ws, attempt = 0;
function connect() {
  ws = new WebSocket("wss://api.example.com/ws");
  ws.onopen = () => { attempt = 0; ws.send("hello"); };
  ws.onmessage = (e) => console.log(e.data);
  ws.onclose = () => setTimeout(connect, Math.min(30000, 500 * 2 ** attempt++) * (0.5 + Math.random()));
}
connect();

Os dois servidores devem ficar atrás de TLS em produção; nenhum dos exemplos trata autenticação além de uma checagem de Origin.

Checklist de decisão: quando usar cada um

  • Chat (1:1, grupo, suporte). WebSockets. As mensagens fluem nas duas direções o tempo todo, e indicadores de digitação e presença são baratos num socket aberto. SSE mais POST funciona para widgets de suporte de baixo volume.
  • Dashboards ao vivo e monitoramento. SSE. Unidirecional, texto, tolera um segundo de reconexão. Agrupe atualizações no servidor; veja desenvolvimento de dashboards para o lado dos dados.
  • Notificações e feeds de atividade. SSE enquanto a aba está aberta; Web Push, APNs ou FCM quando não está. Nunca um WebSocket só para notificações.
  • Edição colaborativa. WebSockets. Sincronização com CRDT ou OT precisa de mensagens pequenas, frequentes e bidirecionais, muitas vezes com codificação binária.
  • Jogos multiplayer. WebSockets hoje; WebTransport onde você pode exigir HTTP/3 e quer datagramas não confiáveis para atualizações de posição.
  • Streaming de tokens de LLM. SSE, ou um fetch com streaming interpretando text/event-stream quando você precisa de headers e cancelamento. É o que as principais APIs de modelos expõem, e compõe bem com serverless.
  • Trading e dados de mercado. WebSockets para fluxo de ordens; SSE é aceitável para tickers somente leitura. Fan-out via Redis ou Kafka em qualquer um dos casos.
  • Telemetria de IoT para o browser. MQTT sobre WebSockets a partir dos dispositivos, pub/sub no meio, SSE ou WebSockets até o dashboard dependendo de os operadores enviarem comandos ou não.
  • Voz ou vídeo. Nenhum dos dois. Use WebRTC, com WebSockets ou SSE apenas para sinalização.
  • Redes corporativas restritivas. SSE primeiro, long polling como último recurso.

WebTransport: a opção emergente

WebTransport é a API do browser sobre HTTP/3 e QUIC: múltiplos streams independentes sem head-of-line blocking, e datagramas não confiáveis para dados em que chegar atrasado é pior do que se perder. Para jogos, mídia e telemetria de alta frequência, ele remove os dois limites estruturais dos WebSockets: um único stream TCP e entrega apenas confiável.

Adoção é o problema. O suporte nos browsers é amplo no Chromium e vem melhorando nos outros, mas servidores, load balancers e CDNs ficam para trás, e redes hostis a UDP precisam de um caminho WebSocket de qualquer forma. Em 2026 ele se justifica para um conjunto estreito de produtos e é item de pesquisa para todo o resto. Abstraia o transporte para que ele possa mudar; não planeje a migração ainda.

Recomendação

Comece com SSE para tudo que é do servidor para o cliente, combinado com requisições HTTP normais na outra direção. Sirva sobre HTTP/2, desative o buffering do proxy, defina id: em todo evento e apoie tudo num log com replay para que o Last-Event-ID recupere o que foi perdido. Mova uma funcionalidade para WebSockets só quando ela precisar de tráfego bidirecional de alta frequência ou frames binários, e reserve orçamento para a lógica de reconexão, um handshake de auth, uma camada de pub/sub e sticky sessions ou estado externalizado. Mantenha o long polling como fallback atrás de uma flag, não como design. Na Arvucore, costumamos recomendar um transporte por funcionalidade, escolhido pelo checklist acima, em vez de um para o produto inteiro; os sistemas que envelhecem bem são aqueles em que a arquitetura é a camada de pub/sub, não o socket.

Pronto para Transformar seu Negócio?

Vamos conversar sobre como nossas soluções podem ajudá-lo a alcançar seus objetivos. Entre em contato com nossos especialistas hoje mesmo.

Falar com um Especialista

Tags:

websockets vs. ssecomunicação em tempo realnotificações pushserver-sent eventsdesvantagens do websocketlong polling
Equipe Arvucore

Equipe Arvucore

A equipe editorial da Arvucore é formada por profissionais experientes em desenvolvimento de software. Somos dedicados a produzir e manter conteúdo de alta qualidade que reflete as melhores práticas da indústria e insights confiáveis.

Perguntas frequentes

SSE é melhor que WebSockets?
Nenhum dos dois é melhor em geral. SSE é mais simples e serve bem a fluxos unidirecionais do servidor para o cliente, como notificações, dashboards e streaming de tokens de LLM. WebSockets são a escolha certa quando o cliente também precisa enviar mensagens frequentes com baixa latência, ou quando você precisa de frames binários.
Quais são as principais desvantagens dos WebSockets?
Sem reconexão ou retomada automática, sem headers customizados no handshake do browser, conexões com estado que exigem sticky sessions ou uma camada de pub/sub, encaixe ruim com serverless e com alguns proxies ou CDNs, e um protocolo separado que passa ao largo das ferramentas, do cache e da compressão padrão do HTTP.
SSE funciona sobre HTTP/2?
Sim. Sobre HTTP/2, cada stream SSE é um stream multiplexado em uma única conexão TCP, o que elimina o limite de seis conexões por origem que prejudica o SSE no HTTP/1.1. A maioria dos browsers e provedores de edge negocia HTTP/2 por padrão com TLS.
Dá para enviar headers customizados com EventSource ou WebSocket no browser?
Não. Nenhuma das duas APIs do browser permite definir um header Authorization. Use cookies, um token de curta duração na query string, ou substitua o EventSource por fetch com um ReadableStream, que aceita headers.
Long polling ainda é relevante em 2026?
Só como fallback para redes restritivas ou atualizações de frequência muito baixa. Funciona em qualquer lugar, mas desperdiça requisições, adiciona latência e é mais difícil de raciocinar do que o SSE, que todo browser moderno suporta.
WebTransport vai substituir os WebSockets?
Ainda não. WebTransport roda sobre HTTP/3 e QUIC e oferece datagramas não confiáveis e múltiplos streams sem head-of-line blocking, mas o suporte em browsers e infraestrutura ainda é irregular. Trate como opção para jogos e mídia, não como padrão.