Merge Node
The Merge node combines events from parallel branches back into a single flow. Configure it to wait for all branches to complete or proceed as soon as the first one arrives — essential for parallel processing workflows.
Overview
When your pipeline splits into multiple parallel branches (via Router or multiple chains), the Merge node brings them back together. It collects events from its connected branches and, depending on the mode, either waits for all branches to report in (wait_all) or proceeds as soon as the first branch completes (first).
The merged output is a combined payload containing results from all (or the first) branch(es). A configurable timeout ensures the Merge node does not wait indefinitely for missing branches. On the canvas, Merge nodes appear as teal rectangles.
Configuration
| Field | Type | Default | Description |
|---|---|---|---|
| name | string | — | Friendly label for the merge node |
| mode | string | all | 'all' (wait for every wired branch) or 'n_of_m' (proceed once requiredCount branches arrived) |
| requiredCount | number | — | For n_of_m: how many branches are enough |
| timeoutMinutes | number | 30 | Minutes to wait for the missing branches, counted from the FIRST arrival |
| autoActionOnTimeout | string | fail | 'fail' (the run is marked failed) or 'continue_partial' (go on with what arrived) |
| mergeStrategy | string | return_all | How the branches become one payload: 'return_all', 'array' or 'keyed' — see Merge strategy below |
| returnInput | number | — | For return_all: forward only this branch (0 = first to arrive) instead of all of them |
| correlationKeyField | string | — | Top-level payload field; branches with the same value wait for each other. With 'keyed' it also joins items — see below |
| isActive | boolean | true | Whether the merge is enabled |
| inputNodes | { nodeType, nodeId }[] | [] | Upstream nodes that feed this one. nodeType is the node kind (webhook, scheduledWorkflow, filter, …) |
all mode the merge waits until every wired branch has arrived or the timeout expires. In n_of_m mode it fires as soon as requiredCount branches are in.Merge strategy
Three ways to turn the branches into the one payload that goes downstream. Pick by what the next node needs, not by the name:
- Return all as one — the branches side by side (
branch_0,branch_1, …), or just one of them whenreturnInputpicks it. - Array (flat concat) — every item from every branch in one flat
itemslist. Three rows from one branch and one from another give four items. - Keyed (join by key field) — items that share the value of the correlation key field become one object (an inventory row and a price row with the same
sku, say); items with no match are kept as they are. When both sides carry a field, the branch that arrived later wins. Without a key field, Keyed falls back to pairing by position: item 0 of every branch becomes one object, item 1 the next — with rows 2, 3, 4 on one side and row 8 on the other you get row 2 + row 8, then rows 3 and 4 untouched.
Correlation key field
A top-level field of the payload, such as orderId. It does two separate things:
- Which branches wait for each other — with any strategy. Events whose payload carries the same value are merged together. Without the field, branches from the same run (a webhook fanned out by a Split or a Conditional) are grouped by that run, and branches from independent sources — two webhooks wired to the same Merge — pair up in arrival order: each arrival joins the oldest open wait that has not yet heard from its source, so one event per source completes the merge.
- How items are joined — only with Keyed. Items that share the value are combined into one object; the rest are appended as they are. Array is always the flat list and Return all keeps the branches apart, so neither uses the field for items.
{{ }} and no dots. The branch grouping reads the field literally, so a template or a dotted path never matches and the branches fall back to grouping by run — and with a key configured, independent sources do not pair up by arrival: the key decides what goes together.Create a Merge Node
curl -X POST /api/merge-nodes \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"workspaceId": "<your-workspace-id>",
"name": "Payment + Inventory Join",
"mode": "wait_all",
"expectedBranchCount": 2,
"timeoutSeconds": 120,
"isActive": true,
"inputNodes": [{ "nodeType": "webhook", "nodeId": "ep_payment_result" }, { "nodeType": "webhook", "nodeId": "ep_inventory_result" }]
}'Response
{
"_id": "6655d0e1f2a3b4c5d6e7f8a9",
"name": "Payment + Inventory Join",
"mode": "wait_all",
"expectedBranchCount": 2,
"timeoutSeconds": 120,
"isActive": true,
"inputNodes": [{ "nodeType": "webhook", "nodeId": "ep_payment_result" }, { "nodeType": "webhook", "nodeId": "ep_inventory_result" }],
"ownerId": "org_abc123",
"createdAt": "2025-05-01T12:00:00.000Z",
"updatedAt": "2025-05-01T12:00:00.000Z"
}Update a Merge Node
curl -X PATCH /api/merge-nodes/6655d0e1f2a3b4c5d6e7f8a9 \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"mode": "first",
"timeoutSeconds": 60
}'wait_all to first mode while events are pending may cause in-flight merges to resolve immediately. Ensure no critical branches are still in progress.Canvas Integration
The Merge node uses canvas type merge-node and renders in teal. It has multiple incoming edges (one per branch) and a single outgoing edge to the downstream node that receives the merged payload.
{
"id": "merge-6655d0e1f2a3b4c5d6e7f8a9",
"type": "merge-node",
"position": { "x": 700, "y": 300 },
"data": {
"label": "Payment + Inventory Join",
"mergeNodeId": "6655d0e1f2a3b4c5d6e7f8a9",
"mode": "wait_all",
"expectedBranchCount": 2,
"isActive": true
}
}Payload Examples
wait_all — merged output
When all branches complete, the Merge node produces a combined payload:
{
"mergeNodeId": "6655d0e1f2a3b4c5d6e7f8a9",
"mode": "wait_all",
"completedAt": "2025-05-01T12:01:30.000Z",
"branches": [
{
"branchIndex": 0,
"webhookId": "ep_payment_result",
"payload": {
"paymentId": "pay_abc123",
"status": "captured",
"amount": 5000
},
"receivedAt": "2025-05-01T12:00:45.000Z"
},
{
"branchIndex": 1,
"webhookId": "ep_inventory_result",
"payload": {
"sku": "WIDGET-001",
"reserved": true,
"warehouse": "us-east-1"
},
"receivedAt": "2025-05-01T12:01:30.000Z"
}
]
}first — immediate output
{
"mergeNodeId": "6655d0e1f2a3b4c5d6e7f8a9",
"mode": "first",
"completedAt": "2025-05-01T12:00:45.000Z",
"branches": [
{
"branchIndex": 0,
"webhookId": "ep_payment_result",
"payload": {
"paymentId": "pay_abc123",
"status": "captured",
"amount": 5000
},
"receivedAt": "2025-05-01T12:00:45.000Z"
}
]
}Timeout scenario
{
"mergeNodeId": "6655d0e1f2a3b4c5d6e7f8a9",
"mode": "wait_all",
"completedAt": "2025-05-01T12:02:00.000Z",
"timedOut": true,
"branches": [
{
"branchIndex": 0,
"webhookId": "ep_payment_result",
"payload": { "paymentId": "pay_abc123", "status": "captured" },
"receivedAt": "2025-05-01T12:00:45.000Z"
}
],
"missingBranches": [
{ "branchIndex": 1, "webhookId": "ep_inventory_result" }
]
}Use Cases
- Split/join pattern — Split an order event into payment and inventory checks, then merge results before confirming the order.
- Fastest response — Query multiple providers in parallel and use
firstmode to proceed with whichever responds first. - Data enrichment — Send to multiple enrichment services in parallel, merge their results, and deliver the enriched payload to the final destination.
- Saga orchestration — Coordinate multi-step transactions where all steps must complete before finalizing.
- Graceful degradation — Use timeouts to proceed with partial results when some branches are slow or unavailable.
API Reference
| Method | Webhook | Description |
|---|---|---|
| GET | /api/merge-nodes | List all merge nodes |
| POST | /api/merge-nodes | Create a new merge node |
| GET | /api/merge-nodes/:id | Get a specific merge node |
| PATCH | /api/merge-nodes/:id | Update a merge node |
| DELETE | /api/merge-nodes/:id | Delete a merge node |
timeoutSeconds to a value that accounts for the slowest expected branch. If a branch is unreliable, consider using first mode with a fallback strategy instead.