AWS API Gateway WebSocket APIs: Real-Time Communication Guide (2026)

WebSocket APIs on AWS API Gateway let you build persistent, bidirectional communication channels between clients and your backend — without polling, without long-polling hacks, and without managing your own WebSocket servers. Whether you are building a live chat application, a real-time dashboard, a collaborative editing tool, or a live trading feed, AWS API Gateway WebSocket APIs combined with AWS Lambda and DynamoDB give you a fully serverless, infinitely scalable real-time backend that costs nothing when idle.

This guide covers everything you need to know in 2026: how WebSocket APIs compare to REST and HTTP APIs, how to create and configure them, how to manage connections with DynamoDB, how to push messages back to clients, how to secure your endpoints, and how to monitor and control costs.

1. WebSocket API vs REST API vs HTTP API

AWS API Gateway offers three distinct API types, and choosing the right one is critical before you start building. Each type is optimized for a different communication pattern and comes with different pricing, features, and trade-offs.

REST APIs (also called API Gateway V1) are request-response APIs. The client sends an HTTP request, the server responds, and the connection closes. They are ideal for CRUD operations, data retrieval, and any workflow where the client initiates every interaction. REST APIs in API Gateway support the full feature set: usage plans, API keys, request validation, caching, and AWS WAF integration. They are more expensive than HTTP APIs but offer the richest configuration options.

HTTP APIs (API Gateway V2) are a streamlined, lower-cost alternative to REST APIs for simple proxy scenarios. They support JWT authorizers, Lambda proxy integrations, and CORS out of the box. They are up to 71% cheaper than REST APIs and have lower latency. Use them when you do not need the advanced features of REST APIs.

WebSocket APIs (also API Gateway V2) are fundamentally different. Instead of closing after each request, the connection remains open. Either side — client or server — can send messages at any time. This is the publish-subscribe model at the transport layer. WebSocket APIs are the right choice when:

  • You need server-initiated pushes (e.g., stock price updates, live notifications)
  • You need low-latency bidirectional communication (e.g., chat, collaborative whiteboards)
  • You want to avoid the overhead of repeated HTTP handshakes (e.g., IoT sensor streams)
  • Your clients need to receive events that occur asynchronously on the backend
Key insight: REST and HTTP APIs are client-driven (pull). WebSocket APIs are event-driven (push). If your backend ever needs to send data to a client without the client asking first, you need WebSocket or a workaround like Server-Sent Events or polling — and WebSocket is far more efficient at scale.

A WebSocket connection in API Gateway has a lifecycle: it is established via a standard HTTP Upgrade request, assigned a unique connectionId, and held open until either party closes it or it times out. API Gateway manages all the low-level TCP/WebSocket protocol work. Your Lambda functions just handle the messages.

2. Creating a WebSocket API in API Gateway

You can create a WebSocket API through the AWS Console, the AWS CLI, AWS CDK, or CloudFormation. Let us walk through both the Console approach and the CLI approach so you understand every moving part.

Using the AWS Console

Navigate to API Gateway in the AWS Console and click Create API. Under Choose an API type, select WebSocket. Give your API a name — for example, realtime-chat-api. In the Route selection expression field, enter $request.body.action. This expression tells API Gateway which field in each incoming JSON message determines the route. Click Create API.

After creation, you will see three default routes already defined: $connect, $disconnect, and $default. You can add custom routes (like sendMessage or joinRoom) by clicking Add Route.

Using the AWS CLI

The same API can be created entirely from the command line, which is better for CI/CD pipelines:

# Step 1: Create the WebSocket API
aws apigatewayv2 create-api \
  --name realtime-chat-api \
  --protocol-type WEBSOCKET \
  --route-selection-expression '$request.body.action' \
  --region us-east-1

# Step 2: Note the ApiId from the output, e.g. abc123def4
API_ID=abc123def4

# Step 3: Create a Lambda integration
aws apigatewayv2 create-integration \
  --api-id $API_ID \
  --integration-type AWS_PROXY \
  --integration-uri arn:aws:lambda:us-east-1:123456789012:function:WebSocketHandler \
  --integration-method POST

# Step 4: Create the $connect route
aws apigatewayv2 create-route \
  --api-id $API_ID \
  --route-key '$connect' \
  --target 'integrations/INTEGRATION_ID'

# Step 5: Deploy to a stage
aws apigatewayv2 create-stage \
  --api-id $API_ID \
  --stage-name production \
  --auto-deploy

# Step 6: Your WebSocket URL is:
# wss://abc123def4.execute-api.us-east-1.amazonaws.com/production
Tip: The WebSocket endpoint URL format is wss://<api-id>.execute-api.<region>.amazonaws.com/<stage>. Clients connect to this URL using standard WebSocket clients in any language or browser.

You must grant API Gateway permission to invoke your Lambda function. Add a resource-based policy to your Lambda:

aws lambda add-permission \
  --function-name WebSocketHandler \
  --statement-id AllowAPIGatewayWebSocket \
  --action lambda:InvokeFunction \
  --principal apigateway.amazonaws.com \
  --source-arn "arn:aws:execute-api:us-east-1:123456789012:abc123def4/*"

3. Route Keys: $connect, $disconnect, $default, and Custom Routes

Route keys are the core routing mechanism of WebSocket APIs. They determine which Lambda function (or other integration) handles each type of message. Understanding them deeply is essential to architecting a clean WebSocket backend.

$connect

The $connect route is invoked when a client first establishes a WebSocket connection. This is your opportunity to authenticate the user, validate query string parameters, and store the connectionId in a database. If your $connect Lambda returns a non-2xx HTTP status code, the connection is rejected and the client receives a connection error. The actual WebSocket upgrade only completes after $connect returns successfully.

Query string parameters passed during the initial HTTP Upgrade request are available to your $connect handler in event['queryStringParameters']. This is the standard pattern for passing authentication tokens.

$disconnect

The $disconnect route fires when the connection closes — whether the client closed it intentionally, the server closed it, or it timed out. Use this handler to remove the connectionId from your database. Note that $disconnect is best-effort: API Gateway will try to invoke it, but your Lambda cannot prevent the disconnection from happening.

$default

The $default route catches any message that does not match a more specific route key. It is your fallback handler. If your route selection expression evaluates to a value for which no route exists, $default handles it. For simple applications with a single message type, you can route everything through $default.

Custom Routes

Custom routes let you build a clean message-based API. If your route selection expression is $request.body.action, a client can send:

// Client sends this JSON over the WebSocket:
{"action": "sendMessage", "roomId": "room-42", "text": "Hello everyone!"}

// API Gateway evaluates $request.body.action = "sendMessage"
// and routes to your sendMessage Lambda integration

You can define as many custom routes as you need: sendMessage, joinRoom, leaveRoom, typing, subscribe, etc. Each maps to its own Lambda function or a shared function that branches internally. This gives you a clean RPC-style API over a single persistent connection.

Best practice: Always define a $default route even if you have specific custom routes. Without it, unmatched messages produce an error visible to the client. Your $default handler can log unknown actions for debugging or return a helpful error message.

4. Lambda Handler for WebSocket Connections

Your Lambda function receives a standard event object with WebSocket-specific fields. The structure is similar to API Gateway proxy events but with a requestContext.routeKey field identifying which route triggered the invocation. You can use a single Lambda function for all routes and branch on routeKey, or separate functions per route for cleaner isolation.

Here is a complete Python handler that manages all three system routes plus a custom sendMessage route:

import json
import boto3
import os
from boto3.dynamodb.conditions import Key

dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table(os.environ['CONNECTIONS_TABLE'])

def lambda_handler(event, context):
    route_key = event['requestContext']['routeKey']
    connection_id = event['requestContext']['connectionId']
    domain = event['requestContext']['domainName']
    stage = event['requestContext']['stage']

    if route_key == '$connect':
        return handle_connect(event, connection_id)
    elif route_key == '$disconnect':
        return handle_disconnect(connection_id)
    elif route_key == 'sendMessage':
        return handle_send_message(event, connection_id, domain, stage)
    else:
        return handle_default(event, connection_id, domain, stage)


def handle_connect(event, connection_id):
    """Store connection in DynamoDB. Reject if auth fails."""
    query_params = event.get('queryStringParameters') or {}
    token = query_params.get('token', '')

    # Validate token (replace with real auth logic)
    if not is_valid_token(token):
        return {'statusCode': 401, 'body': 'Unauthorized'}

    # Extract user info from token
    user_id = get_user_from_token(token)

    table.put_item(Item={
        'connectionId': connection_id,
        'userId': user_id,
        'connectedAt': event['requestContext']['connectedAt']
    })

    print(f"Connected: {connection_id} for user {user_id}")
    return {'statusCode': 200, 'body': 'Connected'}


def handle_disconnect(connection_id):
    """Remove connection from DynamoDB."""
    table.delete_item(Key={'connectionId': connection_id})
    print(f"Disconnected: {connection_id}")
    return {'statusCode': 200, 'body': 'Disconnected'}


def handle_send_message(event, sender_id, domain, stage):
    """Broadcast a message to all connected clients."""
    body = json.loads(event.get('body', '{}'))
    message_text = body.get('text', '')

    # Get all active connections
    response = table.scan(ProjectionExpression='connectionId')
    connections = response.get('Items', [])

    # Send to each connection
    apigw = boto3.client(
        'apigatewaymanagementapi',
        endpoint_url=f'https://{domain}/{stage}'
    )

    payload = json.dumps({
        'type': 'message',
        'from': sender_id,
        'text': message_text
    })

    stale_connections = []
    for conn in connections:
        cid = conn['connectionId']
        try:
            apigw.post_to_connection(ConnectionId=cid, Data=payload)
        except apigw.exceptions.GoneException:
            stale_connections.append(cid)

    # Clean up stale connections
    for cid in stale_connections:
        table.delete_item(Key={'connectionId': cid})

    return {'statusCode': 200, 'body': 'Message sent'}


def handle_default(event, connection_id, domain, stage):
    """Return an error for unknown actions."""
    apigw = boto3.client(
        'apigatewaymanagementapi',
        endpoint_url=f'https://{domain}/{stage}'
    )
    error_payload = json.dumps({'error': 'Unknown action'})
    try:
        apigw.post_to_connection(ConnectionId=connection_id, Data=error_payload)
    except Exception:
        pass
    return {'statusCode': 400, 'body': 'Unknown action'}


def is_valid_token(token):
    # Implement JWT validation or Cognito token check here
    return bool(token and len(token) > 10)


def get_user_from_token(token):
    # Decode JWT or look up session token
    return f"user-{token[:8]}"

The Lambda execution role needs permissions for dynamodb:PutItem, dynamodb:DeleteItem, dynamodb:Scan, and execute-api:ManageConnections on the API Gateway management endpoint. See our AWS IAM roles and policies guide for setting up least-privilege execution roles.

5. Connection Management with DynamoDB

The biggest architectural challenge with WebSocket APIs is tracking who is connected. Unlike stateful WebSocket servers that hold connections in memory, a serverless Lambda function has no persistent memory across invocations. You need an external store for connectionId values, and DynamoDB is the natural choice — it is fast, serverless, scales automatically, and supports TTL-based expiration for stale connections.

DynamoDB Table Design

Create a DynamoDB table with connectionId as the partition key. Add a GSI (Global Secondary Index) on userId if you need to look up all connections for a specific user (e.g., to send a direct message or log out all their devices). Enable TTL on a ttl attribute to automatically expire connections that were never cleanly disconnected.

# Create the connections table via AWS CLI
aws dynamodb create-table \
  --table-name WebSocketConnections \
  --attribute-definitions \
      AttributeName=connectionId,AttributeType=S \
      AttributeName=userId,AttributeType=S \
  --key-schema AttributeName=connectionId,KeyType=HASH \
  --billing-mode PAY_PER_REQUEST \
  --global-secondary-indexes '[
    {
      "IndexName": "userId-index",
      "KeySchema": [{"AttributeName":"userId","KeyType":"HASH"}],
      "Projection": {"ProjectionType": "ALL"}
    }
  ]'

# Enable TTL
aws dynamodb update-time-to-live \
  --table-name WebSocketConnections \
  --time-to-live-specification \
      "Enabled=true, AttributeName=ttl"

Storing Connections on $connect

When a client connects, store the connectionId along with metadata. Set the ttl attribute to now + 2 hours (or whatever your maximum connection lifetime is). This ensures that connections which drop without triggering $disconnect are eventually cleaned up automatically — no cron job needed.

import time

def handle_connect(event, connection_id):
    query_params = event.get('queryStringParameters') or {}
    user_id = query_params.get('userId', 'anonymous')
    room_id = query_params.get('roomId', 'general')

    # TTL: 2 hours from now
    ttl_value = int(time.time()) + 7200

    table.put_item(Item={
        'connectionId': connection_id,
        'userId': user_id,
        'roomId': room_id,
        'connectedAt': int(time.time()),
        'ttl': ttl_value
    })
    return {'statusCode': 200, 'body': 'OK'}

Room-Based Broadcasting

For chat applications or collaborative tools, you typically want to broadcast to a subset of connections — everyone in the same room. Use a filter expression when scanning DynamoDB, or add a GSI on roomId and use a query instead of a scan for better performance at scale:

from boto3.dynamodb.conditions import Attr

def get_connections_for_room(room_id):
    """Efficient room-based connection lookup."""
    response = table.scan(
        FilterExpression=Attr('roomId').eq(room_id),
        ProjectionExpression='connectionId'
    )
    return [item['connectionId'] for item in response.get('Items', [])]
Scale tip: DynamoDB Scan reads the entire table, which is costly at scale. For production systems with thousands of concurrent connections, use a GSI on roomId and Query instead. See our DynamoDB complete guide for GSI design patterns and capacity planning.

6. Sending Messages Back to Clients via the @connections Endpoint

One of the most powerful features of API Gateway WebSocket APIs is the @connections management API. This is an HTTPS endpoint that lets any AWS service — not just your WebSocket Lambda — push messages to connected clients. An SQS consumer, an EventBridge rule, a Step Functions workflow, or a scheduled Lambda can all push real-time updates to clients using this endpoint.

The endpoint URL follows the pattern: https://<api-id>.execute-api.<region>.amazonaws.com/<stage>/@connections/<connectionId>

Sending a Message from Python (boto3)

import boto3
import json

def send_to_client(api_id, stage, region, connection_id, message):
    """
    Send a message to a specific connected WebSocket client.
    Works from any Lambda, EC2 instance, or ECS task with
    execute-api:ManageConnections permission.
    """
    endpoint = f"https://{api_id}.execute-api.{region}.amazonaws.com/{stage}"

    apigw_management = boto3.client(
        'apigatewaymanagementapi',
        endpoint_url=endpoint,
        region_name=region
    )

    try:
        apigw_management.post_to_connection(
            ConnectionId=connection_id,
            Data=json.dumps(message).encode('utf-8')
        )
        return True
    except apigw_management.exceptions.GoneException:
        # Connection no longer exists — clean up DynamoDB
        print(f"Stale connection: {connection_id}")
        return False
    except Exception as e:
        print(f"Error sending to {connection_id}: {str(e)}")
        raise


# Example: Push a price update from an EventBridge-triggered Lambda
def push_price_update(event, context):
    dynamodb = boto3.resource('dynamodb')
    table = dynamodb.Table('WebSocketConnections')

    # Get all connections subscribed to this stock ticker
    ticker = event['detail']['ticker']
    price = event['detail']['price']

    response = table.scan(
        FilterExpression=boto3.dynamodb.conditions.Attr('subscribedTicker').eq(ticker),
        ProjectionExpression='connectionId'
    )

    message = {'type': 'priceUpdate', 'ticker': ticker, 'price': price}
    stale = []

    for item in response['Items']:
        cid = item['connectionId']
        success = send_to_client(
            api_id='abc123def4',
            stage='production',
            region='us-east-1',
            connection_id=cid,
            message=message
        )
        if not success:
            stale.append(cid)

    # Batch delete stale connections
    with table.batch_writer() as batch:
        for cid in stale:
            batch.delete_item(Key={'connectionId': cid})

    return {'statusCode': 200}

Sending from JavaScript (Node.js)

const { ApiGatewayManagementApiClient, PostToConnectionCommand } = require("@aws-sdk/client-apigatewaymanagementapi");

async function sendToClient(endpoint, connectionId, data) {
  const client = new ApiGatewayManagementApiClient({ endpoint });

  const command = new PostToConnectionCommand({
    ConnectionId: connectionId,
    Data: Buffer.from(JSON.stringify(data))
  });

  try {
    await client.send(command);
    return true;
  } catch (err) {
    if (err.name === 'GoneException') {
      console.log(`Stale connection: ${connectionId}`);
      return false;
    }
    throw err;
  }
}

// Usage in a Lambda handler
exports.handler = async (event) => {
  const endpoint = `https://${event.requestContext.domainName}/${event.requestContext.stage}`;
  const connectionId = event.requestContext.connectionId;
  const body = JSON.parse(event.body);

  // Echo the message back to the sender
  await sendToClient(endpoint, connectionId, {
    type: 'echo',
    received: body
  });

  return { statusCode: 200 };
};
IAM permission required: The execution role of any Lambda that calls post_to_connection / PostToConnectionCommand must have the execute-api:ManageConnections action on the resource arn:aws:execute-api:region:account:api-id/stage/POST/@connections/*.

7. Authentication and Authorization for WebSocket APIs

Securing WebSocket APIs requires a slightly different approach than REST APIs because the WebSocket protocol does not support custom headers during the initial connection from browser clients — only query string parameters and cookies are available at connect time. Once the connection is established, subsequent messages can include tokens in the message body.

Token in Query String ($connect)

The most common pattern is to pass a short-lived token as a query string parameter when establishing the connection. Your $connect Lambda validates this token and rejects the connection if it is invalid.

# Client connects with a token:
# wss://abc123def4.execute-api.us-east-1.amazonaws.com/production?token=eyJhbGci...

import jwt
import os

def validate_jwt_token(token):
    """Validate a JWT token using Cognito public keys."""
    try:
        # For Cognito tokens, use python-jose with JWKS endpoint
        decoded = jwt.decode(
            token,
            key=os.environ['JWT_SECRET'],
            algorithms=['HS256'],
            options={"require": ["exp", "sub"]}
        )
        return decoded['sub']  # Return user ID
    except jwt.ExpiredSignatureError:
        raise ValueError("Token expired")
    except jwt.InvalidTokenError:
        raise ValueError("Invalid token")

def handle_connect(event, connection_id):
    query_params = event.get('queryStringParameters') or {}
    token = query_params.get('token', '')

    try:
        user_id = validate_jwt_token(token)
    except ValueError as e:
        print(f"Auth failed for {connection_id}: {e}")
        return {'statusCode': 401, 'body': 'Unauthorized'}

    # Store valid connection
    table.put_item(Item={
        'connectionId': connection_id,
        'userId': user_id,
        'ttl': int(time.time()) + 7200
    })
    return {'statusCode': 200, 'body': 'Connected'}

Lambda Authorizers for WebSocket APIs

API Gateway supports Lambda authorizers for WebSocket APIs, but only for the $connect route. A Lambda authorizer is a separate Lambda function that receives the connection request, validates the token, and returns an IAM policy document that either allows or denies the connection. This separates authentication logic from your main handler.

Configure a Lambda authorizer in the console under Authorizers, set the source to $request.querystring.token, and attach it to the $connect route. API Gateway invokes the authorizer before your $connect handler. Authorizer responses can be cached by API Gateway for a configurable TTL, reducing the number of authorizer Lambda invocations.

Cognito Integration

If you use AWS Cognito for user management, generate a short-lived Cognito ID token in your frontend and pass it as the token query parameter. In your $connect Lambda, use the python-jose library to verify the token against Cognito's JWKS endpoint. This gives you full Cognito user pool integration without any extra infrastructure.

Security note: Query string parameters in WebSocket URLs are logged by API Gateway access logs and may appear in browser history or server logs. Use short-lived tokens (5–15 minute expiry) specifically issued for WebSocket connections, separate from your main API tokens.

8. Monitoring WebSocket APIs with CloudWatch

AWS API Gateway automatically publishes metrics to CloudWatch for WebSocket APIs. Monitoring these metrics is essential for understanding the health of your real-time backend, detecting connection storms, and diagnosing message delivery failures.

Key CloudWatch Metrics for WebSocket APIs

The following metrics are available under the AWS/ApiGateway namespace with dimensions ApiId and Stage:

  • ConnectCount — Number of $connect route invocations. A spike here indicates a connection storm.
  • DisconnectCount — Number of $disconnect route invocations.
  • MessageCount — Total messages received from clients (all routes). Use this for billing estimation.
  • IntegrationError — Number of Lambda invocations that returned a 5xx error. Alarm on this immediately.
  • ClientError — 4xx errors (auth failures, route not found). High values indicate client configuration issues.
  • ExecutionError — Errors in API Gateway itself, not your Lambda. Rare but worth monitoring.
  • IntegrationLatency — Time API Gateway waited for Lambda to respond. High values indicate cold starts or Lambda timeouts.

Setting Up Alarms via AWS CLI

# Alarm when integration errors exceed 10 in 5 minutes
aws cloudwatch put-metric-alarm \
  --alarm-name "WebSocket-IntegrationErrors" \
  --alarm-description "WebSocket Lambda integration errors" \
  --metric-name IntegrationError \
  --namespace AWS/ApiGateway \
  --statistic Sum \
  --period 300 \
  --threshold 10 \
  --comparison-operator GreaterThanOrEqualToThreshold \
  --evaluation-periods 1 \
  --dimensions \
      Name=ApiId,Value=abc123def4 \
      Name=Stage,Value=production \
  --alarm-actions arn:aws:sns:us-east-1:123456789012:WebSocketAlerts \
  --treat-missing-data notBreaching

# Alarm when active connection count (ConnectCount - DisconnectCount)
# is approximated via ConnectCount anomaly detection
aws cloudwatch put-metric-alarm \
  --alarm-name "WebSocket-HighConnectRate" \
  --metric-name ConnectCount \
  --namespace AWS/ApiGateway \
  --statistic Sum \
  --period 60 \
  --threshold 1000 \
  --comparison-operator GreaterThanThreshold \
  --evaluation-periods 2 \
  --dimensions Name=ApiId,Value=abc123def4 Name=Stage,Value=production \
  --alarm-actions arn:aws:sns:us-east-1:123456789012:WebSocketAlerts

Enabling Access Logging

Enable access logging on your WebSocket stage to get per-connection, per-message logs in CloudWatch Logs. In the Console, go to your stage settings and set a CloudWatch Logs ARN. In the log format, include $context.connectionId, $context.routeKey, $context.status, and $context.integrationLatency for full observability. For deeper log analysis patterns, see our CloudWatch monitoring guide.

Pro tip: Use CloudWatch Contributor Insights with the built-in API Gateway rule to automatically identify the top connection IDs generating the most traffic or errors — useful for detecting misbehaving clients.

9. Cost Model and Limits

WebSocket APIs have a different pricing model than REST or HTTP APIs. Understanding costs upfront prevents billing surprises, especially for applications with many long-lived connections or high message volumes.

Pricing Components (us-east-1, June 2026)

  • Connection minutes: $0.00003 per connection-minute. A connection open for 1 hour costs $0.0018. 10,000 users connected for 1 hour = $1.08.
  • Messages: $1.00 per million messages (first 1M free per month). Each message unit = 32 KB of data. A 96 KB message counts as 3 message units.
  • Lambda invocations: Billed separately at Lambda pricing — $0.20 per million requests plus duration charges.
  • DynamoDB: Connection storage billed at DynamoDB on-demand rates — typically negligible for connection metadata.
  • Data transfer: Standard AWS data transfer rates apply for messages leaving AWS regions.

Cost Optimization Strategies

For cost efficiency: batch small messages into larger ones to reduce message count (stay within 32 KB per unit). Use client-side heartbeats (ping/pong at the WebSocket protocol level) rather than sending JSON heartbeat messages that count toward your message quota. Set aggressive idle timeouts to disconnect inactive clients — the default maximum connection duration is 2 hours, but you can close idle connections sooner using the management API.

Service Limits

LimitDefaultAdjustable?
Maximum connection duration2 hoursNo
Idle connection timeout10 minutesNo
Maximum frame size32 KBNo
Maximum message size (aggregated)128 KBNo
Concurrent connections per API500Yes (up to 50,000)
New connections per second500Yes
Routes per API300Yes

Request limit increases through the AWS Support Console. For applications expecting more than 500 concurrent connections at launch, request the limit increase before going live — the process typically takes 1–3 business days.

Free tier: AWS API Gateway WebSocket APIs are included in the 12-month free tier: 500,000 connection-minutes and 1 million messages per month. This is sufficient for development and low-traffic early-stage applications.

10. Frequently Asked Questions

Can I use API Gateway WebSocket APIs with containers instead of Lambda?

Yes. API Gateway WebSocket APIs support HTTP integrations, which means you can forward WebSocket messages to an HTTP endpoint hosted on EKS, ECS, or EC2. API Gateway translates each WebSocket message into an HTTP POST request to your endpoint. However, the serverless Lambda approach is far simpler for most use cases and eliminates server management entirely. Use containers when you need custom WebSocket sub-protocols, binary framing, or stateful in-memory connection handling that does not fit the Lambda event model.

What happens when my Lambda times out during a WebSocket message?

If your Lambda integration times out (default 29 seconds for API Gateway integrations), API Gateway returns a timeout error to the client on that route invocation, but the WebSocket connection itself remains open. The client will receive an error frame or no response depending on how your error handling is configured. Always set your Lambda timeout comfortably below the 29-second API Gateway integration timeout, and for long-running operations, return immediately from the Lambda and process asynchronously — push the result back to the client via the @connections API when ready. See our Lambda serverless guide for async patterns.

How do I handle message ordering with WebSocket APIs?

API Gateway WebSocket APIs do not guarantee message ordering when multiple senders are broadcasting simultaneously. Within a single client's send stream, messages are received in order. But if you have an application (like a Lambda triggered by an EventBridge event or an SQS message) sending to multiple clients, those sends happen in parallel and arrival order is not guaranteed. For ordered delivery, use a sequence number in your message schema and handle reordering on the client side. If strict ordering is critical, funnel all messages through a single SQS FIFO queue and process them sequentially before sending.

Can I migrate an existing REST API to WebSocket without rewriting the client?

Not directly — WebSocket is a fundamentally different protocol from HTTP. Clients must use a WebSocket library to connect. However, you can run both APIs in parallel during a migration. Keep your REST API operational while you add a WebSocket API for real-time features. Clients that support WebSocket can upgrade to the new API; legacy clients continue using REST. A common pattern is to use REST for initial data load (hydration) and WebSocket for live updates — the two APIs complement each other rather than compete.

Read Next

AWS API Gateway Complete Guide

Deep dive into REST APIs, HTTP APIs, stages, usage plans, and API Gateway best practices for production workloads.

AWS Lambda and Serverless Computing

Master Lambda functions, event sources, async patterns, cold start optimization, and serverless architecture patterns.