502 "Malformed Lambda Proxy Response": The Exact Fix

Logeshwaran.C

If your API Gateway REST API is throwing a 502 with the exact message "Execution failed due to configuration error: Malformed Lambda proxy response," and you've already read your Lambda function's code three times looking for the bug, stop looking there. The function's logic is almost never the problem. The problem is the shape of the object it returns. API Gateway will happily accept a Lambda response full of business-logic bugs as long as it arrives with a numeric status code and a string body, and it will reject a functionally perfect response if that status code shows up as a string instead of a number. That is the counterintuitive part: the fix is usually one line, not a rewrite.

⚡ Quick Answer

Check the return object → it must be JSON with a numeric statusCode and a body that is a string, not an object.

Most common single cause → forgetting to run JSON.stringify() (Node) or json.dumps() (Python) on the body before returning it.

See the exact required format below, or jump straight to the decision table if you already know your setup.

Jake runs a small phone repair shop, and last month he bolted a "check my repair status" page onto his shop's website. A customer types a ticket number, a Lambda function looks it up, API Gateway hands the answer back. He followed a tutorial, deployed it, and got this instead of a repair status: a blank error page and, in the browser console, {"message": "Internal server error"}.

"I didn't even touch the part that looks things up," Jake said. "I just added a line to return the customer's phone model along with the status." Ethan pulled up the CloudWatch logs before Jake finished the sentence. "That's the tell," Ethan said. "When the error shows up the moment you touch what you're returning, not what you're computing, it's the return statement, not the logic."

What "Malformed Lambda Proxy Response" Actually Means

When you connect an API Gateway REST API method to a Lambda function using a Lambda proxy integration (the "Use Lambda Proxy integration" checkbox, or AWS_PROXY if you're reading Terraform or CloudFormation), API Gateway stops trying to interpret your function's output for you. It expects the function to hand back a specific JSON envelope, and it passes that envelope straight through to the client, mostly unchanged.

The documented behavior for this integration type is direct: if the function's output doesn't match that envelope, API Gateway returns a 502 Bad Gateway error to the client, and in CloudWatch Logs you'll see the line from the brief above — "Execution failed due to configuration error: Malformed Lambda proxy response." The client never sees your real return value. It sees a generic {"message": "Internal server error"}, because API Gateway can't safely guess what you meant to send back.

🙋‍♂️ Jake's Reality Check

"So my code ran fine, found the right ticket, built the right answer... and API Gateway just throws it away and shows the customer a generic error?"

Yes. Lambda executed successfully and returned a value. API Gateway looked at the shape of that value, decided it didn't match the required format, and discarded it in favor of a generic 502. Your customer never sees the words "Internal server error" because anything actually went wrong with their ticket — they see it because the wrapper around the answer was the wrong shape.

The Exact Shape API Gateway Requires

For a REST API using the classic Lambda proxy integration, the documented output format is:

{ "isBase64Encoded": true|false, "statusCode": httpStatusCode, "headers": { "headerName": "headerValue" }, "multiValueHeaders": { "headerName": ["headerValue"] }, "body": "..." }

Two fields are the ones that actually break things almost every time: statusCode and body. headers and multiValueHeaders can be left out entirely if you have no extra headers to send. isBase64Encoded is only required when you're returning binary data — leave it out or set it to false for ordinary JSON or text responses.

statusCode has to be an actual number, not a numeric string. Writing "statusCode": "200" with quotes around the 200 is a completely different data type than "statusCode": 200, and API Gateway treats the quoted version as malformed. This is the single most common version of this bug, because it's invisible in most editors and doesn't throw a language-level error — your Lambda function runs perfectly, computes the right answer, and still fails at the door.

body has to be a string. If you return your data as a raw JavaScript object or Python dictionary in the body field instead of a JSON-encoded string, that's also a malformed response — even though the data inside it is completely correct. This is why the fix almost never touches your business logic: it's a formatting step you add at the very last line of the function, right before you return.

✅ Why this is the one to fix first

Before touching IAM permissions, timeouts, or resource policies, check statusCode and body. In the overwhelming majority of "Malformed Lambda proxy response" reports, one of those two fields is the wrong data type. It's a five-second check that rules out the two most common causes before you go looking anywhere else.

The Five Ways People Break the Shape

In order of how often each one actually shows up:

  1. The body is an object, not a string. Returning {"statusCode": 200, "body": {"ticket": 44, "status": "ready"}} instead of stringifying the inner object. API Gateway needs body to be text it can hand to the client as-is.
  2. statusCode is a string. "statusCode": "200" instead of "statusCode": 200. Passes code review, fails the integration.
  3. statusCode is missing entirely. Common when a function was written for a non-proxy integration and later switched to proxy without updating the return value — it just returns the data with no envelope at all.
  4. The function throws instead of returning. An unhandled exception, a syntax error, or a thrown Error object skips your return statement completely, so there's no proxy-shaped object at all for API Gateway to read. This one shows a different-looking log line (covered below), but people still describe it as "the malformed error."
  5. A downstream call changes the return type unexpectedly. A database driver, an SDK call, or a helper function that returns undefined, None, or a non-serializable object on one code path (often an error path that only fires occasionally), so the bug is intermittent and hard to reproduce on demand.

Reading the Real Error in CloudWatch Logs

The client-facing message — {"message": "Internal server error"} — tells you nothing. The real diagnosis lives in CloudWatch Logs, and if you haven't enabled logging for this API yet, that's step one.

  1. Open the API Gateway console and select your API, then Stages, then the stage the request is hitting. Under Logs and tracing, choose Edit and turn on a CloudWatch Logs level (for full request and response detail, turn on the data tracing option as well — avoid leaving that on for a production stage that handles sensitive data).
  2. Set the account-level CloudWatch role. Under the API Gateway console's Settings page (not the per-API settings — the account-wide one), paste the ARN of an IAM role that has the AmazonAPIGatewayPushToCloudWatchLogs managed policy attached. Without this, logging silently produces nothing even though you turned it on in the stage.
  3. Redeploy the stage so the logging change takes effect, then trigger the request again.
  4. Open CloudWatch Logs and find the log group for your API and stage. Look for the exact line "Execution failed due to configuration error: Malformed Lambda proxy response," and check the line immediately above it — that's usually where your Lambda function's actual raw return value or thrown error is printed.

If the log instead shows a line like "Lambda execution failed with status 200 due to customer function error," that's a different problem — a runtime error inside your code, not a shape mismatch — and the fix is in your business logic, not your return statement.

The Correct Response in Node.js

For a Node.js async handler, the documented pattern is:

exports.handler = async (event) => { const responseBody = { status: "ready", ticket: 44 }; return { statusCode: 200, headers: { "my-header": "my-value" }, body: JSON.stringify(responseBody), isBase64Encoded: false }; };

The two things doing the work: statusCode: 200 with no quotes around the number, and body: JSON.stringify(responseBody) so the body arrives as a string. If you're using the older callback-style handler instead of async/await, the equivalent successful call is callback(null, {"statusCode": 200, "body": "results"}). To throw a genuine server error, you call callback(new Error("internal server error")) rather than trying to hand-build an error envelope yourself — for a client-side error where you still want control over the status code, return the object directly instead of throwing: callback(null, {"statusCode": 400, "body": "Missing parameters"}).

The Correct Response in Python

The same required envelope applies regardless of runtime — it's an API Gateway contract, not a language feature. In Python, the equivalent of JSON.stringify() is json.dumps(), and it's easy to skip because a Python dictionary prints so similarly to JSON that the bug is invisible until it hits API Gateway:

import json def lambda_handler(event, context): response_body = {"status": "ready", "ticket": 44} return {"statusCode": 200, "headers": {"my-header": "my-value"}, "body": json.dumps(response_body), "isBase64Encoded": False}

Note the capitalization difference that trips people up moving between languages: Python's boolean is False, not false. That specific typo doesn't usually cause the malformed-response error itself (the JSON encoder handles the conversion correctly), but it's a sign of the same underlying habit — treating the return value as "close enough" to the required shape instead of matching it field for field.

REST APIs vs HTTP APIs: Format 1.0 vs 2.0

Everything above describes a REST API (payload format 1.0). If your project actually uses API Gateway's newer, cheaper HTTP API type instead, the rules loosen — and copying a REST API example into an HTTP API project is its own way to trigger this error.

Detail REST API (format 1.0) HTTP API (format 2.0)
statusCode required? Yes, always No — if you return plain valid JSON with no statusCode, API Gateway infers a 200
multiValueHeaders Supported Not present — duplicate headers are comma-combined into headers instead
cookies field Not present Present — each response cookie becomes its own set-cookie header
Where to check which one you're using CLI: get-integration shows the payloadFormatVersion. Console: it's set when you create the integration; HTTP APIs default to 2.0 unless you set it explicitly.

"So if I'm on an HTTP API, I don't need statusCode at all?" Jake asked. "You can skip it," Ethan said, "but I still write it every time. Inferred behavior is the kind of thing that changes on you later, and 200 is rarely actually what you want for every response — the moment you need a 404 or a 400, you need statusCode explicitly anyway. Get in the habit now."

When the Lambda Console Test Passes but API Gateway Still Fails

This is the part that makes people doubt their own diagnosis. You open the Lambda function in the console, hit Test with a sample event, and it succeeds — green checkmark, a return value that looks completely reasonable. Then you call it through API Gateway and get the 502 again.

The Lambda console's Test button only tells you the function ran without throwing and shows you whatever it returned. It has no idea what API Gateway expects that return value to look like — it's not validating against the proxy integration's schema at all. A function that returns a bare object with no statusCode or body will pass the console test every single time, because as far as Lambda is concerned, that's a perfectly valid return value. The mismatch only appears one layer up, at API Gateway, which is why testing the function in isolation can't catch this class of bug.

To actually reproduce the failure, use API Gateway's own Test feature on the method (in the console, open the method and choose Test rather than testing the Lambda function directly) — that path runs your function through the same integration API Gateway uses in production and will show you the same malformed-response failure a real client sees.

Adding CORS Headers Without Breaking the Shape

If your Lambda function is being called from a browser on a different domain, you'll eventually need to add a CORS header — and this is a second common way to accidentally break the shape, usually by putting the header in the wrong place. To enable CORS on a Lambda proxy integration, add Access-Control-Allow-Origin to the headers object inside your return value (not as a separate top-level field, and not by trying to set it on multiValueHeaders only unless you actually need multiple values for that header name):

return { statusCode: 200, headers: { "Access-Control-Allow-Origin": "*" }, body: JSON.stringify(responseBody) };

If you specify the same header in both headers and multiValueHeaders, API Gateway merges them, and the value from multiValueHeaders wins if the two disagree — worth knowing if you ever get a CORS header that seems to be "the wrong value" even though you set it correctly in one of the two places.

Lambda Authorizers Can Throw This Too

If your method uses a Lambda authorizer (formerly called a custom authorizer) in front of the actual integration, a malformed response from the authorizer function produces a similar-looking failure, but it's a completely separate function with its own expected shape — an authorizer must return an IAM policy document and a principal ID, not the statusCode/body envelope described above. If your logs point to the authorizer rather than your main integration Lambda, stop looking at the code above entirely; you're debugging a different contract.

The Adjacent Task: Returning Binary Data Correctly

Jake's next request from a customer was a PDF repair invoice served straight from the same Lambda function, and he hit a version of this error again within the week. Returning binary data through a Lambda proxy integration has an extra requirement on top of everything above: you must base64-encode the function's response body, set isBase64Encoded: true, and separately configure the API's binary media types so API Gateway knows to treat that content type as binary rather than text.

To set the binary media types, open the API in the API Gateway console, choose API settings, then Manage media types under Binary Media Types, and add the specific content type — for example application/pdf — or use */* to cover every content type if you don't control the Accept header the client sends (this matters for browser requests, where you often can't control header order). Skipping this step is a separate failure mode from the shape mismatch covered above: your envelope can be perfectly formed and you'll still get garbled output, because API Gateway doesn't know to decode the base64 string back into binary before it reaches the client.

🙋‍♂️ Jake's Reality Check

"I got the statusCode and body right this time. Why is my PDF still coming through as garbage text?"

Because binary is a second, separate setting. A correctly shaped envelope with isBase64Encoded: true only tells API Gateway your body is encoded — you still have to tell the API itself which content types are allowed to be treated as binary in the first place, or it will pass your base64 string through as plain text.

When a Non-Proxy Integration Is the Better Fit

"Every tutorial I've read just tells me to check the proxy box and move on," Jake said. "Is there ever a reason not to?" Ethan's answer: yes, and it's worth knowing before you build the next ten endpoints the same way. Lambda proxy integration hands your function full control over the response envelope, which is powerful but means every function you write has to get the shape right, every time, forever. A non-proxy (custom) integration puts that responsibility back on API Gateway's mapping templates instead.

  Lambda proxy (AWS_PROXY) Non-proxy / custom integration
Who defines the response shape Your Lambda function, every time it returns API Gateway, via a mapping template
Default error handling You set statusCode yourself on every response Lambda errors default to 200 OK unless you map a selectionPattern regex against the errorMessage to a real HTTP status
Best fit You own the Lambda code and want speed and flexibility You don't control the Lambda function's return format, or you want a stable public API contract that doesn't shift every time someone edits a handler

A non-proxy integration isn't a fix for this specific error — you can still misconfigure a mapping template — but it moves the point of failure. Instead of every Lambda function needing to remember the exact envelope, one mapping template enforces it centrally. For a single internal tool like Jake's repair-status lookup, that's more setup than it's worth. For a public API with many contributors writing many functions against the same contract, it's often the safer default.

Catching This Before You Deploy

Because the Lambda console's Test button doesn't catch this class of bug, and redeploying to API Gateway just to check a return value is slow, the more reliable habit is to test through something that actually simulates the HTTP layer before you deploy at all. If you're using the AWS SAM CLI, the sam local start-api command runs your Lambda functions behind a local HTTP server that emulates API Gateway, so a malformed response shows up as a real broken HTTP response on your machine — the same way it would in production — instead of silently passing a function-only test.

For a lighter-weight habit that doesn't require Docker or a local server, a one-line assertion at the end of your handler catches most of the five causes listed earlier before the function ever leaves your laptop: check that the return value has a numeric statusCode and that body is a string, and throw loudly in development if either check fails. It's a few lines of code, and it turns a 502 discovered by a customer into an error discovered by you.

When It Isn't a Format Problem at All

A 502 from API Gateway isn't always the malformed-response error specifically — other causes produce a 502 with a different log message, and it's worth knowing the difference so you don't spend an hour reformatting a return value that was never the problem.

⚠️ What these other causes actually break

A timeout, a missing invoke permission, or a runtime crash all produce a 502 too, but the CloudWatch log line for each looks different from "Malformed Lambda proxy response." A permission problem often shows as a customer function error like a permission-denied error reading a file the function doesn't have access to. A timeout shows the function simply not completing within the integration's configured limit. Reformatting your return object won't fix either of those — check the exact log line before you start editing code.

Two of the most common non-format causes: the Lambda function's resource policy doesn't allow API Gateway to invoke it (fixable by re-creating the trigger through the console, which sets the permission automatically, or adding it directly with the Lambda add-permission CLI command), and the function simply runs longer than the integration's timeout allows. HTTP API Lambda integrations default to a 29-second timeout for the integration itself, separate from your Lambda function's own configured timeout — if your function is doing something slow, both limits matter.

Which Fix Applies to You

What the log shows Likely cause Where to look
"Malformed Lambda proxy response," function ran without error body not stringified, or statusCode wrong type/missing The final return statement
"Lambda execution failed with status 200 due to customer function error" Runtime exception inside your code (permissions, missing module, bad input) Your business logic, not the return shape
502 with no execution log at all Invoke permission missing, or function timed out Resource policy / function timeout vs. integration timeout
Works via console Test, fails via real request Console Test doesn't validate the proxy shape Test the method in API Gateway, not the function in Lambda

"I don't love that I have to memorize which log line means what," Jake said. "You don't have to memorize it," Ethan said. "You have to check it, every time, before you touch code. That's the whole habit. People lose an hour reformatting a perfectly fine return value because they assumed 502 means one thing. It doesn't."

Frequently Asked Questions

What is Lambda proxy integration in API Gateway?

It's a setup where API Gateway passes an entire incoming request to a single Lambda function as one event object, and expects the function to hand back a specific JSON envelope containing the status code, headers, and body, which API Gateway then forwards to the client largely unchanged.

Why does API Gateway return "Malformed Lambda proxy response"?

Because your Lambda function's return value doesn't match the required proxy integration format — most often because the body field is an object instead of a string, or the statusCode field is a string instead of a number, or missing entirely.

What is the exact JSON format Lambda must return?

For a REST API: an object with isBase64Encoded, statusCode, headers, multiValueHeaders, and body. Only statusCode and body are truly required for most responses; the rest can be omitted if you don't need them.

Does statusCode have to be a number, or can it be a string?

It has to be a number. Writing it in quotes as a string, like "200", is a different data type and will trigger the malformed-response error even though the function ran correctly.

Do I have to JSON.stringify the body?

Yes, for a REST API. The body field must be a string, not a raw object or dictionary, because API Gateway forwards it to the client as-is rather than serializing it for you.

What happens if I forget the body field entirely?

The response is considered malformed and API Gateway returns a 502 to the client, even if your function otherwise ran without error and computed the correct answer.

Is this the same error as a Lambda timeout?

No. A timeout and a malformed response both can produce a 502, but they show different lines in CloudWatch Logs. A malformed response shows the exact "Malformed Lambda proxy response" text with the function having run successfully; a timeout shows the function simply not completing in time.

Why does it work in the Lambda console Test but fail through API Gateway?

The Lambda console's Test button only checks that your function runs without throwing an error — it doesn't validate the return value against API Gateway's required proxy format. Test the method through API Gateway's own console Test feature to catch this specific failure.

Is this different for HTTP APIs vs REST APIs (payload format 1.0 vs 2.0)?

Yes. HTTP APIs using payload format 2.0 can infer a response format for you if you return valid JSON with no statusCode, whereas REST APIs (format 1.0) require statusCode on every response. Copying a REST API example into an HTTP API project, or the reverse, is a common source of this error.

Do I need multiValueHeaders?

Only if you need to send multiple values for the same header name, or if you're on an HTTP API, where multiValueHeaders doesn't exist at all — duplicate headers are combined with commas into the regular headers field instead.

How do I add CORS headers without breaking the format?

Add the Access-Control-Allow-Origin header inside the headers object of your return value, alongside statusCode and body, rather than as a separate top-level field on the response.

What if my Lambda function is written in Python — does the format change?

The required fields are identical. The only practical difference is that you use json.dumps() instead of JSON.stringify() to turn your response data into the required string for the body field, and Python's boolean is capitalized (False, not false).

Why do I get "Internal server error" as the client response when the real problem is different?

API Gateway shows a generic message to the client whenever it can't safely forward your Lambda function's actual output, whether the real cause is a malformed response, a runtime exception, a timeout, or a permissions problem. The generic message doesn't distinguish between these — CloudWatch Logs does.

Is this the same as Amazon Lex's Lambda response format?

No. Amazon Lex, the chatbot-building service, uses its own Lambda fulfillment response format for conversational responses, which is entirely separate from the API Gateway proxy integration format described in this post. If you're building a Lex bot rather than a REST or HTTP API, this article doesn't apply to your response shape.

Can a Lambda authorizer cause this same error?

A Lambda authorizer failing produces a similarly generic-looking failure for the client, but it's governed by a different contract — the authorizer must return an IAM policy document and principal ID, not the statusCode/body envelope used by the main integration function. Check which function the logs actually point to before assuming it's the same fix.

Where do I actually see this exact error message?

In Amazon CloudWatch Logs, in the log group for your API's stage, once you've enabled execution logging on that stage and set an account-level CloudWatch IAM role in API Gateway's settings.

Revision note. Written August 2026, covering API Gateway REST APIs (payload format 1.0) and HTTP APIs (payload format 2.0) with Lambda proxy integration. This will need a fresh look if API Gateway changes how it infers response shape for HTTP APIs. If you've been staring at a repair-ticket-shaped or invoice-shaped bug for the last hour convinced your logic was wrong, we hope this saved you the rest of your evening.

Related