Workflow: develop a modular n8n workflow that supports Wait Code Update Webhook
Integrations: code, httpRequest, if, manualTrigger, noOp, respondToWebhook, set, splitInBatches, stickyNote, wait, webhook

{"nodes": [{"id": "0d911b91-bb9a-4177-8cd5-12ddddf1bc61", "name": "When clicking \u2018Test workflow\u2019", "type": "n8n-nodes-base.manualTrigger", "position": [580, 405], "parameters": {}, "typeVersion": 1}, {"id": "d13f78f7-4093-435f-8b38-722f4a5c7a1f", "name": "Loop Over Items", "type": "n8n-nodes-base.splitInBatches", "position": [1020, 405], "parameters": {"options": {}}, "typeVersion": 3}, {"id": "97d26220-a85f-4c40-b97c-b36f2d235776", "name": "Webhook Callback Wait", "type": "n8n-nodes-base.wait", "position": [1720, 445], "webhookId": "5cd058b4-48c8-449a-9c09-959a5b8a2b48", "parameters": {"resume": "webhook", "options": {}, "httpMethod": "POST", "responseMode": "responseNode"}, "typeVersion": 1.1}, {"id": "ee02d5cb-8151-4b24-a630-77a677b1434a", "name": "Update finishedSet", "type": "n8n-nodes-base.code", "position": [1940, 445], "parameters": {"jsCode": "let json = $('If All Finished').first().json;\nif (!json.finishedSet) json.finishedSet = [];\nlet finishedItemId = $('Webhook Callback Wait').item.json.body.finishedItemId;\nif (!json.finishedSet[finishedItemId]) json.finishedSet.push(finishedItemId);\nreturn [json];"}, "typeVersion": 2}, {"id": "09f1cf3f-9e32-43f2-9e57-d7a33970dac4", "name": "Initialize finishedSet", "type": "n8n-nodes-base.set", "position": [1240, 285], "parameters": {"options": {}, "assignments": {"assignments": [{"id": "193ab8f1-0e23-491c-914e-b8b26b0160f7", "name": "finishedSet", "type": "array", "value": "[]"}]}}, "executeOnce": true, "typeVersion": 3.4}, {"id": "105d8f64-8ade-4e02-8722-587a35f2b046", "name": "Simulate Multi-Item for Parallel Processing", "type": "n8n-nodes-base.code", "position": [780, 405], "parameters": {"jsCode": "return [\n  {requestId: 'req4567'},\n  {requestId: 'req8765'},\n  {requestId: 'req1234'}\n];"}, "typeVersion": 2}, {"id": "c5f72fa0-693e-4134-910f-8fd0767861d1", "name": "If All Finished", "type": "n8n-nodes-base.if", "position": [1460, 285], "parameters": {"options": {}, "conditions": {"options": {"version": 1, "leftValue": "", "caseSensitive": true, "typeValidation": "strict"}, "combinator": "and", "conditions": [{"id": "385c3149-3623-4dd2-9022-770c32f82421", "operator": {"type": "number", "operation": "gte"}, "leftValue": "={{ $json.finishedSet.length }}", "rightValue": "={{ $('Simulate Multi-Item for Parallel Processing').all().length }}"}]}}, "typeVersion": 2}, {"id": "20d16393-8573-4cc1-adc0-034f0f1def70", "name": "Start Sub-Workflow via Webhook", "type": "n8n-nodes-base.httpRequest", "position": [1180, 645], "parameters": {"url": "={{ $env.WEBHOOK_URL }}/webhook/parallel-subworkflow-target", "method": "POST", "options": {}, "sendBody": true, "sendHeaders": true, "bodyParameters": {"parameters": [{"name": "requestItemId", "value": "={{ $json.requestId }}"}]}, "headerParameters": {"parameters": [{"name": "callbackurl", "value": "={{ $execution.resumeUrl }}"}]}}, "typeVersion": 4.2}, {"id": "4ad48520-39b3-4016-a6a9-dd789c079e08", "name": "Acknowledge Finished", "type": "n8n-nodes-base.respondToWebhook", "position": [1780, 665], "parameters": {"options": {}}, "typeVersion": 1.1}, {"id": "ad1018a1-3b9d-4613-b23f-136763a514ba", "name": "Sticky Note", "type": "n8n-nodes-base.stickyNote", "position": [720, 605], "parameters": {"color": 3, "width": 390, "height": 109, "content": "### Start Multiple Sub-Workflows Asynchronously\n* Note: Callback/Webhook \"internal\" Base-URL should be configured in the n8n instance to reference the k8s service name and internal port."}, "typeVersion": 1}, {"id": "f4171d39-8bfe-4e3a-9b94-87d969abda2d", "name": "Sticky Note1", "type": "n8n-nodes-base.stickyNote", "position": [1740, 365], "parameters": {"color": 3, "width": 283, "height": 80, "content": "### Pseudo-Synchronously Wait for All Sub-Workflows to finish"}, "typeVersion": 1}, {"id": "98657cd3-968c-4d66-aea0-4e3180f8508f", "name": "Continue Workflow (noop)", "type": "n8n-nodes-base.noOp", "position": [1780, 205], "parameters": {}, "typeVersion": 1}, {"id": "5a9518ea-456e-4975-bf6f-71bf9ed0a6e1", "name": "Sticky Note3", "type": "n8n-nodes-base.stickyNote", "position": [540, 180], "parameters": {"width": 1577.931818181817, "height": 684.1818181818179, "content": "## Main/Parent Workflow\n* This starts multiple executions of the sub-workflow in parallel and then loops until they all report back."}, "typeVersion": 1}, {"id": "13ad3423-c3bf-4144-b76d-03daa8877bed", "name": "Sticky Note2", "type": "n8n-nodes-base.stickyNote", "position": [560, 900], "parameters": {"width": 1477.331211260329, "height": 189.2194473140495, "content": "### Sub-Workflow\n**Cut/Paste this into a separate workflow, and activate it!!!**"}, "typeVersion": 1}, {"id": "e92865b0-b3e9-4195-ae16-5c199875a04b", "name": "Wait", "type": "n8n-nodes-base.wait", "position": [1440, 940], "webhookId": "2d62e5c2-ad4a-4e90-a075-7ca5212e015a", "parameters": {}, "typeVersion": 1.1}, {"id": "710456c8-394d-4c45-8d8e-16e0a4095dc3", "name": "Call Resume on Parent Workflow", "type": "n8n-nodes-base.httpRequest", "notes": "The callback resumes the parent workflow and reports which item finished.  There could be a race condition if the parent workflow was just resumed by a different sub-workflow but hasn't entered a webhook-wait again yet.  The delay and retry mitigates for the possibility that multiple subtasks complete and call back at once.", "position": [1660, 940], "parameters": {"url": "={{ $('Webhook').item.json.headers.callbackurl }}", "method": "POST", "options": {}, "sendBody": true, "bodyParameters": {"parameters": [{"name": "finishedItemId", "value": "={{ $('Webhook').item.json.body.requestItemId }}"}]}}, "retryOnFail": true, "typeVersion": 4.2, "waitBetweenTries": 3000}, {"id": "2ee41b1a-89f0-4d2f-b2ff-74aef5baaa70", "name": "Respond to Webhook", "type": "n8n-nodes-base.respondToWebhook", "position": [1220, 940], "parameters": {"options": {}, "respondWith": "json", "responseBody": "={{ \n{\n  \"finishedItemId\": $json.body.requestItemId\n}\n}}"}, "typeVersion": 1.1}, {"id": "04445a9a-61f9-468e-8589-3eeb403f2553", "name": "Webhook", "type": "n8n-nodes-base.webhook", "position": [1000, 940], "webhookId": "14776b45-77d7-4220-808f-2d0a38bec4de", "parameters": {"path": "parallel-subworkflow-target", "options": {}, "httpMethod": "POST", "responseMode": "responseNode"}, "typeVersion": 2}], "pinData": {}, "connections": {"Wait": {"main": [[{"node": "Call Resume on Parent Workflow", "type": "main", "index": 0}]]}, "Webhook": {"main": [[{"node": "Respond to Webhook", "type": "main", "index": 0}]]}, "If All Finished": {"main": [[{"node": "Continue Workflow (noop)", "type": "main", "index": 0}], [{"node": "Webhook Callback Wait", "type": "main", "index": 0}]]}, "Loop Over Items": {"main": [[{"node": "Initialize finishedSet", "type": "main", "index": 0}], [{"node": "Start Sub-Workflow via Webhook", "type": "main", "index": 0}]]}, "Respond to Webhook": {"main": [[{"node": "Wait", "type": "main", "index": 0}]]}, "Update finishedSet": {"main": [[{"node": "Acknowledge Finished", "type": "main", "index": 0}]]}, "Acknowledge Finished": {"main": [[{"node": "If All Finished", "type": "main", "index": 0}]]}, "Webhook Callback Wait": {"main": [[{"node": "Update finishedSet", "type": "main", "index": 0}]]}, "Initialize finishedSet": {"main": [[{"node": "If All Finished", "type": "main", "index": 0}]]}, "Start Sub-Workflow via Webhook": {"main": [[{"node": "Loop Over Items", "type": "main", "index": 0}]]}, "When clicking \u2018Test workflow\u2019": {"main": [[{"node": "Simulate Multi-Item for Parallel Processing", "type": "main", "index": 0}]]}, "Simulate Multi-Item for Parallel Processing": {"main": [[{"node": "Loop Over Items", "type": "main", "index": 0}]]}}}