Help! Evolution API throws 400 Bad Request [object Object] only on the 2nd item inside an n8n Loop

Hey everyone, I’m pulling my hair out over a weird interaction between n8n and Evolution API.

I have a workflow that processes multiple contacts using the “Loop Over Items” node. Inside the loop, I’m trying to send a presence update (typing/paused) via Evolution API. I’ve tried using both the Evolution API Community Node and a manual HTTP Request node.

Here is the weird part:

  • Item 1: Executes perfectly.

  • Item 2 (and onwards): Fails with this error: 400 - {"status":400,"error":"Bad Request","response":{"message":["[object Object]"]}}

It seems like on the second iteration, n8n or the API parses the target number/delay parameter as an Object instead of a primitive String/Number, even though it’s the exact same node executing it.

What I’ve tried so far (and failed):

  1. Forcing the data type to string using {{ String($json.phone_clean) }} and {{ $json.phone_clean.toString() }}.

  2. Appending the JID manually like {{ $json.phone_clean }}@s.whatsapp.net.

  3. Switching from raw JSON to “Using Fields Below” in the HTTP Request node to bypass n8n’s JSON parsing inside loops.

Has anyone experienced this specific issue? Does Evolution API (or n8n’s auto-casting) behave differently when evaluating expressions inside a loop? How do I force it to consistently send the correct data type for every item in the loop without it reverting to an object?

Any insights or workarounds would be greatly appreciated. Thanks!

Hi @bytegir! :waving_hand:
This is a classic JavaScript and n8n loop error.

Seeing [object Object] in a 400 error means one thing: JavaScript tried to convert an actual Object (like {}) or an Array into a string, and it failed.
Since Item 1 works perfectly but Item 2 fails, the issue is happening during n8n’s expression evaluation on the second loop iteration.

Here are the two most likely reasons:

1: The Data Shape of Item 2 is Different

Even though you expect phone_clean to be a string for every item, data from APIs or databases can sometimes be inconsistent. It is highly likely that Item 1’s phone_clean is a standard string (for example"123456789"), but Item 2’s phone_clean is actually a nested JSON object or array (for example { "number": "987654321" }).

When n8n evaluates {{ String({ "number": "987654321" }) }}, JavaScript outputs the literal string "[object Object]". Evolution API receives this, fails its regex validation for a phone number, and throws the 400 Bad Request.

The Solution for 1:

Open the node immediately before your Loop node.
Look at the JSON view for Item 2. Check exactly how phone_clean is formatted.
If it is an object, you need to update your expression to target the nested value (e.g., {{ $json.phone_clean.number }}) or sanitize the data in a Code node before it enters the loop.

2: Context Loss

While the post mentions {{ $json.phone_clean }}, if your expression is actually referencing a node from outside the loop (for example:{{ $('Set Phone Numbers').item.json.phone_clean }}), n8n’s item linking will break on the second iteration.

Why: On the second loop iteration, n8n is processing “Index 1”. If it looks back at a previous node that only has “Index 0” (one item), it can’t find a 1 to 1 match. To prevent crashing, n8n falls back to returning an array of all items from that previous node. An array of objects stringified becomes [object Object].

The Solution for 2:

If you must reference data from outside the loop, force n8n to grab the first item explicitly using the $first() or $items() method, which bypasses the index matching:

{{ $first('Node Name').json.phone_clean }}

Tip:

Place a Code node directly inside your loop, right before the HTTP Request, with this single line:

console.log("Item Type: ", typeof item.json.phone_clean, " | Value: ", item.json.phone_clean);

Use your browser console (key F12) when it runs. If the second item prints Item Type: object,

Hi @bytegir, as @Haian_Abou-Karam noted it’s an expression eval issue — the gotcha he didn’t cover is that inside Loop Over Items, $json on iteration 2+ sometimes points to the previous HTTP response instead of the current loop item, so your $json.target_phone resolves to the response object. Reference the loop node explicitly with $('Loop Over Items').item.json.target_phone and cast delay to Number:

loop the HTTP back into the SplitInBatches input — that’s what resets $json properly each iteration.