Ops Notes

API Gateway Lambda Proxy: The isBase64Encoded Always False Nightmare — Root Cause & Fix

Cloud & DevOps Visualization

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:

  1. API Gateway REST API: You must manually add binaryMediaTypes in the API settings. Without this, API Gateway passes your base64 string straight through to the client without decoding it.

  2. API Gateway HTTP API: It’s subtler. HTTP APIs have binary support enabled by default, but there’s a hidden condition—the Content-Type in your Lambda response must match an implicit whitelist.

  3. 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:

  1. Go to API Gateway console
  2. Select your API
  3. Click “Settings” in the left menu
  4. Under “Binary Media Types”, add your content types
  5. Click “Save Changes”
  6. 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-Type is text/*, application/json, application/xml → treated as text
  • If Content-Type is image/*, 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 TypeNeeds binaryMediaTypes?isBase64Encoded fieldCommon Pitfall
API Gateway REST API + Lambda ProxyYes, must add manuallyMust be trueForgetting to redeploy after config
API Gateway HTTP API + Lambda ProxyNo, auto-handledMust be trueContent-Type not in implicit whitelist
ALB + LambdaNo, but needs special headersIgnored by ALBNeed X-Amz-Binary-Media-Type header
CloudFront + API GatewayMust configure in CloudFront behaviorSame as API GatewayCloudFront 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:

  1. Checked Lambda logs: confirmed isBase64Encoded: true was being returned
  2. Curl test: response header was Content-Type: application/json, not application/protobuf
  3. Checked API Gateway settings: binaryMediaTypes was empty
  4. Added application/protobuf to binaryMediaTypes
  5. Redeployed API
  6. 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.