HTTP Request Node Fails with 400 Error, While cURL/PowerShell Succeed

Hi community,
I’m facing a puzzling issue with the HTTP Request node and would appreciate any insights.
I’m sending a POST request to a custom internal API endpoint that requires an API-KEY header. When I send the request from the n8n HTTP Request node, it consistently fails with a 400 Bad Request.
However, the key issue is that an identical request sent via cURL and PowerShell works perfectly, returning a 200 OK status with the expected JSON data.
Here is the working cURL command:
Bash
curl --location ‘MY_URL’
–header ‘API-KEY: <MY_API_KEY>’
–header ‘Content-Type: application/json’
–data ‘{
“jsonrpc”: “2.0”,
“method”: “call”,
“params”: {
“params”: {
“model”: “res.users”,
“method”: “search_read”,
“args”: ,
“kwargs”: {
“domain”: [[“id”, “!=”, 0]],
“fields”: [“name”],
“limit”: 2
}
}
}
}’
My n8n node is configured to replicate this exactly:
URL, Method, and Headers are identical.
The Body is generated via an expression to produce the exact JSON payload shown above.
My question is: Are there any known nuances with the HTTP Request node that might cause it to send a request differently than cURL? For example, subtle changes to default headers, character encoding, or the handling of the JSON body that would result in a 400 error while other clients succeed?
I’m using a self-hosted n8n instance, version 1.106.3.
Thanks for any help!

Hey @Lorena_Lopez hope all is well.

It isn’t possible to definitively answer the question without looking at both requests and comparing them. Would you like to provide a reproducible example of the failing call with an HTTP Request node as well as successful curl command to compare it to?

Thanks for the quick response.

I understand the need for a reproducible example. Unfortunately, I can’t provide live access or the full workflow due to project confidentiality.

However, I can provide the exact, anonymized setup for both the successful cURL command and the failing HTTP Request node. The structure and logic are identical to my real case.


1. Successful cURL Command

This command works perfectly every time and returns a 200 OK with the expected data from our server.

Bash

# This request is successful
curl --location 'https://my-custom-api.example.com/api/call' \
--header 'API-KEY: MY_SECRET_API_KEY' \
--header 'Content-Type: application/json' \
--data '{
    "jsonrpc": "2.0",
    "method": "call",
    "params": {
        "params": {
            "model": "some.model",
            "method": "search_read",
            "args": [],
            "kwargs": {
                "domain": [["id", "!=", 0]],
                "fields": ["name", "email"],
                "limit": 2
            }
        }
    }
}'


2. Failing n8n HTTP Request Node Setup

This is how the node is configured. It is intended to generate the exact same request as the cURL command above, but it consistently fails with a 400 Bad Request.

  • Node: HTTP Request

  • Method: POST

  • URL: https://my-custom-api.example.com/api/call

  • Authentication: Header Auth

    • Name: API-KEY

    • Value: MY_SECRET_API_KEY

  • Body Content Type: Raw (application/json)

  • Body (Expression):

    JavaScript

    const kwargs = {
        "domain": $json.domain || [["id", "!=", 0]],
        "fields": $json.fields || ["name", "email"],
        "limit": ($json.limit === null || $json.limit === undefined) ? false : $json.limit,
    };
    
    const innerParams = {
        "model": $json.resource || "some.model",
        "method": $json.method || "search_read",
        "args": [],
        "kwargs": kwargs
    };
    
    const body = {
        "jsonrpc": "2.0",
        "method": "call",
        "params": {
            "params": innerParams
        }
    };
    
    return JSON.stringify(body);
    
    

The puzzle remains: why does the n8n node fail when it’s configured to send what appears to be an identical request to the successful cURL command? My theory is that there must be a subtle, underlying difference in how n8n constructs and sends the final HTTP request.

Any insights on what that difference might be would be greatly appreciated.
Thanks!

The reason this doesn’t work is you are putting naked javascript to the POST request body. If you want to pre-build the request body, do it in the Code node instead. For instance like so:

Alternatively you could put the code into the HTTP request body, like so, but in my opinion this has very little advantage over building your json separately in the code node.

Hi Jabbson,

I’m writing to follow up on my post and say a huge thank you for your help.

Your suggestion to use a Code node to pre-build the JSON body was the key to solving the problem. The issue wasn’t the JavaScript logic itself, but where it was being executed. By moving the logic into a separate Code node and then passing the clean, stringified JSON to the HTTP Request node, it worked perfectly.

I really appreciate you taking the time to look at my problem and provide such a clear and effective solution.

Best regards,

I appreciate the kind words.
If this helped you to solve your problem, kindly mark my answer as solution. Thank you.

Cheers

Done!

Thanks