2025, Oct 15 13:50

Amazon Redshift Lambda UDF error 'Invalid External Function Response': how to return JSON results correctly

Learn why Amazon Redshift Lambda UDFs fail with 'Invalid External Function Response' and how to fix it by returning JSON with a results array matching inputs.

Running a Lambda UDF from Amazon Redshift and getting “Invalid External Function Response” is a common stumbling block. The code runs in Lambda, prints fine, but Redshift refuses to parse what comes back. The root cause is almost always the shape and type of the response, not the business logic.

Problem setup

Consider a minimal handler that returns a constant and an external function defined in Redshift. A straightforward approach might look like this.

def handle(event_obj, ctx_obj):
    print("Got payload:", event_obj)
    num = 42
    print("Sending value:", num, "kind:", type(num))
    payload = [[num]]
    print("Payload to send back:", payload)
    return payload

And the external function in Redshift:

CREATE OR REPLACE EXTERNAL FUNCTION demo_fn()
RETURNS INT
VOLATILE
LAMBDA 'arn:aws:.........'
IAM_ROLE 'arn:aws:iam::........';

Calling it with SELECT demo_fn(); raises the error:

ERROR: Invalid External Function Response
Detail:
Cannot parse External Function response

What’s actually going wrong

Redshift Lambda UDFs must return a JSON string with a very specific structure. The top level must contain a field named results, and its value must be an array containing exactly one element per input row that Redshift batches into event["arguments"]. If the handler returns a bare value, a Python list, or anything that is not a JSON string like {"results": [...]}, Redshift can’t parse it and raises “Cannot parse External Function response”.

In other words, the contract is strict: return json.dumps({"results": [...]}) where the length of that array matches len(event["arguments"]). For a no-argument function, Redshift still sends rows in event["arguments"], for example [ [] ] when you execute SELECT demo_fn(); so your results must contain one element for that single row.

The fix

Return a JSON string with the expected envelope and make sure the number of outputs equals the number of inputs Redshift sent.

import json
def handle(event_obj, ctx_obj):
    # Redshift batches rows in event_obj["arguments"]
    items = event_obj["arguments"]          # e.g., [ [] ] for SELECT demo_fn();
    outputs = [42] * len(items)              # one output per input row
    return json.dumps({"results": outputs})

This minimal handler satisfies the contract for a no-argument UDF that returns an INT. The SQL definition doesn’t need to change if the return type already matches the values you place inside results.

Why this matters

The error isn’t about permissions or runtime; it’s the response contract. Returning a properly formatted JSON string with the top-level key named results prevents the “Cannot parse External Function response” failure. Aligning the results array length to event["arguments"] ensures Redshift has a one-to-one mapping between input rows and output scalars. Finally, the SQL UDF’s declared return type needs to match the values in results; for example, if RETURNS INT is declared, the elements must be integers.

Wrap-up

When wiring Redshift to Lambda, focus on the response envelope. Always serialize the payload with json.dumps, place outputs under the results key, and produce exactly one value per input row in event["arguments"]. Keep the SQL return type aligned with those values. With that in place, SELECT demo_fn(); will return the expected 42 without parse errors.

The article is based on a question from StackOverflow by Stephen Saidani and an answer by Pradipta Dash.