The Symptom: What the Hell Is Going On?
Your Lambda function is returning binary data—images, PDFs, protobuf—and you’ve set isBase64Encoded: true in the response. API Gateway just ignores it. Flat out.
The client gets either garbled text or a 500 error. You check CloudWatch logs. The Lambda response is perfect: isBase64Encoded: true, base64-encoded body, correct statusCode. But API Gateway forwards it to the client as if it were plain text.
The kicker: AWS docs say “if your Lambda function returns valid JSON and doesn’t return a statusCode, isBase64Encoded defaults to false.” But you’re returning statusCode and isBase64Encoded: true. So what gives?
I’ve hit this twice. First time was in 2023 for an image processing service. Second time was last week for a protobuf endpoint. Both times it cost me an entire afternoon.
Root Cause: The Global Config You Didn’t Know Existed
The core truth is brutal: API Gateway’s binary support is a global setting, not something your Lambda function can control.
Here’s the breakdown:
API Gateway REST API: You must manually add
binaryMediaTypesin the API settings. Without this, API Gateway passes your base64 string straight through to the client without decoding it.API Gateway HTTP API: It’s subtler. HTTP APIs have binary support enabled by default, but there’s a hidden condition—the
Content-Typein your Lambda response must match an implicit whitelist.ALB (Application Load Balancer): Different beast entirely. ALB tries to base64-decode all responses by default, but if you haven’t configured “binary support” on the target group, it’ll double-encode your data or just error out.
The Fix: Step-by-Step
Step 1: Verify Your Lambda Response Format
Before touching API Gateway config, make sure your Lambda return format is correct:
# Python Lambda handler
import base64
import json
def lambda_handler(event, context):
binary_data = b'\x89PNG\r\n\x1a\n...' # Your binary content
return {
'statusCode': 200,
'headers': {
'Content-Type': 'image/png',
# Don't add 'Content-Encoding': 'base64' here—common mistake
},
'body': base64.b64encode(binary_data).decode('utf-8'),
'isBase64Encoded': True
}
Node.js version:
exports.handler = async (event) => {
const binaryData = Buffer.from('...'); // Your binary content
return {
statusCode: 200,
headers: {
'Content-Type': 'image/png'
},
body: binaryData.toString('base64'),
isBase64Encoded: true
};
};
Common mistake: People think setting isBase64Encoded: true is enough. But if you add 'Content-Encoding': 'base64' in the headers, API Gateway might pass it through to the client, causing double-decoding issues.
Step 2: Configure Binary Media Types (REST API)
This is the step everyone misses. The one that’ll save your afternoon.
Via AWS CLI:
# Get your API ID
aws apigateway get-rest-apis
# Update binaryMediaTypes
aws apigateway update-rest-api \
--rest-api-id your-api-id \
--patch-operations op=add,path=/binaryMediaTypes,value='image/png' \
--patch-operations op=add,path=/binaryMediaTypes,value='application/octet-stream' \
--patch-operations op=add,path=/binaryMediaTypes,value='application/protobuf'
# Deploy the API (this is mandatory—config doesn't apply until deployment)
aws apigateway create-deployment \
--rest-api-id your-api-id \
--stage-name prod
Via AWS Console:
- Go to API Gateway console
- Select your API
- Click “Settings” in the left menu
- Under “Binary Media Types”, add your content types
- Click “Save Changes”
- Critical: Redeploy your API to a stage
The wildcard trap: People love using */* to match everything. Don’t. Once you add */*, API Gateway tries to base64-decode every response, including your JSON and HTML endpoints. Your text APIs will return garbage.
Step 3: HTTP API Special Handling
If you’re using HTTP API (not REST API), it’s different:
# HTTP API doesn't need explicit binaryMediaTypes
# But your Lambda's Content-Type must match the implicit whitelist
# Check your route config
aws apigatewayv2 get-routes --api-id your-http-api-id
# Ensure no response templates are set on your routes
# Response templates break binary support in HTTP APIs
HTTP API’s implicit rules:
- If
Content-Typeistext/*,application/json,application/xml→ treated as text - If
Content-Typeisimage/*,application/octet-stream→ auto-detected as binary - Exception: If you have response templates configured on the route, binary support breaks
Step 4: Verify Your Configuration
Test with curl:
# Test image endpoint
curl -v -o output.png \
-H "Accept: image/png" \
https://your-api-id.execute-api.region.amazonaws.com/prod/image
# Check response headers
# Should see: Content-Type: image/png
# Should NOT see: Content-Encoding: base64
# Test protobuf endpoint
curl -v -o output.bin \
-H "Content-Type: application/protobuf" \
-H "Accept: application/protobuf" \
https://your-api-id.execute-api.region.amazonaws.com/prod/protobuf
Header check: If you see Content-Type: application/json or Content-Type: text/plain, API Gateway didn’t recognize your binary response. It’s passing the base64 string as text.
Step 5: ALB Edge Case
ALB + Lambda combo is a different animal:
# Create target group for Lambda
aws elbv2 create-target-group \
--name my-lambda-tg \
--target-type lambda \
--protocol HTTP \
--port 80 \
--vpc-id vpc-xxx
# ALB binary support: you can't configure it directly on the target group
# Solution: Don't set isBase64Encoded in Lambda for ALB
# ALB assumes all responses with binary Content-Type are base64-encoded
# Correct ALB+Lambda handler:
def lambda_handler(event, context):
binary_data = b'\x89PNG...'
# ALB auto-decodes base64 for binary content types
# So return base64-encoded data WITHOUT isBase64Encoded
return {
'statusCode': 200,
'headers': {
'Content-Type': 'image/png',
# ALB needs this header to identify binary content
'X-Amz-Binary-Media-Type': 'image/png'
},
'body': base64.b64encode(binary_data).decode('utf-8'),
# Note: isBase64Encoded is ignored by ALB Lambda integration
# ALB always assumes base64 encoding for binary Content-Types
}
Configuration Comparison Table
| Integration Type | Needs binaryMediaTypes? | isBase64Encoded field | Common Pitfall |
|---|---|---|---|
| API Gateway REST API + Lambda Proxy | Yes, must add manually | Must be true | Forgetting to redeploy after config |
| API Gateway HTTP API + Lambda Proxy | No, auto-handled | Must be true | Content-Type not in implicit whitelist |
| ALB + Lambda | No, but needs special headers | Ignored by ALB | Need X-Amz-Binary-Media-Type header |
| CloudFront + API Gateway | Must configure in CloudFront behavior | Same as API Gateway | CloudFront double-caching, need cache invalidation |
Real-World Case: Protobuf Endpoint Fix
Last week I helped a team debug this exact issue. They had an API endpoint returning protobuf data. Lambda was returning isBase64Encoded: true perfectly. The client was getting the base64 string itself, not the decoded binary.
Debug flow:
- Checked Lambda logs: confirmed
isBase64Encoded: truewas being returned - Curl test: response header was
Content-Type: application/json, notapplication/protobuf - Checked API Gateway settings:
binaryMediaTypeswas empty - Added
application/protobufto binaryMediaTypes - Redeployed API
- Retested: response header changed to
Content-Type: application/protobuf, content decoded correctly
Takeaway: API Gateway’s binaryMediaTypes is a whitelist. Only Content-Types in this list get base64-decoded. Everything else gets passed through as text.
FAQ
Q: I set isBase64Encoded: true but API Gateway still returns garbage. Why?
A: Most common cause is missing binaryMediaTypes configuration in API Gateway settings. Also check if you accidentally added Content-Encoding: base64 in response headers, which causes double-decoding on the client side.
Q: Does API Gateway HTTP API need binaryMediaTypes configuration?
A: No. HTTP APIs handle binary content automatically, but your Lambda’s Content-Type header must be a standard binary type (like image/*, application/octet-stream). If it’s application/json, HTTP API treats it as text.
Q: How do I verify that API Gateway correctly decoded the base64 response?
A: Use curl with -v to inspect response headers. If you see Content-Type: image/png and the content renders correctly, decoding worked. If you see Content-Type: application/json or text/plain, decoding failed.
Q: What’s the risk of using wildcard */* as binaryMediaTypes?
A: It causes API Gateway to attempt base64 decoding on ALL responses, including JSON and HTML. If your API serves both text and binary content, avoid wildcards. Specify exact Content-Types instead.