AutomationFlowsWeb Scraping › State Management System for Long-running Workflows with Wait Nodes

State Management System for Long-running Workflows with Wait Nodes

Original n8n title: 🛠️ State Management System for Long-running Workflows with Wait Nodes

ByLucas Peyrin @lucaspeyrin on n8n.io

This template is a powerful, reusable utility for managing stateful, long-running processes. It allows a main workflow to be paused indefinitely at "checkpoints" and then be resumed by external, asynchronous events.

Event trigger★★★★★ complexity42 nodesHTTP RequestExecute Workflow Trigger
Web Scraping Trigger: Event Nodes: 42 Complexity: ★★★★★ Added:

This workflow corresponds to n8n.io template #6269 — we link there as the canonical source.

This workflow follows the Execute Workflow Trigger → HTTP Request recipe pattern — see all workflows that pair these two integrations.

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "nodes": [
    {
      "id": "66e7d75e-727e-465f-9c8c-d588fb11d9b4",
      "name": "D. TELEPORT: Resume Paused Workflow",
      "type": "n8n-nodes-base.httpRequest",
      "notes": "\u00a9 2025 Lucas Peyrin",
      "onError": "continueErrorOutput",
      "position": [
        1504,
        1232
      ],
      "parameters": {
        "url": "={{ $json.resume_url }}",
        "method": "POST",
        "options": {},
        "jsonBody": "={{ $('A. Entry: Receive Session Info').last().json.input_items }}",
        "sendBody": true,
        "specifyBody": "json"
      },
      "typeVersion": 4.2
    },
    {
      "id": "274540d6-0664-49e1-b911-aa182a864286",
      "name": "E. Stop This Execution",
      "type": "n8n-nodes-base.filter",
      "notes": "\u00a9 2025 Lucas Peyrin",
      "position": [
        2128,
        1024
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "0e6f2a44-0407-4534-9df9-71af678be3e8",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": false,
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "59b743ce-288a-4911-8cf5-7d03e11b4d85",
      "name": "B. Check if Session is New or Existing",
      "type": "n8n-nodes-base.code",
      "notes": "\u00a9 2025 Lucas Peyrin",
      "position": [
        816,
        1440
      ],
      "parameters": {
        "jsCode": "// n8n Code Node\n// Description: Checks if a session_id already exists in the workflow's static data.\n// If it's a new session, it stores the session_id and its resume_url.\n// If the session already exists, it returns the previously stored resume_url.\n\n// --- INPUTS ---\n// This code expects the following inputs from the previous node:\n// $json.session_id: (string) A unique identifier for the session.\n// $json.resume_url: (string) The URL associated with the session.\n\nconst sessionId = $json.session_id;\nconst resumeUrl = $json.resume_url;\nconst workflowId = $json.workflow_id;\nconst executionId = $json.execution_id;\n\n// --- VALIDATION ---\nif (!sessionId) {\n  throw new Error(\"Input data is missing 'session_id'. Please ensure it's provided.\");\n}\nif (!workflowId) {\n  throw new Error(\"Input data is missing 'workflow_id'. Please ensure it's provided.\");\n}\nif (!executionId) {\n  throw new Error(\"Input data is missing 'execution_id'. Please ensure it's provided.\");\n}\n\n// Get the workflow's static data (persists between executions).\nconst staticData = $getWorkflowStaticData('global');\n\n// --- INITIALIZATION ---\nif (!staticData.sessions) {\n  staticData.sessions = {};\n}\nif (!staticData.sessions[workflowId]) {\n  staticData.sessions[workflowId] = {};\n}\n\n// Prepare output object\nconst output = {\n  session_id: sessionId,\n  new: false,\n  resume_url: '',\n  main_execution: ''\n};\n\n// --- CORE LOGIC ---\nif (staticData.sessions[workflowId][sessionId]) {\n  // Session already exists\n  output.new = false;\n  output.resume_url = staticData.sessions[workflowId][sessionId].resume_url;\n  output.main_execution = staticData.sessions[workflowId][sessionId].execution_id;\n} else {\n  // New session\n  if (!resumeUrl) {\n    throw new Error(\"Input data is missing 'resume_url' for a new session.\");\n  }\n\n  output.new = true;\n  output.resume_url = resumeUrl;\n  output.main_execution = executionId;\n\n  // Store session data\n  staticData.sessions[workflowId][sessionId] = {\n    resume_url: resumeUrl,\n    execution_id: executionId,\n    first_seen: new Date().toISOString()\n  };\n}\n\n// --- OUTPUT ---\nreturn output;"
      },
      "typeVersion": 2
    },
    {
      "id": "7be98b11-6330-4e34-b879-de57e04de4ac",
      "name": "F. Prepare Initial Data",
      "type": "n8n-nodes-base.set",
      "notes": "\u00a9 2025 Lucas Peyrin",
      "position": [
        1504,
        1616
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "72bd8c68-d201-4376-ab1f-2cbd8a62b3e2",
              "name": "input_items",
              "type": "array",
              "value": "={{ $('A. Entry: Receive Session Info').last().json.input_items }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "bfac6fb5-81d3-4f1e-97b5-441925e14e10",
      "name": "G. Return Data to Main Workflow",
      "type": "n8n-nodes-base.splitOut",
      "notes": "\u00a9 2025 Lucas Peyrin",
      "position": [
        1728,
        1616
      ],
      "parameters": {
        "options": {},
        "fieldToSplitOut": "input_items"
      },
      "typeVersion": 1
    },
    {
      "id": "82f05109-74f2-4bcb-af78-f7f044114192",
      "name": "C. Route Based on Session State",
      "type": "n8n-nodes-base.if",
      "notes": "\u00a9 2025 Lucas Peyrin",
      "position": [
        1168,
        1440
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "40784acd-1fd8-4370-9219-7053267ac426",
              "operator": {
                "type": "boolean",
                "operation": "notEquals"
              },
              "leftValue": "={{ $json.new }}",
              "rightValue": true
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "d9ceec07-f1f4-410b-9184-e91169cfc635",
      "name": "Stop Session On Error ?",
      "type": "n8n-nodes-base.if",
      "notes": "\u00a9 2025 Lucas Peyrin",
      "position": [
        1824,
        1392
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "e3920d1a-8312-4142-81ac-3708e1cfceed",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $('A. Entry: Receive Session Info').last().json.stop_session_on_error }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "905c2e58-88c3-49e5-9297-66554f221c27",
      "name": "Reset Session",
      "type": "n8n-nodes-base.code",
      "notes": "\u00a9 2025 Lucas Peyrin",
      "position": [
        2064,
        1392
      ],
      "parameters": {
        "jsCode": "// n8n Code Node\n// Description: Checks if a session_id already exists in the workflow's static data.\n// If it's a new session, it stores the session_id and its resume_url.\n// If the session already exists, it returns the previously stored resume_url.\n\n// --- INPUTS ---\n// This code expects the following inputs from the previous node:\n// $json.session_id: (string) A unique identifier for the session.\n// $json.resume_url: (string) The URL associated with the session.\n\nconst sessionId = $('A. Entry: Receive Session Info').last().json.session_id;\nconst resumeUrl = $('A. Entry: Receive Session Info').last().json.resume_url;\nconst workflowId = $('A. Entry: Receive Session Info').last().json.workflow_id;\nconst executionId = $('A. Entry: Receive Session Info').last().json.execution_id;\n\n// --- VALIDATION ---\nif (!sessionId) {\n  throw new Error(\"Input data is missing 'session_id'. Please ensure it's provided.\");\n}\nif (!workflowId) {\n  throw new Error(\"Input data is missing 'workflow_id'. Please ensure it's provided.\");\n}\nif (!executionId) {\n  throw new Error(\"Input data is missing 'execution_id'. Please ensure it's provided.\");\n}\n\n// Get the workflow's static data (persists between executions).\nconst staticData = $getWorkflowStaticData('global');\n\n// --- INITIALIZATION ---\nif (!staticData.sessions) {\n  staticData.sessions = {};\n}\nif (!staticData.sessions[workflowId]) {\n  staticData.sessions[workflowId] = {};\n}\n\n// Prepare output object\nconst output = {\n  session_id: sessionId,\n  new: false,\n  resume_url: '',\n  main_execution: ''\n};\n\n// --- CORE LOGIC ---\n\n// Reset session\nif (!resumeUrl) {\n  throw new Error(\"Input data is missing 'resume_url' for a new session.\");\n}\n\noutput.new = true;\noutput.resume_url = resumeUrl;\noutput.main_execution = executionId;\n\n// Store session data\nstaticData.sessions[workflowId][sessionId] = {\n  resume_url: resumeUrl,\n  execution_id: executionId,\n  first_seen: new Date().toISOString()\n};\n\n// --- OUTPUT ---\nreturn output;"
      },
      "typeVersion": 2
    },
    {
      "id": "7ff3c03c-92b5-4072-85b9-89d0b6374ed7",
      "name": "Respond with Input Items",
      "type": "n8n-nodes-base.noOp",
      "notes": "\u00a9 2025 Lucas Peyrin",
      "position": [
        2288,
        1392
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "7e197e8c-be37-4448-84eb-115443502d88",
      "name": "Wait Node Sent Response Items ?",
      "type": "n8n-nodes-base.if",
      "notes": "\u00a9 2025 Lucas Peyrin",
      "position": [
        1888,
        944
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "or",
          "conditions": [
            {
              "id": "27670327-4717-4b1c-9edb-c1ece0f43bdc",
              "operator": {
                "type": "string",
                "operation": "notEquals"
              },
              "leftValue": "={{ $json.message }}",
              "rightValue": "Workflow was started"
            },
            {
              "id": "191d97bf-71dc-4a8c-8715-7171a1dfae9a",
              "operator": {
                "type": "string",
                "operation": "notExists",
                "singleValue": true
              },
              "leftValue": "={{ $json.message }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "d0cb7051-f7f3-43d2-a518-30c4152a0ebb",
      "name": "Send Items From Checkpoint",
      "type": "n8n-nodes-base.noOp",
      "notes": "\u00a9 2025 Lucas Peyrin",
      "position": [
        2128,
        848
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "692897f1-3648-49c1-8058-cbbb3ba3f3b2",
      "name": "Sticky Note13",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        752,
        288
      ],
      "parameters": {
        "color": 4,
        "width": 256,
        "height": 272,
        "content": "![Kitch on fire](https://media2.giphy.com/media/v1.Y2lkPWVjZjA1ZTQ3ODFuYjVvdWdicXcwOGxqNmZ2anB0c3J2MjB3ZzlhYWtmYzBtejJ2biZlcD12MV9naWZzX3NlYXJjaCZjdD1n/THUvAWoL80rJgwlucA/giphy.webp)"
      },
      "typeVersion": 1
    },
    {
      "id": "7b5566b3-d453-4e96-9aa1-e32b94716aa6",
      "name": "A. Entry: Receive Session Info",
      "type": "n8n-nodes-base.executeWorkflowTrigger",
      "notes": "\u00a9 2025 Lucas Peyrin",
      "position": [
        512,
        1440
      ],
      "parameters": {
        "workflowInputs": {
          "values": [
            {
              "name": "session_id"
            },
            {
              "name": "resume_url"
            },
            {
              "name": "input_items",
              "type": "any"
            },
            {
              "name": "workflow_id"
            },
            {
              "name": "execution_id"
            },
            {
              "name": "stop_session_on_error",
              "type": "boolean"
            }
          ]
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "d574f6b5-5d05-4d53-ae1c-a0c40ebc4f58",
      "name": "1. Start Main Workflow (Manual)",
      "type": "n8n-nodes-base.manualTrigger",
      "notes": "\u00a9 2025 Lucas Peyrin",
      "position": [
        512,
        384
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "67105d44-18eb-4d86-b8a3-8292cbc5de41",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -416,
        -320
      ],
      "parameters": {
        "width": 624,
        "height": 640,
        "content": "# The \"Teleport\" Workflow Pattern\n\nThis workflow demonstrates a powerful stateful pattern for managing long-running, multi-step processes that need to be paused and resumed by external events.\n\n**Use Cases:**\n- Multi-day user onboarding sequences.\n- Processes waiting for human approval (e.g., via email link).\n- Chatbots that need to maintain conversation state across multiple messages.\n\n\nIt consists of two parts:\n1.  **The Main Process (Top):** Your primary business logic. It can be paused at any 'Checkpoint' (`Wait` node).\n2.  **The Async Portal (Bottom):** A state-management engine that remembers running processes and can 'teleport' new data to them.\n\n---\n\n### Automate your operations today\nYour time is valuable. Let us automate the boring stuff for you.\n\n**\ud83d\udc47 CHOOSE YOUR PATH:**\n\n[ **\u26a1\ufe0f I WANT A FREE AUDIT (2 min)** ](https://workflows.ac/audit?utm_source=n8n_template&utm_medium=workflow_note&utm_campaign=state_management_system_for_long_running_workflows_with_wait_nodes&utm_content=6269)\n> *We've put our heart into this business evaluation machine.*\n\n[ **\ud83d\udca1 I HAVE A SPECIFIC REQUEST** ](https://workflows.ac/form?utm_source=n8n_template&utm_medium=workflow_note&utm_campaign=state_management_system_for_long_running_workflows_with_wait_nodes&utm_content=6269)\n\n"
      },
      "typeVersion": 1
    },
    {
      "id": "7aa6b48c-c517-4f2d-bbad-8c70261a277b",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        240,
        -320
      ],
      "parameters": {
        "color": 7,
        "width": 3344,
        "height": 944,
        "content": "## MAIN PROCESS\n### This is your primary business logic. It calls the Portal to register itself, processes some data, and then pauses at a Checkpoint, waiting for another event to resume it."
      },
      "typeVersion": 1
    },
    {
      "id": "38960902-f86f-45b4-9924-8af6d587d465",
      "name": "2. Call Async Portal",
      "type": "n8n-nodes-base.executeWorkflow",
      "notes": "\u00a9 2025 Lucas Peyrin",
      "position": [
        832,
        384
      ],
      "parameters": {
        "options": {
          "waitForSubWorkflow": true
        },
        "workflowId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $workflow.id }}"
        },
        "workflowInputs": {
          "value": {
            "resume_url": "={{ $execution.resumeUrl }}",
            "session_id": "test123",
            "input_items": "={{ $input.all().map(item => item.json) }}",
            "workflow_id": "={{ $workflow.id }}",
            "execution_id": "={{ $execution.id }}",
            "stop_session_on_error": false
          },
          "schema": [
            {
              "id": "session_id",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "session_id",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "resume_url",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "resume_url",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "input_items",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "input_items",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "workflow_id",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "workflow_id",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "execution_id",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "execution_id",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "stop_session_on_error",
              "type": "boolean",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "stop_session_on_error",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "session_id"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": true
        }
      },
      "executeOnce": true,
      "typeVersion": 1.2
    },
    {
      "id": "c4074066-f74b-458b-8231-603f11065af1",
      "name": "3. Process Initial Data (Before Checkpoint 1)",
      "type": "n8n-nodes-base.set",
      "notes": "\u00a9 2025 Lucas Peyrin",
      "position": [
        1568,
        384
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3.4
    },
    {
      "id": "8bc0d9c4-d276-42b1-9456-40bc296eb770",
      "name": "4. PAUSE at Checkpoint 1",
      "type": "n8n-nodes-base.wait",
      "notes": "\u00a9 2025 Lucas Peyrin",
      "position": [
        2016,
        384
      ],
      "parameters": {
        "resume": "webhook",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 1.1
    },
    {
      "id": "8aa13a69-0a54-49f6-a358-dd4f47ab1f01",
      "name": "5a. Receive Resumed Data",
      "type": "n8n-nodes-base.splitOut",
      "notes": "\u00a9 2025 Lucas Peyrin",
      "position": [
        2368,
        384
      ],
      "parameters": {
        "options": {},
        "fieldToSplitOut": "body"
      },
      "typeVersion": 1
    },
    {
      "id": "244c7937-bb60-477d-b3fa-07033ba80fea",
      "name": "6. PAUSE at Checkpoint 2",
      "type": "n8n-nodes-base.wait",
      "notes": "\u00a9 2025 Lucas Peyrin",
      "position": [
        2960,
        384
      ],
      "parameters": {
        "resume": "webhook",
        "options": {},
        "httpMethod": "POST"
      },
      "typeVersion": 1.1
    },
    {
      "id": "88751841-891e-42c3-b1d4-fda40c9d0901",
      "name": "7. Process Final Data (After Checkpoint 2)",
      "type": "n8n-nodes-base.set",
      "notes": "\u00a9 2025 Lucas Peyrin",
      "position": [
        3312,
        384
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3.4
    },
    {
      "id": "59679849-6a24-4d24-a3f4-47cb730fb984",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        272,
        224
      ],
      "parameters": {
        "color": 7,
        "width": 384,
        "height": 320,
        "content": "### 1. Start Main Process\n\nThis is the entry point for your stateful process. It could be a webhook, a form submission, or a manual trigger like this one."
      },
      "typeVersion": 1
    },
    {
      "id": "27ae7dc9-bc93-4426-b7f9-ed0d39c1434f",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1904,
        96
      ],
      "parameters": {
        "color": 7,
        "width": 320,
        "height": 464,
        "content": "### 4. PAUSE at Checkpoint 1\n\nThis `Wait` node is a **Checkpoint**. The workflow execution physically stops here and is persisted.\n\nIt generates a unique `resume_url` that is stored by the Portal. The only way to wake this execution up is for the Portal to make an HTTP POST request to that specific URL."
      },
      "typeVersion": 1
    },
    {
      "id": "e6041571-5298-41e2-882a-f91dc7b09aeb",
      "name": "Sticky Note8",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2256,
        160
      ],
      "parameters": {
        "color": 7,
        "width": 560,
        "height": 400,
        "content": "### 5. Resumption & Response\n\nWhen the Portal 'teleports' data, this sequence is triggered.\n\n- **5a:** Receives the new data from the body of the resume request.\n- **5b:** Immediately sends a response back to the Portal. This is optional and can be used for passing data back to the Portal if needed."
      },
      "typeVersion": 1
    },
    {
      "id": "9c660804-6d8e-48ac-ba88-ff989a899464",
      "name": "Sticky Note9",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2848,
        160
      ],
      "parameters": {
        "color": 7,
        "width": 320,
        "height": 400,
        "content": "### 6. PAUSE at Checkpoint 2\n\nThis demonstrates that a single Main Process can have multiple, sequential checkpoints. The Portal will always resume the *latest* Checkpoint that was registered for the session."
      },
      "typeVersion": 1
    },
    {
      "id": "134ff79d-e2ca-4a24-ad5f-2bd54ba468bb",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        688,
        32
      ],
      "parameters": {
        "color": 4,
        "width": 384,
        "height": 560,
        "content": "### 2. Call Async Portal\n\nThis is the first and only direct interaction with the Portal. The Main Process sends its `session_id` and its current `resume_url` to register itself.\n\n**Key Setting:** `Wait for Sub-Workflow` is **ON**. The Main Process needs the Portal's response to know if it should continue (new session) or stop (existing session that will get teleported to a checkpoint)."
      },
      "typeVersion": 1
    },
    {
      "id": "fa1833f6-2281-471c-b2dc-6c1ece25580b",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        240,
        656
      ],
      "parameters": {
        "color": 7,
        "width": 2256,
        "height": 1280,
        "content": "## ASYNC PORTAL\n### This is the state-management engine. It's a separate, reusable workflow. Its only job is to check if a session is new or existing. If it's existing, it finds the correct paused workflow's resume url and sends the new data to it. If it's new, then it simply returns all the input items."
      },
      "typeVersion": 1
    },
    {
      "id": "73190cf8-6b2c-4d61-9c60-4d660464e02d",
      "name": "Sticky Note10",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        272,
        1264
      ],
      "parameters": {
        "color": 7,
        "width": 384,
        "height": 352,
        "content": "### Portal Entry\n\nThis is the single entry point for the entire state management system. Any external event (a webhook, a form, another workflow) that needs to interact with a stateful session will call this trigger."
      },
      "typeVersion": 1
    },
    {
      "id": "0e109b16-8a85-49f4-9c56-f2c5f65e14aa",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        688,
        1024
      ],
      "parameters": {
        "color": 6,
        "width": 352,
        "height": 592,
        "content": "### Session State Manager (Code)\n\nThis Code node is the brain of the portal. It uses **Workflow Static Data** (`$getWorkflowStaticData`) as a persistent, shared memory (a \"whiteboard\") to track all active sessions.\n\n**Technical Breakdown:**\n1.  It receives a `session_id` and a `workflow_id`.\n2.  It checks its whiteboard to see if an entry for `[workflow_id][session_id]` already exists.\n3.  **If it exists (Existing Session):** It retrieves the stored `resume_url` and outputs `new: false`.\n4.  **If it's new (New Session):** It stores the incoming `resume_url` and `execution_id` on the whiteboard and outputs `new: true`."
      },
      "typeVersion": 1
    },
    {
      "id": "dd8043f7-59b4-410a-9d70-4202242fc608",
      "name": "Sticky Note11",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1056,
        1136
      ],
      "parameters": {
        "color": 7,
        "width": 304,
        "height": 480,
        "content": "### Route: New vs. Existing Session\n\nThis `IF` node directs traffic based on the output of the State Manager.\n\n- **True (Existing Session):** The flow proceeds to the 'Teleport' node to resume the paused process.\n- **False (New Session):** The flow proceeds to the 'Pass-through' nodes, which simply return the initial data back to the Main Process so it can start."
      },
      "typeVersion": 1
    },
    {
      "id": "94c5b95c-89de-4199-87ac-68869c2034b4",
      "name": "Sticky Note12",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1728,
        1184
      ],
      "parameters": {
        "color": 7,
        "width": 736,
        "height": 368,
        "content": "### Handle Teleport Error\n\nThis is a crucial safety feature. If the 'Teleport' action fails (e.g., the main execution was manually stopped and the `resume_url` is invalid), this branch is triggered.\n\nIt allows you to decide whether to stop the session entirely or to reset the state, allowing a new Main Process to begin for that `session_id`."
      },
      "typeVersion": 1
    },
    {
      "id": "94cbe651-8ce0-4107-9f81-322fb9277409",
      "name": "Sticky Note14",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1376,
        944
      ],
      "parameters": {
        "color": 7,
        "width": 336,
        "height": 464,
        "content": "### TELEPORT: Resume Paused Process\n\nThis is the \"Teleport\" action. It's triggered only for existing sessions.\n\n**Technical Breakdown:**\nIt takes the `resume_url` retrieved by the State Manager and makes an HTTP POST request to it. This action wakes up the specific `Wait` node that is paused in the Main Process, effectively delivering the new data directly to the correct checkpoint."
      },
      "typeVersion": 1
    },
    {
      "id": "5bfc19bd-3d06-422e-9b2c-8a4006a16e42",
      "name": "5b. Send Back Data to Portal",
      "type": "n8n-nodes-base.respondToWebhook",
      "notes": "\u00a9 2025 Lucas Peyrin",
      "position": [
        2624,
        384
      ],
      "parameters": {
        "options": {
          "responseKey": "teleport_data"
        },
        "respondWith": "allIncomingItems"
      },
      "typeVersion": 1.4
    },
    {
      "id": "038f658e-7e78-4088-8e5a-f7d3c5f23796",
      "name": "Response From Checkpoint",
      "type": "n8n-nodes-base.if",
      "notes": "\u00a9 2025 Lucas Peyrin",
      "position": [
        1168,
        384
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "4e2a2496-c47a-4909-9a63-f92156484e9d",
              "operator": {
                "type": "array",
                "operation": "exists",
                "singleValue": true
              },
              "leftValue": "={{ $json.teleport_data }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "fdf121cf-8870-4175-bbc0-adf100116d59",
      "name": "Split Out Teleported Items",
      "type": "n8n-nodes-base.splitOut",
      "notes": "\u00a9 2025 Lucas Peyrin",
      "position": [
        1456,
        32
      ],
      "parameters": {
        "options": {},
        "fieldToSplitOut": "teleport_data"
      },
      "typeVersion": 1
    },
    {
      "id": "289e0b88-3128-406d-bcfc-9dbb541610bc",
      "name": "How to Test",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -416,
        352
      ],
      "parameters": {
        "color": 5,
        "width": 624,
        "height": 704,
        "content": "## How to Test This Workflow\n\nThis pattern requires a special testing method to see it in action.\n\n1.  **Open Two Tabs:** Open this workflow in two separate browser tabs (let's call them **Tab A** and **Tab B**).\n\n2.  **Start the First Session (Tab A):**\n    - In **Tab A**, click **\"Execute Workflow\"** on the `1. Start Main Workflow` node.\n    - Observe the execution. It will run until `4. PAUSE at Checkpoint 1` and the status will change to **\"Waiting\"**. This is your paused process.\n\n3.  **Trigger the Teleport (Tab B):**\n    - Now, go to **Tab B** and click **\"Execute Workflow\"** on the *same* `1. Start Main Workflow` node.\n\n4.  **Observe the Magic:**\n    - The execution in **Tab B** will be very short. The Portal will detect an existing session and \"teleport\" its data.\n    - Instantly, the execution in **Tab A** will resume! It will pass Checkpoint 1, run through the `5a` and `5b` nodes, and then pause again at `6. PAUSE at Checkpoint 2`.\n\n5.  **Resume the Final Checkpoint (Tab B again):**\n    - In **Tab B**, click **\"Execute Workflow\"** one more time.\n\n6.  **Observe the Completion:**\n    - The execution in **Tab A** will now resume from Checkpoint 2 and run to completion.\n\n\nThis test proves how external events (from Tab B) can control and pass data to a long-running, paused process (in Tab A)."
      },
      "typeVersion": 1
    },
    {
      "id": "16bd788f-e207-4e8f-a944-a2e14bfb9aa7",
      "name": "Sticky Note15",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1104,
        -208
      ],
      "parameters": {
        "color": 7,
        "width": 736,
        "height": 800,
        "content": "### 2a. Handle Portal's Response\n\nThis `IF` node routes the initial execution.\n\n- **False Path (First Run):** When the Portal is first called, it confirms the session is new but sends no special data back. The workflow proceeds down the `false` path to begin its work.\n\n- **True Path (Data from Checkpoint):** If a checkpoint sends data back to the Portal (like Checkpoint 1 does), the Portal returns that data here. This path is for handling data that has been \"teleported\" from a checkpoint in another execution."
      },
      "typeVersion": 1
    },
    {
      "id": "5ed49e7d-d8da-4a51-aabd-6f646749d981",
      "name": "Sticky Note16",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1424,
        192
      ],
      "parameters": {
        "color": 7,
        "width": 384,
        "height": 368,
        "content": "### 3. Process Initial Data\n\nThis is a placeholder for your own business logic. This is where you would perform the first set of actions in your process, like creating a user account, sending a welcome email, etc., before reaching the first checkpoint."
      },
      "typeVersion": 1
    },
    {
      "id": "804d3aaf-0b0a-4c39-9048-7dddc7dfa397",
      "name": "Sticky Note17",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3200,
        160
      ],
      "parameters": {
        "color": 7,
        "width": 320,
        "height": 400,
        "content": "### 7. Process Final Data\n\nThis represents the final steps of your process after all checkpoints have been passed. For example, this is where you would mark an order as 'complete' or send a final notification."
      },
      "typeVersion": 1
    },
    {
      "id": "b933ada8-99e5-48f0-a49b-3cb02751c2d7",
      "name": "Sticky Note18",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1872,
        -128
      ],
      "parameters": {
        "color": 6,
        "width": 1680,
        "height": 720,
        "content": "### Two Types of Checkpoints\n\nThis workflow demonstrates two different ways to use `Wait` nodes as checkpoints.\n\n- **Checkpoint 1 (Two-Way):** This `Wait` node has **`Response Mode`** set to `On Resume`. This means it can not only be resumed, but it can also **send data back** to the Portal that resumed it. This is useful for two-way communication.\n\n- **Checkpoint 2 (One-Way):** This `Wait` node has **`Response Mode`** set to `Never`. It can only be resumed; it **cannot send data back**. This is useful for simple, one-way continuation."
      },
      "typeVersion": 1
    },
    {
      "id": "8c57606f-3906-46ed-acc2-6f7407a127a6",
      "name": "Sticky Note20",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2528,
        656
      ],
      "parameters": {
        "color": 7,
        "width": 540,
        "height": 1280,
        "content": "## Was this helpful? Let me know!\n[![clic](https://supastudio.ia2s.app/storage/v1/object/public/assets/n8n/clic_down_lucas.gif)](https://n8n.ac)\n\nI really hope this utility helped you implement asynchronous workflows in n8n easily. Your feedback is incredibly valuable and helps me create better resources for the n8n community.\n\n### **Share Your Thoughts & Ideas**\n\nWhether you have a suggestion, found a typo, or just want to say thanks, I'd love to hear from you!\nHere's a simple n8n form built for this purpose:\n\n#### \u27a1\ufe0f **[Click here to give feedback](https://api.ia2s.app/form/templates/feedback?template=Async%20Portal)**\n\n### **Ready to Build Something Great?**\n\nIf you're looking to take your n8n skills or business automation to the next level, I can help.\n\n**\ud83c\udf93 n8n Coaching:** Want to become an n8n pro? I offer one-on-one coaching sessions to help you master workflows, tackle specific problems, and build with confidence.\n#### \u27a1\ufe0f **[Book a Coaching Session](https://api.ia2s.app/form/templates/coaching?template=Async%20Portal)**\n\n**\ud83d\udcbc n8n Consulting:** Have a complex project, an integration challenge, or need a custom workflow built for your business? Let's work together to create a powerful automation solution.\n#### \u27a1\ufe0f **[Inquire About Consulting Services](https://api.ia2s.app/form/templates/consulting?template=Async%20Portal)**\n\n---\n\nHappy Automating!\nLucas Peyrin | [n8n Academy](https://n8n.ac)"
      },
      "typeVersion": 1
    }
  ],
  "connections": {
    "Reset Session": {
      "main": [
        [
          {
            "node": "Respond with Input Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "2. Call Async Portal": {
      "main": [
        [
          {
            "node": "Response From Checkpoint",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "F. Prepare Initial Data": {
      "main": [
        [
          {
            "node": "G. Return Data to Main Workflow",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Stop Session On Error ?": {
      "main": [
        [
          {
            "node": "E. Stop This Execution",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Reset Session",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "4. PAUSE at Checkpoint 1": {
      "main": [
        [
          {
            "node": "5a. Receive Resumed Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "5a. Receive Resumed Data": {
      "main": [
        [
          {
            "node": "5b. Send Back Data to Portal",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "6. PAUSE at Checkpoint 2": {
      "main": [
        [
          {
            "node": "7. Process Final Data (After Checkpoint 2)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Respond with Input Items": {
      "main": [
        [
          {
            "node": "F. Prepare Initial Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Response From Checkpoint": {
      "main": [
        [
          {
            "node": "Split Out Teleported Items",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "3. Process Initial Data (Before Checkpoint 1)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "5b. Send Back Data to Portal": {
      "main": [
        [
          {
            "node": "6. PAUSE at Checkpoint 2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "A. Entry: Receive Session Info": {
      "main": [
        [
          {
            "node": "B. Check if Session is New or Existing",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "1. Start Main Workflow (Manual)": {
      "main": [
        [
          {
            "node": "2. Call Async Portal",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "C. Route Based on Session State": {
      "main": [
        [
          {
            "node": "D. TELEPORT: Resume Paused Workflow",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "F. Prepare Initial Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait Node Sent Response Items ?": {
      "main": [
        [
          {
            "node": "Send Items From Checkpoint",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "E. Stop This Execution",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "D. TELEPORT: Resume Paused Workflow": {
      "main": [
        [
          {
            "node": "Wait Node Sent Response Items ?",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Stop Session On Error ?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "B. Check if Session is New or Existing": {
      "main": [
        [
          {
            "node": "C. Route Based on Session State",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "3. Process Initial Data (Before Checkpoint 1)": {
      "main": [
        [
          {
            "node": "4. PAUSE at Checkpoint 1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

This template is a powerful, reusable utility for managing stateful, long-running processes. It allows a main workflow to be paused indefinitely at "checkpoints" and then be resumed by external, asynchronous events.

Source: https://n8n.io/workflows/6269/ — original creator credit. Request a take-down →

More Web Scraping workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Web Scraping

Upload files from any source to your account Kommo or AmoCRM with a simple and reusable workflow. It can split a large file into small ones and upload chunks. Works for Kommo and amoCRM There are 3 re

HTTP Request, Execute Workflow Trigger, Stop And Error
Web Scraping

Remixed Backup your workflows to GitHub from Solomon's work. Check out his templates.

HTTP Request, GitHub, Execute Workflow Trigger +1
Web Scraping

Remixed Backup your workflows to GitHub from Solomon's work. Check out his templates.

Execute Workflow Trigger, HTTP Request, GitHub
Web Scraping

This workflow audits your SharePoint Online environment for external sharing risks by identifying files and folders that are shared with anonymous links or external/guest users. It is designed to trav

HTTP Request, Execute Workflow Trigger
Web Scraping

This template syncs prospects from ProspectPro into HubSpot. It checks if a company already exists in HubSpot (by ProspectPro ID or domain), then updates the record or creates a new one. Sync results

Execute Workflow Trigger, @Bedrijfsdatanl/N8N Nodes Prospectpro, HTTP Request +1