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.
- WebSocket API vs REST API vs HTTP API
- Creating a WebSocket API
- Route Keys: $connect, $disconnect, $default, and Custom Routes
- Lambda Handler for WebSocket Connections
- Connection Management with DynamoDB
- Sending Messages Back to Clients
- Authentication and Authorization
- Monitoring with CloudWatch
- Cost Model and Limits
- Frequently Asked Questions
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
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
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.
$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', [])]
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 };
};
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.
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
$connectroute invocations. A spike here indicates a connection storm. - DisconnectCount — Number of
$disconnectroute 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.
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
| Limit | Default | Adjustable? |
|---|---|---|
| Maximum connection duration | 2 hours | No |
| Idle connection timeout | 10 minutes | No |
| Maximum frame size | 32 KB | No |
| Maximum message size (aggregated) | 128 KB | No |
| Concurrent connections per API | 500 | Yes (up to 50,000) |
| New connections per second | 500 | Yes |
| Routes per API | 300 | Yes |
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.
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.