Always the same tool response
# support
r
I'm building a workflow in n8n where a VAPI tool call hits a webhook, processes some data, and responds back to VAPI using a "Respond to Webhook" node. The issue is with the "Respond to Webhook" node. VAPI expects a specific JSON structure in the response, where one of the fields must be a dynamic value (the tool call ID) and another must be a stringified JSON object built from processed data. When I set "Respond With" to JSON, the node throws "Invalid JSON in Response Body" as soon as I use any expression inside the body. When I set "Respond With" to Text, the expressions evaluate correctly but VAPI returns "No result returned", suggesting the response isn't being parsed correctly on their end. What is the correct way to build a dynamic JSON response body with expressions in the "Respond to Webhook" node? https://cdn.discordapp.com/attachments/1512102019798667334/1512102020272750733/image.png?ex=6a22de3f&is=6a218cbf&hm=bc61f08f3e1a60686bc5095a2e7039b8c63e22f85e4d3f32aa4027e0dc483ab2& https://cdn.discordapp.com/attachments/1512102019798667334/1512102021094838363/image.png?ex=6a22de3f&is=6a218cbf&hm=2b09c0f73f82509de1023c1c5e4372e52c2373bbe3f6c9887d477c18e60e2004&
j
You’re very close, the real issue isn’t just syntax, it’s how n8n resolves .item vs merged array context, which is why VAPI ends up getting inconsistent toolCallId binding. A safer approach is to avoid index-based access entirely and normalize both payloads into a single item before the Respond node. I can help you fix this cleanly so it’s stable with VAPI’s expected schema Quick one, are you merging both branches into a single item or still passing them as separate items into the Respond node? @Rodrigo
r
The Merge node is already returning only 1 item, and it is still returning the same response as before
c
Hi Rodrigo, Good progress, the single-item merge is correct and the toolCallId is being picked up. The remaining issue is in how the response body is being built. Using template placeholders like
{{ JSON.stringify($json) }}
inside hand-written JSON breaks the response when the substituted value itself contains double quotes. The inner quotes do not get escaped, so Vapi receives invalid JSON and cannot parse the response. Replace your entire Response Body with this single line: ={{ JSON.stringify({ results: [{ toolCallId: json.toolCallId,result:JSON.stringify(json.toolCallId, result: JSON.stringify( json.toolCallId,result:JSON.stringify(json) }] }) }} Make sure the field starts with = so n8n treats it as an expression rather than plain text. The outer JSON.stringify handles all the escaping correctly, so Vapi receives properly formatted JSON every time. Keep the Content-Type header as application/json and keep Respond With set to Text. That should resolve it. Let me know how it goes. Regards, Chiranjeet Vapi Support
r
Hello
It does not function
c
Hi Rodrigo, The expression itself is now correct and the toolCallId is being picked up properly. There is one more thing to fix upstream. Open your Webhook trigger node (the one at the beginning of the workflow, not the Respond to Webhook node). Inside it you will find a setting called "Respond." Change that value from "Immediately" to "Using Respond to Webhook Node." When it is set to "Immediately," n8n sends an automatic empty response to Vapi the moment the webhook fires, before your workflow finishes processing. Vapi receives that empty response first, cannot find a tool result in it, and moves on. Your Respond to Webhook node fires correctly afterward but Vapi has already given up waiting. Changing this setting tells n8n to hold the response open until your Respond to Webhook node actually runs. That way Vapi receives your correctly built payload. Give that a try and let me know. Regards, Chiranjeet Vapi Support
r
It was already actived
id doesn't work
c
Hi Rodrigo, To figure out what is happening on the Vapi side, I need a call ID from one of the failing test runs. You can find it in your Vapi dashboard under Observe > Call Logs. It will look something like
01JXYZ...
(a long alphanumeric string), listed next to each call entry. Note that this is different from the toolCallId values inside your n8n workflow. While you grab that, also check two things in n8n: 1. Open the execution history for this workflow and confirm every node ran to completion with no errors. If the workflow is failing on an earlier node, the Respond to Webhook node may never actually fire. 2. Confirm that the webhook URL configured in your Vapi tool matches the n8n URL you are testing with. n8n uses a different URL for test mode (/webhook-test/) versus live production mode (/webhook/). These need to match. Once you share the call ID I can check what Vapi actually received and pinpoint exactly where it is breaking down. Regards, Chiranjeet Vapi Support
r
019e9392-dc80-7ffa-85f0-bc3dd086e0ad
c
Hi Rodrigo, After going through the call logs and your assistant configuration, I found two separate issues worth addressing. First, in the call you shared, get_today was never called at all. The assistant went straight to a text response in turn two without triggering the tool. Looking at your assistant config, all 29 of your tools have empty description fields. The model has to figure out when to call each tool purely from the system prompt, which is already very long. With 29 tools that look structurally similar and no descriptions to guide selection, gpt-4.1-mini can skip calls it should be making. Adding a short description to get_today will help significantly. Something like "Llama a esta tool cuando el cliente mencione un servicio, una fecha concreta o una hora" is enough for the model to reliably trigger it. Second, the "No result returned" you saw in your screenshots is a separate situation where get_today did fire but came back empty. That is an n8n side issue and I still need a call ID from a test where that happened to trace it properly. In Observe > Call Logs, look for a call where the logs show a server URL request event and the response shows "No result returned." Share that call ID and I can dig into what happened on our end. So the short version: fix the tool descriptions first, then share a call ID from a test where the n8n response was empty. Regards, Chiranjeet Vapi Support
r
019e9d74-63ba-7000-8e02-8367ba82bb56
It failed again, but the n8n workflow has been executed like the other time
c
Hi Rodrigo, Good progress. The description fix worked: get_today is now firing correctly. I can see in the logs that Vapi sent the tool call to your n8n webhook with the right arguments, including servicio "corte," fecha "mañana," and hora "11." The remaining issue is in what n8n sends back. Vapi received a response from n8n but couldn't use it, which is why you see "No result returned." This almost always means the response body format is slightly off. Vapi requires this exact structure: json{ "results": [
Copy code
{
  "toolCallId": "<the id from the incoming request>",
  "result": "<your tool output>"
}
] } The toolCallId must match exactly what Vapi sent. In n8n, that value arrives in the webhook body and can be read as {{ $json.message.toolCalls[0].id }}. Can you check the execution history in n8n for the run that happened during this test, and share what your Respond to Webhook node is actually sending back? That will let me confirm whether the format matches or point to the exact field causing the mismatch. Regards, Chiranjeet Vapi Support
This is what my respond to webhook node is sending back
c
Hi Rodrigo, I looked at the screenshots. The structure and toolCallId look correct. There is one setting worth changing though: in the Respond to Webhook node, "Respond With" is set to "Text." Change this to "JSON." With JSON mode, n8n handles serialization and Content-Type automatically, which is more reliable. When you switch to JSON, also remove the outer JSON.stringify wrapper, since n8n handles that itself. Your expression should look like this: ={{ { results: [
Copy code
{

  toolCallId: $json.toolCallId,

  result: JSON.stringify($json)

}
] } }} After making that change, test it with a real call (not just the n8n test button) and share the call ID. I want to confirm Vapi receives and accepts the response during an actual conversation. Regards, Chiranjeet Vapi Support
r
Hi, I made the change as suggested: the Respond to Webhook node is now set to JSON, and I used the structure you provided without the outer JSON.stringify wrapper. However, the issue still persists. Vapi still shows “No result returned” exactly as before, even though the workflow runs and the response structure appears correct in n8n. Could you please check whether Vapi is actually receiving/rejecting the webhook response, or if this is still a UI/logging issue? Thanks.
c
Hi Rodrigo, I checked the Vapi logs for your latest test. Vapi received your n8n response successfully (HTTP 200), but immediately logged "No result returned." This happens when the toolCallId in your response doesn't match the one Vapi sent for that specific call. Here is the problem: every call generates a unique toolCallId. Vapi sent call_Edr4oD4OG0hCeXakDNIJBYgQ for your latest test, but n8n sent back a different, stale value. Your result preview in n8n shows call_q6HkojXRum4AJ5cT8A7tLfdJ which was from an older test run. That value is being used instead of the current one. I also owe you a correction. In an earlier message I told you the toolCallId arrives at $json.message.toolCalls[0].id. That was wrong. In n8n, Vapi's webhook payload comes in without a message wrapper, so the correct path is $json.toolCalls[0].id. The most reliable fix is to read the toolCallId directly from the original webhook payload inside the Respond to Webhook node, bypassing any intermediate node state. In your Respond to Webhook node (JSON mode), change the Response Body to: ={{ { results: [{
Copy code
toolCallId: $('Webhook').item.json.toolCalls\[0\].id,

result: JSON.stringify($json)
}] } }} $('Webhook') references your Webhook trigger node by name and reads the live data from the actual current request every time, so the toolCallId will always match what Vapi sent. Please also check your Edit Fields or Code node upstream. If any of those are extracting toolCallId using $json.message.toolCalls[0].id, change that to $json.toolCalls[0].id as well. After making this change, run a new test call and share the call ID. That will confirm whether the mismatch is resolved. Apologies for the incorrect path earlier. Regards, Chiranjeet Vapi Support
r
Hello friend, i've just solved it, thank you