I’m trying to have a single workflow that can be called either by a Webhook Trigger (for external HTTP requests) or by a When Executed by Another Workflow (to be used by other flows within n8n).
Parsing the input works fine. For example, I can normalize my inputs within an Edit/Set node with expressions like $json.query.parameter ?? $json.parameter and determine how the workflow was invoked using !!$json.query.
The problem is returning the result. I’m wondering if what I’m attempting is not supported, short of having the webhooks also respond when last node finishes.
What is the error message (if any)?
When called from a Webhook Trigger, I need to use Respond to Webhook.
When called as a When Executed by Another Workflow, the workflow should simply finish and return the output of the last node. However, the presence of a Respond to Webhook node throws the error No Webhook node found in the workflow.
If I add an IF node before the return to determine whether to return the last result of via Respond to Webhook, then the Execute Workflow node receives two output branches (one empty and one containing the actual data), which causes problems because the parent workflow now receives two branches instead of a single result.
@ThomasLu_EarthDaily yeah youve basically found it. Respond to Webhook needs a live webhook in the execution, so it throws on the sub-workflow path, and branching around it gives you the two-output problem. set the Webhook nodes Respond option to “When Last Node Finishes” and delete the Respond to Webhook node. then the webhook returns the last nodes output by itself, exactly like the sub-workflow return, so both entry points behave the same with one clean output. the only tradeoff is you lose the custom status code/headers Respond to Webhook gives you, if you dont need those this is the clean path.
As an AI Automation Engineer building modular, multi-tenant workflows on n8n daily, I encounter this architectural challenge all the time. Building reusable sub-workflows that also double as independent webhooks is a massive time-saver, but you’ve hit the exact structural bottleneck that happens when mixing execution contexts.
@achamm’s suggestion is absolutely the cleanest native approach if your payload requirements are simple: switching the Webhook Trigger’s response mode to When Last Node Finishes removes the need for the conditional branch entirely.
However, if you are building this as a production-grade API for clients and strictly need custom HTTP status codes or response headers from the webhook execution path, you can circumvent the “two output branches” problem in the parent workflow with a slight structural redesign.
Here are the two best ways to handle this depending on your requirements:
Method 1: The Code Node “Payload Wrap” (Best for Custom HTTP Responses)
If you must keep the Respond to Webhook node to pass specific status codes or headers, the reason your parent workflow is receiving an empty branch is that the final output data stream is splitting. You can bypass this by executing a single Javascript line right before your workflow ends.
Keep your IF node that checks how the workflow was invoked.
On the true path (Webhook), route it straight to your Respond to Webhook node.
On the false path (Sub-workflow), route it to a Code node set to Run Once for All Items.
Inside the Code node, return the structured data explicitly:
JavaScript
return $input.all();
In your parent workflow’s Execute Workflow node, ensure you target the output of this specific Code node using expressions rather than relying on the default node output, or simply let the sub-workflow terminate naturally on that single active branch.
Method 2: Separate the Inbound Triggers from the Core Logic (Best Practice)
For large-scale or enterprise automation setups, the most stable architecture is to decouple the entry point entirely from the functional logic. Instead of packing both triggers into one canvas, split them into two micro-workflows and one master engine:
Flow A (The Webhook Gateway): Contains only the Webhook Trigger node $\rightarrow$ Execute Workflow (Engine) $\rightarrow$ Respond to Webhook.
Flow B (The Sub-Workflow Gateway): Contains only the “When Executed by Another Workflow” trigger $\rightarrow$ Execute Workflow (Engine).
Flow C (The Core Engine): Contains the actual processing steps, API calls, and data transformations, ending with a clean data payload.
Why this pays off:
It completely eliminates conditional logic checks inside your core workflow.
If you ever need to change your HTTP status codes, you only touch the lightweight Gateway flow without risking breaking your core automation engine.
Debugging executions in the n8n execution history becomes drastically easier because your webhook traffic and internal sub-workflow traffic are explicitly separated.
If you don’t need fancy response headers, go with @achamm’s solution and switch to When Last Node Finishes. If you do, try the decoupled gateway architecture—it will save you a ton of maintenance headaches down the road!