Lambda Proxy Integration vs Standard Integration in API Gateway: Event Format Deep Dive
When wiring AWS Lambda to API Gateway, the integration type you choose determines not just how requests are forwarded, but the entire shape of the event object your function receives — a distinction that silently breaks deployments when teams switch between the two modes without updating their handler logic.
TL;DR: Lambda Proxy Integration vs Standard Integration
| Dimension | Lambda Proxy Integration | Standard (Custom) Integration |
|---|---|---|
| Request mapping | API Gateway passes the full HTTP context as-is | You define mapping templates (VTL) to shape the payload |
| Event object | Structured APIGatewayProxyRequest envelope | Arbitrary JSON you control via mapping template |
| Response contract | Lambda MUST return {statusCode, headers, body} | API Gateway applies output mapping; Lambda returns raw data |
| Header/query access | Available in event.headers, event.queryStringParameters | Only what your mapping template explicitly extracts |
| Setup complexity | Low — no VTL required | High — requires mapping templates for every method |
| Flexibility | Lower — response shape is fixed | Higher — full control over request/response transformation |
How Lambda Proxy Integration Works in API Gateway
Lambda Proxy Integration is the mode where API Gateway acts as a transparent pass-through. Every HTTP detail — method, path, headers, query parameters, body, stage variables, and request context — is serialized into a single JSON envelope and delivered to your Lambda function as the event object. No Velocity Template Language (VTL) mapping is required, and no transformation occurs before the function is invoked.
The trade-off is a strict response contract. Your Lambda function must return a JSON object with at minimum a statusCode integer and a body string. If the response is malformed, API Gateway returns a 502 Bad Gateway to the caller — not a Lambda error, which is why this failure mode is frequently misdiagnosed as a network issue.
Full context serialized APIGW->>Lambda: event = {httpMethod, path,
headers, queryStringParameters, body} Lambda->>Lambda: JSON.parse(event.body) Lambda-->>APIGW: {statusCode: 200, headers: {...}, body: "..."} Note over APIGW: Validates response contract APIGW-->>Client: HTTP 200 with body Note over APIGW,Client: 502 if body is not a string
- Client → API Gateway: An HTTP request arrives with method, path, headers, and body.
- API Gateway → Lambda (Proxy): The entire request context is serialized into the
APIGatewayProxyRequestevent envelope — no VTL transformation. - Lambda Handler: Your function reads
event.httpMethod,event.path,event.headers,event.queryStringParameters, andevent.body. - Lambda → API Gateway (Response): The function returns a structured object with
statusCode,headers, andbody. - API Gateway → Client: The response is forwarded directly. If the contract is violated, API Gateway emits
502.
The Proxy Event Object: Exact Structure
Understanding the exact shape of the proxy event is non-negotiable. A handler that assumes event.body is already a parsed object will throw a runtime error — the body arrives as a raw string and must be explicitly parsed with JSON.parse().
🔽 Click to expand — Full APIGatewayProxyRequest event structure (Node.js example)
// event object received by Lambda under Proxy Integration
{
"resource": "/users/{id}",
"path": "/users/42",
"httpMethod": "POST",
"headers": {
"Content-Type": "application/json",
"Authorization": "Bearer eyJ..."
},
"multiValueHeaders": {
"Accept": ["application/json", "text/html"]
},
"queryStringParameters": {
"verbose": "true"
},
"multiValueQueryStringParameters": {
"tag": ["aws", "lambda"]
},
"pathParameters": {
"id": "42"
},
"stageVariables": {
"env": "prod"
},
"requestContext": {
"accountId": "123456789012",
"resourceId": "abc123",
"stage": "prod",
"requestId": "req-id-xyz",
"identity": {
"sourceIp": "203.0.113.5",
"userAgent": "Mozilla/5.0"
},
"httpMethod": "POST",
"path": "/prod/users/42"
},
"body": "{\"name\": \"Alice\"}", // Always a string — call JSON.parse(event.body)
"isBase64Encoded": false
}
The body field deserves special attention: it is always a string, never a pre-parsed object. When isBase64Encoded is true (binary payloads or certain content types), you must decode it before parsing. This is a common source of SyntaxError: Unexpected token in production handlers.
Standard Integration: VTL Mapping Templates
Standard integration gives you full control over what the Lambda function receives. You author Velocity Template Language (VTL) mapping templates in the Integration Request and Integration Response sections of API Gateway. The function receives only what your template explicitly constructs — nothing more.
Think of standard integration as a customs checkpoint: every field must be declared on the manifest before it crosses the border into your Lambda function. Proxy integration is the open border — everything passes through, and your handler sorts it out.
A minimal VTL mapping template that extracts the request body and a path parameter looks like this:
## Integration Request mapping template (application/json)
{
"userId": "$input.params('id')",
"payload": $input.json('$')
}
With this template, your Lambda event contains only userId and payload. Headers, stage variables, and request context are absent unless you explicitly add them. This is both the power and the operational risk of standard integration — a missing field in the template means a missing field at runtime, with no error from API Gateway.
(VTL Template) participant Lambda participant VTL_Res as Integration Response
(VTL Template) Client->>APIGW: HTTP POST /users/42 APIGW->>VTL_Req: Apply request mapping template Note over VTL_Req: Extracts only declared fields
e.g. userId, payload VTL_Req->>Lambda: {userId: "42", payload: {...}} Lambda-->>VTL_Res: Raw return value VTL_Res->>APIGW: Apply response mapping template APIGW-->>Client: Transformed HTTP response
- Integration Request Template: VTL transforms the incoming HTTP request into a custom JSON shape before Lambda is invoked.
- Lambda receives a clean, minimal event — only the fields your template defines.
- Lambda returns raw data — no structural contract enforced by API Gateway.
- Integration Response Template: VTL maps the Lambda output back to an HTTP response, including status codes and headers.
Required Lambda Response Format for Proxy Integration
This is where most proxy integration failures originate. The response your Lambda function returns must conform to a specific structure. API Gateway validates this structure before forwarding the response to the client.
// Node.js — correct proxy integration response
exports.handler = async (event) => {
const body = JSON.parse(event.body || '{}');
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
},
body: JSON.stringify({ message: 'Success', input: body })
// body MUST be a string — JSON.stringify() is required
};
};
Key constraints on the response object:
statusCodemust be an integer (not a string).bodymust be a string. Returning a raw object causes a502.headersis optional but required for CORS scenarios.- If your function throws an unhandled exception, API Gateway returns
502, not500.
Experience Signal: The Silent 502 Misdiagnosis
A team migrates a Lambda function from standard integration to proxy integration. The function works perfectly in unit tests. In production, every request returns 502 Bad Gateway. CloudWatch shows the Lambda invocation succeeded with status 200. The instinct is to check VPC routing, security groups, or API Gateway throttling.
The actual cause: the function was returning { statusCode: 200, body: { message: 'ok' } } — the body was a JavaScript object, not a string. Under standard integration, the output mapping template handled serialization. Under proxy integration, API Gateway receives a non-string body, fails its response validation, and emits 502 — with no Lambda error logged, because Lambda itself succeeded.
The fix is a single character change: wrap the body in JSON.stringify(). The diagnostic path is checking the Execution Logs in API Gateway, not Lambda logs, where you will find: Endpoint response body before transformations: ... followed by a malformed structure.
Enabling API Gateway Execution Logs for Proxy Integration Debugging
API Gateway execution logs are disabled by default. Enabling them is the fastest way to observe the raw event and response at the gateway layer — essential when debugging proxy integration contract violations.
First, ensure the API Gateway stage has a CloudWatch Logs role configured at the account level. Then enable logging on the stage:
# Step 1: Create the IAM role for API Gateway to write CloudWatch Logs
aws iam create-role \
--role-name APIGatewayCloudWatchLogsRole \
--assume-role-policy-document '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "Service": "apigateway.amazonaws.com" },
"Action": "sts:AssumeRole"
}]
}'
# Step 2: Attach the managed policy for CloudWatch Logs access
aws iam attach-role-policy \
--role-name APIGatewayCloudWatchLogsRole \
--policy-arn arn:aws:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs
# Step 3: Set the CloudWatch Logs role ARN on the API Gateway account settings
aws apigateway update-account \
--patch-operations op=replace,path=/cloudwatchRoleArn,value=arn:aws:iam::123456789012:role/APIGatewayCloudWatchLogsRole
# Step 4: Enable execution logging and data trace on the stage
# Uses correct patch paths for per-method settings (/*/*)
aws apigateway update-stage \
--rest-api-id YOUR_API_ID \
--stage-name prod \
--patch-operations \
op=replace,path='/~1*~1*/logging/loglevel',value=INFO \
op=replace,path='/~1*~1*/logging/dataTrace',value=true
The ~1 encoding represents a forward slash (/) in JSON Pointer syntax, which API Gateway uses for patch paths. The path /~1*~1*/logging/loglevel targets all HTTP methods across all resources (/*/*) on the stage.
Once enabled, execution logs appear in CloudWatch Logs under the log group API-Gateway-Execution-Logs_{rest-api-id}/{stage-name}. Look for the Endpoint response body before transformations line to inspect exactly what Lambda returned before API Gateway processed it.
IAM Policy for Lambda Execution with CloudWatch Logs
Your Lambda execution role requires permissions to write its own logs. This is separate from the API Gateway CloudWatch role above.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "LambdaBasicLogging",
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:us-east-1:123456789012:log-group:/aws/lambda/*"
}
]
}
Enabling Proxy Integration via CLI
When creating or updating an API Gateway integration, the --type parameter controls whether proxy integration is active. For Lambda proxy integration, use AWS_PROXY. For standard integration, use AWS.
# Create a Lambda Proxy Integration on a resource/method
aws apigateway put-integration \
--rest-api-id YOUR_API_ID \
--resource-id YOUR_RESOURCE_ID \
--http-method POST \
--type AWS_PROXY \
--integration-http-method POST \
--uri arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-1:123456789012:function:YourFunctionName/invocations
Note that --integration-http-method must always be POST for Lambda integrations, regardless of the HTTP method the client uses. This is a fixed requirement of the Lambda invoke API.
Depth Signal: multiValueHeaders and the Header Casing Trap
HTTP headers are case-insensitive by specification, but the proxy event object delivers them with the casing API Gateway received from the client. A handler checking event.headers['Authorization'] will find undefined if the client sent authorization (lowercase). This is not a bug — it is the documented behavior of the proxy event.
The multiValueHeaders field was added to handle cases where a client sends multiple values for the same header name (e.g., multiple Set-Cookie headers). If your integration only reads headers, duplicate header values are collapsed and only the last value is retained. For cookie handling or multi-value scenarios, always read from multiValueHeaders.
A defensive header lookup pattern in Node.js:
// Case-insensitive header lookup for proxy integration events
function getHeader(event, name) {
const headers = event.headers || {};
const key = Object.keys(headers).find(
k => k.toLowerCase() === name.toLowerCase()
);
return key ? headers[key] : undefined;
}
const authHeader = getHeader(event, 'authorization');
Choosing Between Proxy and Standard Integration
in Lambda?"} Q1 -- Yes --> Q2{"Willing to enforce
response contract?"} Q2 -- Yes --> Proxy["Use AWS_PROXY
Lambda Proxy Integration"] Q2 -- No --> Standard Q1 -- No --> Q3{"Need request/response
transformation?"} Q3 -- Yes --> Standard["Use AWS
Standard Integration + VTL"] Q3 -- No --> Q4{"Need different HTTP status
codes from Lambda output?"} Q4 -- Yes --> Standard Q4 -- No --> Proxy
- If you need full HTTP context (headers, query params, path params, request context) in Lambda without writing VTL — use Proxy Integration.
- If you need to transform the request shape before Lambda receives it, or transform the Lambda response before the client receives it — use Standard Integration with VTL templates.
- If you are building a REST API where Lambda acts as a controller handling routing internally — Proxy Integration is the standard choice.
- If you need to return different HTTP status codes based on Lambda output without modifying the function — Standard Integration with response mapping gives you that control.
Wrap-Up: Lambda Proxy Integration and Next Steps
Lambda Proxy Integration removes the VTL complexity of standard integration at the cost of a strict response contract and a richer, more complex event object. The most common production failures — 502 errors and missing header values — both trace back to misunderstanding that contract. Enable API Gateway execution logs at the stage level as a first diagnostic step, and always treat event.body as a string that requires explicit parsing.
For further reading, consult the AWS API Gateway Lambda Proxy Integration documentation and the Mapping Template Reference for standard integration VTL syntax.
Glossary
| Term | Definition |
|---|---|
| Lambda Proxy Integration | API Gateway integration type (AWS_PROXY) that forwards the full HTTP request context to Lambda as a structured event envelope without VTL transformation. |
| Standard Integration | API Gateway integration type (AWS) that uses VTL mapping templates to transform requests before invoking Lambda and responses before returning to the client. |
| VTL (Velocity Template Language) | The templating language used by API Gateway mapping templates to transform request and response payloads in standard integration. |
| APIGatewayProxyRequest | The structured JSON event object delivered to Lambda under proxy integration, containing httpMethod, path, headers, queryStringParameters, pathParameters, body, and requestContext. |
| 502 Bad Gateway | The HTTP error returned by API Gateway when a Lambda function under proxy integration returns a response that does not conform to the required {statusCode, headers, body} structure. |
Comments
Post a Comment