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

DimensionLambda Proxy IntegrationStandard (Custom) Integration
Request mappingAPI Gateway passes the full HTTP context as-isYou define mapping templates (VTL) to shape the payload
Event objectStructured APIGatewayProxyRequest envelopeArbitrary JSON you control via mapping template
Response contractLambda MUST return {statusCode, headers, body}API Gateway applies output mapping; Lambda returns raw data
Header/query accessAvailable in event.headers, event.queryStringParametersOnly what your mapping template explicitly extracts
Setup complexityLow — no VTL requiredHigh — requires mapping templates for every method
FlexibilityLower — response shape is fixedHigher — 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.

sequenceDiagram participant Client participant APIGW as API Gateway participant Lambda Client->>APIGW: HTTP POST /users/42?verbose=true Note over APIGW: No VTL transformation
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
  1. Client → API Gateway: An HTTP request arrives with method, path, headers, and body.
  2. API Gateway → Lambda (Proxy): The entire request context is serialized into the APIGatewayProxyRequest event envelope — no VTL transformation.
  3. Lambda Handler: Your function reads event.httpMethod, event.path, event.headers, event.queryStringParameters, and event.body.
  4. Lambda → API Gateway (Response): The function returns a structured object with statusCode, headers, and body.
  5. 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.

sequenceDiagram participant Client participant APIGW as API Gateway participant VTL_Req as Integration Request
(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
  1. Integration Request Template: VTL transforms the incoming HTTP request into a custom JSON shape before Lambda is invoked.
  2. Lambda receives a clean, minimal event — only the fields your template defines.
  3. Lambda returns raw data — no structural contract enforced by API Gateway.
  4. 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:

  • statusCode must be an integer (not a string).
  • body must be a string. Returning a raw object causes a 502.
  • headers is optional but required for CORS scenarios.
  • If your function throws an unhandled exception, API Gateway returns 502, not 500.

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

flowchart TD Start(["Choosing Integration Type"]) --> Q1{"Need full HTTP context
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
  1. If you need full HTTP context (headers, query params, path params, request context) in Lambda without writing VTL — use Proxy Integration.
  2. 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.
  3. If you are building a REST API where Lambda acts as a controller handling routing internally — Proxy Integration is the standard choice.
  4. 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

TermDefinition
Lambda Proxy IntegrationAPI Gateway integration type (AWS_PROXY) that forwards the full HTTP request context to Lambda as a structured event envelope without VTL transformation.
Standard IntegrationAPI 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.
APIGatewayProxyRequestThe structured JSON event object delivered to Lambda under proxy integration, containing httpMethod, path, headers, queryStringParameters, pathParameters, body, and requestContext.
502 Bad GatewayThe 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.

Related Posts

Comments

Popular posts from this blog

EC2 No Internet Access in Custom VPC: Fix Internet Gateway and Route Table

EC2 SSH Connection Timeout: Which Security Group Rules to Check

Difference Between IAM User and IAM Role: Which One Should Your EC2 Use?