WebSocket async/await con AWS Lambda: cómo hacerlo
Si alguna vez construiste una app de chat con AWS WebSocket API Gateway y Lambda, sabés que el modelo fire-and-forget funciona perfectamente… hasta que necesitás una respuesta del servidor. El patrón WebSocket async await con AWS Lambda resuelve exactamente eso: enviar un mensaje y esperar la respuesta como si fuera una Promise, sin abrir una segunda conexión HTTP.
En 30 segundos
- AWS WebSocket API Gateway + Lambda maneja conexiones persistentes, pero por defecto no tiene mecanismo nativo para solicitud-respuesta (solo fire-and-forget).
- El patrón async/await se implementa con un
requestIdúnico: el cliente lo envía, Lambda lo devuelve, el cliente resuelve la Promise. - Las promesas sin respuesta se rechazan automáticamente después de ~30 segundos para evitar leaks de memoria.
- La librería
@tricksumo/ws-awaitencapsula todo esto con reconexión automática y heartbeat integrado. - Los límites críticos a no ignorar: 10 minutos de inactividad = desconexión, 29 segundos de timeout de integración, 2 horas máximo de conexión (sin posibilidad de extender).
Qué son WebSockets y por qué necesitás async/await
Una conexión WebSocket es una conexión TCP bidireccional persistente entre cliente y servidor. A diferencia de HTTP donde cada request-response es independiente, WebSocket mantiene el canal abierto y permite que cualquiera de los dos lados envíe mensajes en cualquier momento.
El problema es que el modelo natural de WebSocket es fire-and-forget: mandás un mensaje al servidor y seguís de largo. El servidor puede responderte, pero no hay ningún mecanismo incorporado que vincule esa respuesta con el mensaje que la originó. Es como mandar un telegram y no saber si la respuesta que llegó mañana era para este mensaje o para otro.
Para apps de chat, eso zafa. Llega un mensaje, lo mostrás en pantalla, listo. Pero ponele que el usuario quiere subir un archivo y necesitás pedirle a Lambda una S3 signed URL. Ahí necesitás la respuesta específica a ese request específico, y necesitás esperar a tenerla antes de continuar. La alternativa “fácil” sería crear un HTTP API Gateway separado, con su propio authorizer, su propia lógica de conexión… básicamente duplicar trabajo. Según la historia de origen de la librería, exactamente ese fue el problema que motivó construir la solución.
Arquitectura: API Gateway WebSocket + Lambda + DynamoDB
La arquitectura base es bastante estándar. API Gateway WebSocket actúa como el frontal que acepta conexiones entrantes y las enruta. Cada cliente que se conecta recibe un connectionId único que API Gateway asigna automáticamente.
Lambda procesa los eventos de cada conexión. Hay tres rutas predefinidas que vienen out-of-the-box: $connect (cuando un cliente se conecta), $disconnect (cuando se va) y $default (para cualquier mensaje que no matchee una ruta personalizada). Después podés agregar rutas propias como sendMessage o addGroup.
DynamoDB entra para persistir los connectionId. En el handler de $connect, guardás el connectionId en DynamoDB. En $disconnect, lo eliminás. Cuando Lambda quiere enviar algo de vuelta al cliente, usa la API Management de API Gateway con ese connectionId. Sin DynamoDB (o similar), no tenés forma de saber a qué conexión responderle. Esto se conecta con lo que analizamos en optimización SEO en aplicaciones reales.
El patrón async/await: requestId y promesas pendientes
Acá viene el núcleo de todo. El mecanismo es conceptualmente simple pero hay que implementarlo bien.
El cliente genera un requestId único (puede ser un UUID v4) y lo incluye en el payload del mensaje. Después crea una Promise y la guarda en un Map con ese requestId como clave. La Lambda recibe el mensaje, lo procesa, y al armar la respuesta incluye el mismo requestId en el payload de vuelta. Del lado del cliente, el handler de mensajes entrantes revisa si el mensaje trae requestId. Si encuentra ese requestId en el Map de promesas pendientes, resuelve esa promesa con el payload recibido.
¿Y qué pasa si el servidor nunca responde? Las promesas que llevan esperando más de ~30 segundos se rechazan automáticamente. Ese timeout es tuyo para configurar, pero 30 segundos es el estándar razonable considerando que API Gateway tiene un límite de integración de 29 segundos de todas formas.
El resultado desde el código cliente se ve así de limpio: mandás un request y esperás la respuesta con await, como si fuera una llamada HTTP normal. La complejidad del WebSocket queda encapsulada.
WebSocket async await con la librería ws-await
Toda esa lógica ya está empaquetada en @tricksumo/ws-await. La instalación es straightforward:
npm install @tricksumo/ws-await zustand
La librería expone tres capacidades principales. Primero, createSocket() para establecer la conexión y configurar la URL del WebSocket. Segundo, ws.send() para los mensajes fire-and-forget de siempre. Tercero, y esto es lo que importa, ws.request() para el patrón async/await: enviás el payload y recibís una Promise que se resuelve cuando llega la respuesta con el requestId correspondiente. Cubrimos ese tema en detalle en garantizar confiabilidad en producción.
El hook useSocket expone los estados de conexión que necesitás en la UI: isConnected, isConnecting y error. Sin tener que manejar el estado de WebSocket a mano, que es un dolor de cabeza clásico.
El ejemplo más directo es el caso de S3 signed URLs. El usuario selecciona un archivo, el frontend hace const result = await ws.request({ action: 'getSignedUrl', fileName: file.name }), Lambda genera la URL con el SDK de S3, la devuelve con el mismo requestId, y el cliente usa esa URL para el upload directo a S3. Todo por la misma conexión WebSocket que ya tenías abierta. (Sí, es tan limpio como parece.)
Heartbeat, reconexión y los límites que no podés ignorar
Hay tres límites de AWS que tenés que tener tatuados si trabajás con WebSocket API Gateway:
- 10 minutos de inactividad: si no hay tráfico en 10 minutos, la conexión se cierra automáticamente.
- 29 segundos de timeout de integración: el tiempo máximo que Lambda puede tardar en responder a un mensaje. Más que eso y API Gateway corta la conexión.
- 2 horas de conexión máxima: es un límite duro que AWS impone y no se puede configurar para aumentarlo.
El heartbeat resuelve el primero: la librería envía mensajes periódicos automáticos para mantener la conexión activa cuando no hay tráfico real. Es básicamente un ping que le dice a AWS “acá seguimos”.
La reconexión con exponential backoff maneja los cortes inesperados. Si la conexión se cae, el cliente reintenta en 3 segundos, luego 6, luego 12, luego 24. No spamea el servidor con reconexiones y da tiempo para que el problema se resuelva. Las promesas pendientes en el Map también se rechazan cuando se detecta una desconexión, así no quedás con Promises zombies esperando eternamente.
El límite de 2 horas es el más tricky. Para apps que requieren conexiones largas (una sesión de trabajo de toda la mañana, por ejemplo), necesitás implementar reconexión proactiva antes de llegar a las 2 horas. No hay vuelta: AWS no te da más. Ya lo cubrimos antes en desplegar fácilmente con Docker.
Casos de uso: más allá del chat
El caso de S3 signed URLs es el que motivó la librería, y es instructivo porque muestra bien el patrón. El flujo completo: usuario selecciona archivo en el browser, el frontend hace un ws.request() pidiendo la URL firmada, Lambda genera la signed URL con permisos correctos y la devuelve con el requestId, el cliente resuelve la Promise y usa esa URL para el PUT directo a S3. Todo sin abrir ninguna conexión HTTP adicional.
Otros escenarios donde el patrón aplica bien: datos de mercado en tiempo real donde necesitás confirmar que el servidor recibió una orden, aplicaciones colaborativas donde un usuario hace una acción y necesita confirmación antes de actualizar su estado local, y cualquier flujo donde tenés validación server-side antes de continuar.
Para proyectos que necesitan infraestructura serverless robusta en Argentina, donweb.com tiene opciones de hosting que pueden complementar la capa de frontend mientras AWS maneja la lógica WebSocket.
Tabla comparativa: WebSocket vs HTTP API para solicitudes con respuesta
| Aspecto | WebSocket + async/await | HTTP API Gateway separado |
|---|---|---|
| Conexiones | 1 conexión persistente | 1 por WebSocket + 1 HTTP nueva |
| Latencia promedio | Baja (canal abierto) | Mayor (nueva conexión TCP) |
| Complejidad de setup | Media (requestId + Map) | Alta (auth duplicado, CORS, etc.) |
| Timeout máximo | 29 seg (integración AWS) | 29 seg (integración AWS) |
| Costo AWS | Por mensaje enviado | Por request HTTP |
| Estado de conexión | Un solo estado a manejar | Dos estados independientes |
| Autenticación | Una vez en $connect | En cada request HTTP |

Errores comunes al implementar este patrón
Lambda no devuelve el requestId en la respuesta
Es el error más frecuente y el más difícil de debuggear porque todo parece funcionar pero las promesas nunca se resuelven. Lambda recibe el requestId en el payload, procesa la lógica, genera la respuesta… y se olvida de incluir el requestId en lo que devuelve. El cliente busca ese ID en el Map, no lo encuentra, y la promesa queda pendiente hasta que el timeout de 30 segundos la rechaza. Revisá siempre que el handler de Lambda propagua el requestId al payload de respuesta.
No implementar heartbeat y culpar a AWS del timeout
Los 10 minutos de inactividad de API Gateway no son un bug ni algo configurable: son el comportamiento documentado. Si tu app tiene períodos de silencio (el usuario está leyendo, pensando, o simplemente la tiene abierta), la conexión se cae. La solución es heartbeat, no abrir un issue en AWS.
Ignorar el límite de 2 horas para sesiones largas
Apps de productividad, dashboards de monitoreo, herramientas de trabajo colaborativo: todas pueden tener usuarios con sesiones de más de 2 horas. Si no implementás reconexión proactiva antes de llegar a ese límite, la conexión se corta en el medio de una operación y tenés un estado inconsistente. La recomendación es reconectar alrededor de la hora 1:45 en segundo plano, sin interrumpir al usuario.
No limpiar promesas rechazadas del Map
Si una promesa se rechaza por timeout, el Map tiene que eliminarla. Si no, acumulás entradas que ya no sirven para nada y eventualmente tenés un leak de memoria. El cleanup debe ocurrir tanto en el resolve exitoso como en el reject por timeout. Relacionado: automatizar procesos sin código.
DynamoDB con connectionIds obsoletos
¿Alguien lo verificó? Rara vez. El handler de $disconnect tiene que limpiar el connectionId de DynamoDB. Si no lo hace, la tabla acumula IDs de conexiones ya cerradas y cualquier intento de enviar mensajes a esas conexiones falla con error de API Gateway. Es un error silencioso que aparece en CloudWatch pero no explota en la cara del usuario.
Preguntas Frecuentes
¿Cómo implementar WebSocket con async/await en AWS API Gateway?
El cliente envía cada mensaje con un requestId único y guarda una Promise en un Map con ese ID. Lambda lee el requestId del payload, procesa la lógica y devuelve la respuesta incluyendo el mismo requestId. El handler de mensajes del cliente resuelve la Promise correspondiente cuando recibe la respuesta. La librería @tricksumo/ws-await encapsula todo este mecanismo con ws.request().
¿Cuál es el timeout de una conexión WebSocket en API Gateway?
Hay dos timeouts distintos. El timeout de integración es de 29 segundos: Lambda tiene ese tiempo para procesar y responder antes de que API Gateway corte. El timeout de inactividad es de 10 minutos: si no hay mensajes en ese período, la conexión se cierra. El límite absoluto es 2 horas de conexión continua, independientemente de la actividad. Ninguno de estos valores se puede aumentar.
¿Cómo subir archivos a S3 usando la conexión WebSocket existente?
El cliente solicita una S3 signed URL a través de ws.request() con los datos del archivo. Lambda genera la URL firmada usando el SDK de S3 (getSignedUrl para PUT) y la devuelve en la respuesta WebSocket con el requestId. El cliente recibe la URL, resuelve la Promise y hace el PUT directo a S3 desde el browser. La ventaja es que todo ocurre por la conexión WebSocket ya autenticada, sin abrir canales adicionales.
¿Cómo funciona la reconexión automática con exponential backoff?
Cuando la conexión se cae, el cliente espera 3 segundos antes del primer reintento. Si falla, espera 6 segundos, luego 12, luego 24. Este patrón evita saturar el servidor con reconexiones simultáneas (especialmente en desconexiones masivas) y le da tiempo al problema de resolverse. El manejo correcto del backoff también incluye rechazar las promesas pendientes cuando se detecta la desconexión.
¿Qué herramientas usar para debuggear problemas en WebSocket API Gateway?
CloudWatch Logs es el punto de partida: cada Lambda tiene su log group donde aparecen los eventos de conexión, los payloads procesados y los errores. Para la parte de API Gateway, activá los logs de ejecución desde la consola de AWS. Las métricas de API Gateway también muestran el conteo de conexiones activas, mensajes enviados y errores de integración. Si las promesas no se resuelven, buscá en CloudWatch si Lambda está recibiendo el requestId y si lo está devolviendo en la respuesta.
Conclusión
El patrón WebSocket async/await con AWS Lambda resuelve un gap real: tener comunicación bidireccional y solicitudes con respuesta por la misma conexión persistente, sin duplicar infraestructura. El mecanismo de requestId + Map de promesas es simple de entender pero hay varios detalles operativos donde se puede ir todo al diablo: el requestId que Lambda no propaga, el heartbeat que falta, las promesas que nunca se limpian.
Para proyectos nuevos, la librería @tricksumo/ws-await es un punto de partida sólido que ya tiene resueltos los casos edge más comunes. Para proyectos existentes, el patrón se puede incorporar gradualmente: empezás usando ws.request() solo donde lo necesitás (como en la carga de archivos) y mantenés el fire-and-forget donde alcanza.
El límite de 2 horas de conexión es el único que no tiene workaround real: si tu caso de uso lo requiere, tenés que diseñar la reconexión proactiva desde el día cero, no como un agregado posterior.
Fuentes
- dev.to — Turn WebSockets into Async/Await Requests: historia de origen y documentación de la librería
- AWS Documentation — WebSocket API Gateway: referencia oficial de rutas, límites y configuración
- HatchSoftware — Cómo superar los timeouts de API Gateway con WebSocket
- Velotio Engineering — Building a WebSocket service with AWS Lambda y DynamoDB






