---
term: 'GraphQL Subscriptions vs. WebSockets'
seoTitle: 'GraphQL Subscriptions vs. WebSockets: Protocolo vs. Transporte'
headline: 'GraphQL Subscriptions vs. WebSockets: ¿qué se está comparando en realidad?'
slug: graphql-subscriptions-vs-websockets
category: api-realtime
shortDefinition: 'Una GraphQL subscription es un stream de eventos tipado y definido por el schema; un WebSocket es el transporte crudo sobre el que suele viajar.'
relatedTerms:
  - graphql
  - websockets-real-time-sync
  - real-time-live-queries
  - sse-vs-websockets-vs-polling
contrastsWith:
  - websockets-real-time-sync
aboutTerms:
  - 'GraphQL Subscriptions'
  - 'WebSockets'
faq:
  - question: '¿Las GraphQL subscriptions son lo mismo que los WebSockets?'
    answer: 'No — viven en capas diferentes. Un WebSocket es transporte: una tubería de bytes persistente y full-duplex, sin opinión sobre lo que pasa por ella. Una GraphQL subscription es protocolo y contrato por encima: una operación definida en el schema cuyos eventos llegan tipados, validados y con la misma forma que cualquier otra respuesta GraphQL. Compararlas directamente es comparar una carretera con una línea de autobús.'
  - question: '¿Qué protocolo usan las GraphQL subscriptions?'
    answer: 'Lo más común es graphql-ws, el sub-protocolo moderno de GraphQL sobre WebSocket: el cliente abre el socket, envía connection_init, recibe connection_ack y luego inicia operaciones con mensajes subscribe; el servidor transmite payloads next por id de subscription y cualquiera de los dos lados cierra con complete. Un sub-protocolo más antiguo de los inicios del ecosistema todavía circula, y por eso cliente y servidor deben acordar cuál de los dos hablan.'
  - question: '¿Pueden las GraphQL subscriptions correr sobre Server-Sent Events?'
    answer: 'Sí — el contrato de subscription es agnóstico del transporte, y SSE es un portador legítimo cada vez más popular. Como el tráfico de subscriptions es abrumadoramente del servidor al cliente, un stream HTTP unidireccional encaja de forma natural, mantiene contentos a los proxies comunes y a la semántica HTTP, y trae reconexión automática gratis. Los WebSockets siguen siendo el default en la mayoría del tooling, pero "las subscriptions exigen WebSockets" es folclore, no un hecho.'
  - question: '¿Cuándo deberías usar WebSockets crudos en lugar de GraphQL subscriptions?'
    answer: 'Cuando el tráfico deja de parecer eventos de API tipados: frames binarios (audio, estado de juego, streams de sensores), tasas de mensajes muy altas donde validar el schema y envolver en JSON cada evento cuesta throughput real, o protocolos que necesitan semántica propia — cursores, deltas, acknowledgments — que pelean con la forma de la subscription. Si aún no invertiste en un schema GraphQL, el socket crudo también evita importar uno solo por los eventos.'
  - question: '¿Las GraphQL subscriptions escalan?'
    answer: 'El transporte escala como cualquier flota de WebSockets — estado de conexión, enrutamiento con afinidad y un backplane de pub/sub entre servidores. La capa de subscription agrega su propio eje: cada evento puede resolverse y filtrarse por suscriptor, así que un topic caliente con muchos suscriptores multiplica el trabajo de los resolvers. Las plataformas gestionadas absorben la flota; el diseño del schema y la disciplina de filtrado por suscriptor siguen siendo tuyos.'
  - question: '¿Las GraphQL subscriptions son lo mismo que las live queries?'
    answer: 'Primas cercanas con disparadores distintos. Las subscriptions se disparan con eventos nombrados que cableas explícitamente — una mutation publica a un topic, los suscriptores reciben. Las live queries se disparan cuando cambia el conjunto de resultados de una consulta — sin cableado de eventos, con cada ruta de escritura cubierta automáticamente. Ambas suelen viajar sobre WebSockets. Una capa de live query cambia el diseño de eventos schema-first por detección automática de cambios sobre los datos mismos.'
  - question: '¿Por qué las subscriptions necesitan un sub-protocolo?'
    answer: 'Porque un WebSocket pelado es solo bytes ordenados. En cuanto dos partes necesitan multiplexar varias subscriptions en un socket, correlacionar eventos con operaciones, negociar autenticación, señalizar errores y cerrar streams con limpieza, necesitan framing de mensajes y reglas — que cada equipo alguna vez inventó mal, una versión incompatible a la vez. graphql-ws estandariza exactamente esa capa para que clientes y servidores interoperen.'
codeLanguages: [javascript, dart, swift, kotlin]
externalAuthorities:
  - name: 'Subscriptions — graphql.org'
    url: 'https://graphql.org/learn/subscriptions/'
  - name: 'GraphQL over WebSocket Protocol (graphql-ws)'
    url: 'https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md'
  - name: 'RFC 6455 — The WebSocket Protocol'
    url: 'https://datatracker.ietf.org/doc/html/rfc6455'
  - name: 'The WebSocket API — MDN Web Docs'
    url: 'https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API'
  - name: 'GraphQL — Wikipedia'
    url: 'https://en.wikipedia.org/wiki/GraphQL'
  - name: 'WebSocket — Wikipedia'
    url: 'https://en.wikipedia.org/wiki/WebSocket'
cta:
  title: 'Tiempo real sin ser dueño de ninguna capa'
  text: 'Back4app combina una API GraphQL generada automáticamente con Live Queries — subscriptions tipadas y con permisos verificados sobre una flota gestionada de WebSockets. Define el schema, suscríbete a la consulta y sáltate la ingeniería de protocolos por completo.'
  linkText: 'Empieza gratis'
  linkUrl: 'https://www.back4app.com/signup'
author: 'Back4app Engineering'
publishedDate: '2026-09-04'
translationKey: graphql-subscriptions-vs-websockets
---

**Una GraphQL subscription es un stream de eventos tipado y definido por el schema; un WebSocket es el transporte crudo sobre el que suele viajar.** El "vs." del título es una confusión de capas que vale la pena deshacer antes de tomar cualquier decisión: las subscriptions no son una *alternativa* a los WebSockets — son una de las cosas que puedes correr *sobre* uno, igual que HTTP corre sobre TCP. La elección real es entre un protocolo tipado que alguien ya especificó y una tubería cruda cuyo protocolo inventas tú.

## Puntos clave

| Pregunta | Respuesta |
| --- | --- |
| WebSocket | Una tubería de bytes persistente y full-duplex — sin semántica de mensajes incluida |
| GraphQL subscription | Un stream de eventos definido por el schema, tipado y validado como cualquier respuesta GraphQL |
| Su relación | Capas, no rivales — las subscriptions viajan sobre WebSockets (o SSE) vía un sub-protocolo |
| El sub-protocolo | graphql-ws: handshake init/ack, ids que multiplexan operaciones, frames next/complete |
| La decisión real | Protocolo tipado de estante vs. socket crudo más un protocolo que ahora es tuyo |

## Las capas, en código

Lo que una subscription realmente es en el cable — una conversación [graphql-ws](https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md) dentro de un WebSocket:

```json
// cliente → servidor, después de que el socket abre
{ "type": "connection_init", "payload": { "authToken": "…" } }
// servidor → cliente
{ "type": "connection_ack" }
// el cliente inicia una operación — el id multiplexa esta subscription
{ "type": "subscribe", "id": "1", "payload": {
    "query": "subscription { orderUpdated(status: PREPARING) { id status eta } }" } }
// el servidor transmite eventos tipados, un frame por ocurrencia
{ "type": "next", "id": "1", "payload": { "data": { "orderUpdated": { "id": "o42", "status": "READY", "eta": null } } } }
// cualquiera de los dos lados cierra el stream
{ "type": "complete", "id": "1" }
```

Y lo que la mayor parte del código de aplicación escribe en realidad — una subscription tipada con toda la pila gestionada:

**JavaScript:**

```javascript
// JavaScript — Back4app JS SDK
// A typed subscription over a managed WebSocket fleet: the protocol,
// reconnects, and fan-out are the platform's problem, not yours
const orders = new Parse.Query('Order');
orders.equalTo('status', 'preparing');

const sub = await orders.subscribe();
sub.on('create', (o) => addCard(o));      // typed event, full object
sub.on('update', (o) => refreshCard(o));
sub.on('leave',  (o) => removeCard(o));   // edited out of the result set

sub.on('close', () => showOfflineBadge()); // socket lifecycle surfaced
```

**Flutter:**

```dart
// Flutter / Dart — Back4app Flutter SDK
// A typed subscription over a managed WebSocket fleet: the protocol,
// reconnects, and fan-out are the platform's problem, not yours
final liveQuery = LiveQuery();
final orders = QueryBuilder<ParseObject>(ParseObject('Order'))
  ..whereEqualTo('status', 'preparing');

final sub = await liveQuery.client.subscribe(orders);
sub.on(LiveQueryEvent.create, (o) => addCard(o));    // typed event
sub.on(LiveQueryEvent.update, (o) => refreshCard(o));
sub.on(LiveQueryEvent.leave, (o) => removeCard(o));  // left the set
```

**Swift:**

```swift
// iOS / Swift — Back4app Swift SDK
// A typed subscription over a managed WebSocket fleet: the protocol,
// reconnects, and fan-out are the platform's problem, not yours
let orders = Order.query("status" == "preparing")

let subscription = orders.subscribeCallback
subscription?.handleEvent { _, event in
  switch event {
  case .created(let o): addCard(o)       // typed event, full object
  case .updated(let o): refreshCard(o)
  case .left(let o):    removeCard(o)    // edited out of the result set
  default:              break
  }
}
```

**Kotlin:**

```kotlin
// Android / Kotlin — Back4app Android SDK
// A typed subscription over a managed WebSocket fleet: the protocol,
// reconnects, and fan-out are the platform's problem, not yours
val client = ParseLiveQueryClient.Factory.getClient()
val orders = ParseQuery.getQuery<ParseObject>("Order")
orders.whereEqualTo("status", "preparing")

val sub = client.subscribe(orders)
sub.handleEvent(SubscriptionHandling.Event.CREATE) { _, o -> addCard(o) }
sub.handleEvent(SubscriptionHandling.Event.UPDATE) { _, o -> refreshCard(o) }
sub.handleEvent(SubscriptionHandling.Event.LEAVE)  { _, o -> removeCard(o) }
```

## Una pila, tres capas

```mermaid
flowchart TB
  accTitle: GraphQL subscriptions en capas sobre un transporte WebSocket
  accDescr: El código de aplicación consume eventos tipados. Debajo, un protocolo de subscription como graphql-ws maneja el handshake, la multiplexación y el ciclo de vida del stream. Debajo de eso, la capa de transporte suele ser un WebSocket y a veces Server-Sent Events. El transporte mueve bytes; el protocolo les da significado; el schema les da tipos.
  A["Código de aplicación<br/>eventos tipados, payloads con la forma del schema"] --> P["Protocolo de subscription — graphql-ws<br/>connection_init/ack · subscribe · next · complete"]
  P --> T["Transporte — normalmente WebSocket, a veces SSE<br/>conexión persistente, entrega ordenada"]
  T --> N["TCP/IP"]
```

La capa [WebSocket](/glossary/es/websockets/) ([RFC 6455](https://datatracker.ietf.org/doc/html/rfc6455)) promete exactamente esto: un stream persistente, full-duplex y ordenado de frames de texto o binarios, alcanzable desde cualquier navegador mediante [una API pequeña](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API). No dice nada sobre lo que un mensaje *significa* — sin correlación solicitud/respuesta, sin convención de autenticación, sin señalización de errores, sin manera de correr dos streams lógicos por un socket. Cada proyecto de socket crudo vuelve a decidir todo eso.

La [capa de subscription](https://graphql.org/learn/subscriptions/) es precisamente ese conjunto de decisiones faltantes, estandarizado: `connection_init`/`connection_ack` lleva la autenticación; los `id` por operación multiplexan muchas subscriptions en un socket; los frames `next` entregan payloads que son respuestas GraphQL ordinarias — tipadas por el schema, validadas, introspectables, consumidas por la misma maquinaria de cliente que las queries y las mutations; `complete` cierra un stream sin matar a sus vecinos. Una nota histórica importa en la práctica: un sub-protocolo más antiguo de los inicios del ecosistema sigue desplegado, y un cliente que habla uno con un servidor que habla el otro falla de formas confusamente silenciosas — fija el sub-protocolo explícitamente en ambos extremos.

Y como el contrato es de mensajes, no de sockets, el transporte de abajo es intercambiable — la misma semántica de subscription corre cada vez más sobre Server-Sent Events, que encaja con la forma abrumadoramente unidireccional del tráfico de subscriptions y hereda [la tolerancia a proxies y la reconexión automática de SSE](/glossary/es/sse-vs-websockets-vs-polling/). "Subscriptions vs. WebSockets" se disuelve al tocarlo: una es contrato, el otro es portador.

## GraphQL subscriptions vs. WebSockets crudos

| | GraphQL subscriptions | WebSockets crudos |
| --- | --- | --- |
| Capa | Protocolo + sistema de tipos sobre un transporte | El transporte en sí |
| Contrato de mensajes | Definido por el schema, validado, introspectable | Lo que inventes y documentes |
| Multiplexación | Incluida — ids por operación en un socket | Tuya de diseñar |
| Handshake de autenticación | Estandarizado (payload de `connection_init`) | Tuyo de diseñar |
| Payloads | JSON, con la forma del schema | Texto **y binario**, cualquier formato |
| Filtrado | Argumentos en el campo de la subscription | Código de servidor que escribes tú |
| Overhead por evento | Envelope JSON + ejecución de resolver | ~2–14 bytes de framing |
| Ecosistema | Clientes GraphQL, codegen, tooling | Bibliotecas de socket pelado |
| Mejor cuando | Los eventos son datos de API tipados en una app GraphQL | Binario, alta frecuencia o semántica propia |

## Cuándo los sockets crudos les ganan a las subscriptions tipadas

Los casos honestos existen, solo que son más estrechos de lo que sugiere el entusiasmo por el socket crudo. **Payloads binarios** — trozos de audio, protocol buffers, estado de juego — viajan de forma nativa en frames WebSocket, pero necesitarían codificarse dentro de un envelope JSON de subscription. **Tasa de mensajes** — a miles de eventos por segundo por cliente, la ejecución de resolvers y el envelope JSON por evento dejan de ser ruido; un formato de frame compacto y propio es una optimización legítima. **Semántica propia** — backpressure, acks del cliente, codificación de deltas, cursores reanudables — pertenece a protocolos que diseñas tú, y atornillarla a los frames de subscription pelea con la spec. Y **no tener GraphQL para empezar** — adoptar schema, resolvers y tooling de cliente *solo* para tener eventos tipados es la cola moviendo al perro; un socket crudo con un formato de mensajes documentado es más pequeño. La trampa corre también en sentido contrario: los equipos que eligen sockets crudos para eventos de API tipados con forma JSON terminan escribiendo a mano multiplexación, handshakes de autenticación y semántica de reconexión — un graphql-ws peor, un equipo incompatible a la vez.

## Casos de uso comunes

- **Streams de pedidos y estados** — "avísame cuando este pedido cambie": datos de API tipados, tasa baja, el punto dulce de las subscriptions.
- **Presencia y comentarios colaborativos** — subscriptions en apps GraphQL que ya son dueñas del schema; los eventos son solo más schema.
- **Cotizaciones financieras y dashboards** — subscriptions mientras los payloads sigan con forma JSON; sockets crudos cuando la tasa de ticks exige frames compactos.
- **Chat** — cualquiera de las dos capas funciona; el voto decisivo suele ser si la app es GraphQL-first, ya que las [live queries](/glossary/es/live-queries-tiempo-real/) cubren el mismo terreno sin cableado de eventos.
- **Estado multijugador y media** — binario, alta frecuencia, crítico en latencia: territorio de WebSocket crudo, con protocolo y todo.

## ¿Deberías usar GraphQL subscriptions o WebSockets crudos? Matriz de decisión

| Tu situación | Usa |
| --- | --- |
| La app ya habla [GraphQL](/glossary/es/graphql/); los eventos son datos de API tipados | GraphQL subscriptions |
| Frames binarios, o miles de eventos/s por cliente | WebSockets crudos |
| Necesitas semántica propia — acks, deltas, cursores, backpressure | WebSockets crudos, con el protocolo documentado |
| Sin inversión en GraphQL, feed de eventos simple | Socket crudo con un pequeño protocolo de frames — o SSE |
| Los eventos son "los resultados de esta consulta cambiaron" | Una capa de live query — sin cableado de eventos alguno |
| Proxies estrictos, infraestructura solo HTTP | Subscriptions sobre SSE |
| Equipo pequeño, sin apetito por ser dueño de un protocolo | Subscriptions tipadas en un backend gestionado |

## Limitaciones y trade-offs

- **Las subscriptions heredan la operación de los WebSockets.** Apilar capas agrega significado, no magia: el estado de conexión, el enrutamiento con afinidad, los heartbeats y un backplane de pub/sub entre servidores siguen siendo la realidad de despliegue por debajo.
- **La reconexión sigue perdiendo eventos.** graphql-ws define streams, no reanudación: un socket caído significa frames perdidos, y ponerse al día (reconsultar y luego resuscribirse) es lógica de aplicación en cualquiera de las dos pilas.
- **La resolución por suscriptor cuesta.** Las subscriptions filtradas pueden ejecutar trabajo de resolvers y de permisos por evento y por suscriptor — un topic caliente con miles de oyentes lo multiplica; diseña los filtros del lado del servidor y estrechos.
- **Circulan dos sub-protocolos.** Los protocolos legado y moderno de GraphQL sobre WebSocket son mutuamente ininteligibles; los extremos desparejados fallan en silencio. Fija las versiones explícitamente.
- **Los envelopes tipados cobran throughput.** La serialización JSON y la validación de schema por evento son invisibles a decenas de eventos por segundo y dominantes a miles — mide antes de asumir en cualquier dirección.

## GraphQL subscriptions y WebSockets en Back4app

Back4app es una plataforma open-source de Backend as a Service (BaaS) que combina base de datos gestionada, APIs REST y GraphQL generadas automáticamente, autenticación, almacenamiento de archivos y funciones serverless con Cloud Code. El apilamiento de este artículo se mapea directo sobre la plataforma: la API GraphQL generada automáticamente cubre las capas de query y mutation a partir de tu schema sin una línea de resolvers, mientras que la capa de tiempo real llega como [Live Queries](/glossary/es/live-queries-tiempo-real/) — eventos de subscription tipados y verificados por ACL sobre una flota gestionada de WebSockets, usando el protocolo abierto LiveQuery en lugar de graphql-ws y disparándose por cambios en el conjunto de resultados en vez de eventos cableados a mano. Te quedas con la columna del protocolo tipado de la tabla comparativa — multiplexación, autenticación y manejo de reconexión incluidos — sin operar la flota de sockets ni ser dueño de una especificación de protocolo.
