Webhook Sink issues

Hi team,

A couple of issues with the Webhook Sink that I discovered when building my project:

Issue 1: Authentication issue due to Authorization request header

  1. I was unable to authenticate to my external system (Elasticsearch - Authentication | Elasticsearch API documentation ) by using the below in the Request headers:

{
  "Authorization": "ApiKey $inputs.api_key"
}

When running the workflow and the Webhook Sink block, I was simply getting a 401 from Elasticsearch.

In order to make the authentication work with the Webhook Sink, I had to build a custom block to “format” the authorization header value:

def run(self, api_key) -> BlockResult:
    formatted_api_key = f"ApiKey {api_key}"
    return { "formatted_api_key": formatted_api_key }

and reuse the formatted_api_key in the Request headers by defining:

{
  "Authorization": "$steps.my_custom_block.formatted_api_key"
} 

This approach worked.

I suspect that there is a problem with the Execution Engine where the authorization header value is not correctly interpreted when defined with:

{
  "Authorization": "ApiKey $inputs.api_key"
}

Issue 2: Getting a 400 on a simple GET request

In Elasticsearch, many APIs do not require any request parameters or any JSON payload.

When attempting to run a Webhook Sink to run a simple request like GET _cluster/health (c.f doc), Elasticsearch returns a 400. When configured with GET and no query parameters, no JSON payload, I suspect the Webhook Sink is actually sending an empty JSON payload and this will be considered as a bad request in Elasticsearch.

I just wanted to bring these up for review and considerations for further fixes/improvements.

Thank you team :slight_smile:

Hi @ropc

Issue 1

The string interpolation only works when the entire header value is a reference (like $inputs.api_key), not when the reference is embedded within other text (like "ApiKey $inputs.api_key").

Your workaround is a good approach - using a custom block to pre-format the authorization string:

def run(self, api_key) -> BlockResult:
    formatted_api_key = f"ApiKey {api_key}"
    return { "formatted_api_key": formatted_api_key }

Issue 2

This is likely the Webhook Sink sending an empty JSON body or Content-Type: application/json header even when no payload is configured for GET requests. Elasticsearch (and many APIs) will reject GET requests that contain any body content.

If possible, check if Elasticsearch offers a POST-based alternative for the health check endpoint, or consider using a custom HTTP block that gives you more control over the request.

Hi @Patrick_Nihranz - thank you for getting back to me.

The string interpolation only works when the entire header value is a reference (like $inputs.api_key), not when the reference is embedded within other text (like "ApiKey $inputs.api_key").

That is what I thought. In this case, would it make sense to have some sort of validation in the workflow itself and provide some errors/warnings to the user?

This is likely the Webhook Sink sending an empty JSON body or Content-Type: application/json header even when no payload is configured for GET requests.

I believe the Webhook Sink is actually sending an empty JSON body for a GET request when no payload is defined in the Webhook Sink block. From a logic perspective, if no payload is defined in this block, we should probably not send an empty JSON block. What do you think?

Hi @ropc ,

Just to clarify and readdress the ways we can fix these issues.

Issue 1 The execution engine’s selector detection only recognizes values that start with $. When you write:

{“Authorization”: “ApiKey $inputs.api_key”}

The value starts with "ApiKey ", not $, so the entire string is treated as a literal - no substitution occurs. The $inputs.api_key part is never resolved. Your workaround using a custom block works because “$steps.my_custom_block.formatted_api_key” starts with $, so it gets properly resolved. This is why the workaround with a custom block you identified works

Issue 2

Two things combine to cause this:

1. json_payload defaults to an empty dict {} when not specified

2. The request always includes the json parameter regardless of HTTP method

So for a GET request with no payload:

  • json_payload becomes {}

  • The requests library sends a body containing {} with Content-Type:

    application/json

  • Elasticsearch (and many REST APIs) reject GET requests with a body,

    returning 400

    Unfortunately there’s no clean workaround within the current Webhook Sink.

    Options:

  • Use a custom HTTP block that omits the body for GET requests

  • Use POST if your API supports it for the same endpoint

    Let me know if you need any additional details!