대기 노드를 포함한 장기 실행 워크플로우 상태 관리 시스템
고급
이것은Engineering분야의자동화 워크플로우로, 42개의 노드를 포함합니다.주로 If, Set, Code, Wait, Filter 등의 노드를 사용하며. 대기 노드를 포함한 장기 실행 워크플로우 상태 관리 시스템
사전 요구사항
- •대상 API의 인증 정보가 필요할 수 있음
- •HTTP Webhook 엔드포인트(n8n이 자동으로 생성)
사용된 노드 (42)
카테고리
워크플로우 미리보기
노드 연결 관계를 시각적으로 표시하며, 확대/축소 및 이동을 지원합니다
워크플로우 내보내기
다음 JSON 구성을 복사하여 n8n에 가져오면 이 워크플로우를 사용할 수 있습니다
{
"meta": {
"creator": "Lucas Peyrin",
"instanceId": "e409ea34548a2afe2dffba31130cd1cf2e98ebe2afaeed2a63caf2a0582d1da0",
"fingerprint": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjcmVhdG9yIjoiIiwiaXNzIjoibjhuIiwiaWF0IjoxNzUzMTg1NTA2fQ.dVu-rLvYTXqkxAD6CAZ88wBJP6Esr41Te54DdaImktw"
},
"nodes": [
{
"id": "1ec4313c-df14-4c89-86e7-a51b2462428c",
"cid": "Ikx1Y2FzIFBleXJpbiI",
"name": "D. TELEPORT: 일시 중지된 워크플로우 재개",
"type": "n8n-nodes-base.httpRequest",
"notes": "© 2025 Lucas Peyrin",
"creator": "Lucas Peyrin",
"onError": "continueErrorOutput",
"position": [
1632,
896
],
"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": "ab925a2d-f8b4-4267-8aa6-f6c2eeb1f150",
"cid": "Ikx1Y2FzIFBleXJpbiI",
"name": "E. 이 실행 중지",
"type": "n8n-nodes-base.filter",
"notes": "© 2025 Lucas Peyrin",
"creator": "Lucas Peyrin",
"position": [
2256,
688
],
"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": "14e9e8a7-b52b-43a7-a24c-133ed203789f",
"cid": "Ikx1Y2FzIFBleXJpbiI",
"name": "B. 세션이 신규인지 기존인지 확인",
"type": "n8n-nodes-base.code",
"notes": "© 2025 Lucas Peyrin",
"creator": "Lucas Peyrin",
"position": [
944,
1104
],
"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": "529a1e67-9e2c-4c59-b402-790bb62a34ea",
"cid": "Ikx1Y2FzIFBleXJpbiI",
"name": "F. 초기 데이터 준비",
"type": "n8n-nodes-base.set",
"notes": "© 2025 Lucas Peyrin",
"creator": "Lucas Peyrin",
"position": [
1632,
1280
],
"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": "9d8cc3b8-f4ae-4d2a-9e27-c41b3eb0a708",
"cid": "Ikx1Y2FzIFBleXJpbiI",
"name": "G. 메인 워크플로우에 데이터 반환",
"type": "n8n-nodes-base.splitOut",
"notes": "© 2025 Lucas Peyrin",
"creator": "Lucas Peyrin",
"position": [
1856,
1280
],
"parameters": {
"options": {},
"fieldToSplitOut": "input_items"
},
"typeVersion": 1
},
{
"id": "116121f5-8fa4-4d23-80f4-3000511f901a",
"cid": "Ikx1Y2FzIFBleXJpbiI",
"name": "C. 세션 상태에 따른 라우팅",
"type": "n8n-nodes-base.if",
"notes": "© 2025 Lucas Peyrin",
"creator": "Lucas Peyrin",
"position": [
1296,
1104
],
"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": "54bdfb27-85ca-44c5-9cdb-3dc92d44000e",
"cid": "Ikx1Y2FzIFBleXJpbiI",
"name": "오류 발생 시 세션 중지?",
"type": "n8n-nodes-base.if",
"notes": "© 2025 Lucas Peyrin",
"creator": "Lucas Peyrin",
"position": [
1952,
1056
],
"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": "d8bf9a2d-045d-4bbe-8c85-ca1183a4b86e",
"cid": "Ikx1Y2FzIFBleXJpbiI",
"name": "세션 재설정",
"type": "n8n-nodes-base.code",
"notes": "© 2025 Lucas Peyrin",
"creator": "Lucas Peyrin",
"position": [
2192,
1056
],
"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": "cf9710a0-8f0b-4370-a35e-39b5878f1cc2",
"cid": "Ikx1Y2FzIFBleXJpbiI",
"name": "입력 항목으로 응답",
"type": "n8n-nodes-base.noOp",
"notes": "© 2025 Lucas Peyrin",
"creator": "Lucas Peyrin",
"position": [
2416,
1056
],
"parameters": {},
"typeVersion": 1
},
{
"id": "b23f9aa6-e9c8-441f-8d65-9474b2f9cba6",
"cid": "Ikx1Y2FzIFBleXJpbiI",
"name": "대기 노드가 응답 항목을 전송했는가?",
"type": "n8n-nodes-base.if",
"notes": "© 2025 Lucas Peyrin",
"creator": "Lucas Peyrin",
"position": [
2016,
608
],
"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": "dab41bea-d997-4a48-a1b8-9f90f2267743",
"cid": "Ikx1Y2FzIFBleXJpbiI",
"name": "체크포인트에서 항목 전송",
"type": "n8n-nodes-base.noOp",
"notes": "© 2025 Lucas Peyrin",
"creator": "Lucas Peyrin",
"position": [
2256,
512
],
"parameters": {},
"typeVersion": 1
},
{
"id": "e29f9889-86f7-41c2-969a-459f5450bfe9",
"cid": "Ikx1Y2FzIFBleXJpbiI",
"name": "스티키 노트13",
"type": "n8n-nodes-base.stickyNote",
"notes": "© 2025 Lucas Peyrin",
"creator": "Lucas Peyrin",
"position": [
880,
-48
],
"parameters": {
"color": 4,
"width": 256,
"height": 272,
"content": ""
},
"typeVersion": 1
},
{
"id": "7f899a48-c96a-417e-957a-a2fa720f6d73",
"cid": "Ikx1Y2FzIFBleXJpbiI",
"name": "A. 진입점: 세션 정보 수신",
"type": "n8n-nodes-base.executeWorkflowTrigger",
"notes": "© 2025 Lucas Peyrin",
"creator": "Lucas Peyrin",
"position": [
640,
1104
],
"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": "d66ecc05-1b86-4f16-9534-d5c47c76f1bc",
"cid": "Ikx1Y2FzIFBleXJpbiI",
"name": "1. 메인 워크플로우 시작 (수동)",
"type": "n8n-nodes-base.manualTrigger",
"notes": "© 2025 Lucas Peyrin",
"creator": "Lucas Peyrin",
"position": [
640,
48
],
"parameters": {},
"typeVersion": 1
},
{
"id": "d72f27c3-2724-4126-b0c2-93fd82ecbb21",
"cid": "Ikx1Y2FzIFBleXJpbiI",
"name": "스티키 노트6",
"type": "n8n-nodes-base.stickyNote",
"notes": "© 2025 Lucas Peyrin",
"creator": "Lucas Peyrin",
"position": [
-288,
-656
],
"parameters": {
"width": 624,
"height": 416,
"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."
},
"typeVersion": 1
},
{
"id": "b8c415e6-3d16-432d-844d-97bb9634d066",
"cid": "Ikx1Y2FzIFBleXJpbiI",
"name": "스티키 노트1",
"type": "n8n-nodes-base.stickyNote",
"notes": "© 2025 Lucas Peyrin",
"creator": "Lucas Peyrin",
"position": [
368,
-656
],
"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": "9368010f-f32c-43fc-bac1-e90d193ffca4",
"cid": "Ikx1Y2FzIFBleXJpbiI",
"name": "2. 비동기 포털 호출",
"type": "n8n-nodes-base.executeWorkflow",
"notes": "© 2025 Lucas Peyrin",
"creator": "Lucas Peyrin",
"position": [
960,
48
],
"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": "9c98707e-763a-4022-8996-9ded3f587754",
"cid": "Ikx1Y2FzIFBleXJpbiI",
"name": "3. 초기 데이터 처리 (체크포인트 1 이전)",
"type": "n8n-nodes-base.set",
"notes": "© 2025 Lucas Peyrin",
"creator": "Lucas Peyrin",
"position": [
1696,
48
],
"parameters": {
"options": {}
},
"typeVersion": 3.4
},
{
"id": "9814ef51-c934-4b71-af89-4d6a3df671ff",
"cid": "Ikx1Y2FzIFBleXJpbiI",
"name": "4. 체크포인트 1에서 일시 중지",
"type": "n8n-nodes-base.wait",
"notes": "© 2025 Lucas Peyrin",
"creator": "Lucas Peyrin",
"position": [
2144,
48
],
"webhookId": "d522c442-945b-4350-9d88-76276d6afaf9",
"parameters": {
"resume": "webhook",
"options": {},
"httpMethod": "POST",
"responseMode": "responseNode"
},
"typeVersion": 1.1
},
{
"id": "3bc4a204-04e8-4294-8d52-5fb1ae0d8f95",
"cid": "Ikx1Y2FzIFBleXJpbiI",
"name": "5a. 재개된 데이터 수신",
"type": "n8n-nodes-base.splitOut",
"notes": "© 2025 Lucas Peyrin",
"creator": "Lucas Peyrin",
"position": [
2496,
48
],
"parameters": {
"options": {},
"fieldToSplitOut": "body"
},
"typeVersion": 1
},
{
"id": "b76cafb7-8d42-40e4-b276-62536e360b06",
"cid": "Ikx1Y2FzIFBleXJpbiI",
"name": "6. 체크포인트 2에서 일시 중지",
"type": "n8n-nodes-base.wait",
"notes": "© 2025 Lucas Peyrin",
"creator": "Lucas Peyrin",
"position": [
3088,
48
],
"webhookId": "d522c442-945b-4350-9d88-76276d6afaf9",
"parameters": {
"resume": "webhook",
"options": {},
"httpMethod": "POST"
},
"typeVersion": 1.1
},
{
"id": "a9bffc0c-fcdb-49c0-b920-8ed458798e78",
"cid": "Ikx1Y2FzIFBleXJpbiI",
"name": "7. 최종 데이터 처리 (체크포인트 2 이후)",
"type": "n8n-nodes-base.set",
"notes": "© 2025 Lucas Peyrin",
"creator": "Lucas Peyrin",
"position": [
3440,
48
],
"parameters": {
"options": {}
},
"typeVersion": 3.4
},
{
"id": "6ca47cb1-f7ec-4c4f-87d8-4f780224ce61",
"cid": "Ikx1Y2FzIFBleXJpbiI",
"name": "스티키 노트5",
"type": "n8n-nodes-base.stickyNote",
"notes": "© 2025 Lucas Peyrin",
"creator": "Lucas Peyrin",
"position": [
400,
-112
],
"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": "9ff30639-61f4-49a9-89fc-0e071b8faebe",
"cid": "Ikx1Y2FzIFBleXJpbiI",
"name": "스티키 노트7",
"type": "n8n-nodes-base.stickyNote",
"notes": "© 2025 Lucas Peyrin",
"creator": "Lucas Peyrin",
"position": [
2032,
-240
],
"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": "88e03f8a-efda-4194-b3a4-cda3513e2753",
"cid": "Ikx1Y2FzIFBleXJpbiI",
"name": "스티키 노트8",
"type": "n8n-nodes-base.stickyNote",
"notes": "© 2025 Lucas Peyrin",
"creator": "Lucas Peyrin",
"position": [
2384,
-176
],
"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": "0c961fcc-5f67-4301-a8f4-e8bb1434bcc9",
"cid": "Ikx1Y2FzIFBleXJpbiI",
"name": "스티키 노트9",
"type": "n8n-nodes-base.stickyNote",
"notes": "© 2025 Lucas Peyrin",
"creator": "Lucas Peyrin",
"position": [
2976,
-176
],
"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": "c1bb3790-8b55-48f5-aab9-fa9d28157ddb",
"cid": "Ikx1Y2FzIFBleXJpbiI",
"name": "스티키 노트",
"type": "n8n-nodes-base.stickyNote",
"notes": "© 2025 Lucas Peyrin",
"creator": "Lucas Peyrin",
"position": [
816,
-304
],
"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": "54ca2319-a769-4cb7-b459-1e83863460ff",
"cid": "Ikx1Y2FzIFBleXJpbiI",
"name": "스티키 노트2",
"type": "n8n-nodes-base.stickyNote",
"notes": "© 2025 Lucas Peyrin",
"creator": "Lucas Peyrin",
"position": [
368,
320
],
"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": "8e86a20e-afd8-4105-9d10-2b8038beb6ae",
"cid": "Ikx1Y2FzIFBleXJpbiI",
"name": "스티키 노트10",
"type": "n8n-nodes-base.stickyNote",
"notes": "© 2025 Lucas Peyrin",
"creator": "Lucas Peyrin",
"position": [
400,
928
],
"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": "b6ca1d7e-b880-4cc0-bbfb-df910c968a6a",
"cid": "Ikx1Y2FzIFBleXJpbiI",
"name": "스티키 노트3",
"type": "n8n-nodes-base.stickyNote",
"notes": "© 2025 Lucas Peyrin",
"creator": "Lucas Peyrin",
"position": [
816,
688
],
"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": "9d825e53-fe5b-4930-a6fa-075786dd161f",
"cid": "Ikx1Y2FzIFBleXJpbiI",
"name": "스티키 노트11",
"type": "n8n-nodes-base.stickyNote",
"notes": "© 2025 Lucas Peyrin",
"creator": "Lucas Peyrin",
"position": [
1184,
800
],
"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": "99c10763-a6fd-4a62-9046-b4853c50b013",
"cid": "Ikx1Y2FzIFBleXJpbiI",
"name": "스티키 노트12",
"type": "n8n-nodes-base.stickyNote",
"notes": "© 2025 Lucas Peyrin",
"creator": "Lucas Peyrin",
"position": [
1856,
848
],
"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": "1c496a9e-0f6d-486b-8728-52800fd97f0b",
"cid": "Ikx1Y2FzIFBleXJpbiI",
"name": "스티키 노트14",
"type": "n8n-nodes-base.stickyNote",
"notes": "© 2025 Lucas Peyrin",
"creator": "Lucas Peyrin",
"position": [
1504,
608
],
"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": "1fee913a-2bc9-4295-83d3-33734b0dd9bb",
"cid": "Ikx1Y2FzIFBleXJpbiI",
"name": "5b. 포털에 데이터 반환",
"type": "n8n-nodes-base.respondToWebhook",
"notes": "© 2025 Lucas Peyrin",
"creator": "Lucas Peyrin",
"position": [
2752,
48
],
"parameters": {
"options": {
"responseKey": "teleport_data"
},
"respondWith": "allIncomingItems"
},
"typeVersion": 1.4
},
{
"id": "d98a1678-d297-4cfe-9b3c-9f34b42279a5",
"cid": "Ikx1Y2FzIFBleXJpbiI",
"name": "체크포인트 응답",
"type": "n8n-nodes-base.if",
"notes": "© 2025 Lucas Peyrin",
"creator": "Lucas Peyrin",
"position": [
1296,
48
],
"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": "78a48514-f755-482b-a138-904838d54108",
"cid": "Ikx1Y2FzIFBleXJpbiI",
"name": "TELEPORT된 항목 분리",
"type": "n8n-nodes-base.splitOut",
"notes": "© 2025 Lucas Peyrin",
"creator": "Lucas Peyrin",
"position": [
1584,
-304
],
"parameters": {
"options": {},
"fieldToSplitOut": "teleport_data"
},
"typeVersion": 1
},
{
"id": "b2e5b9f4-2111-40af-aced-19b7c84c0878",
"cid": "Ikx1Y2FzIFBleXJpbiI",
"name": "테스트 방법",
"type": "n8n-nodes-base.stickyNote",
"notes": "© 2025 Lucas Peyrin",
"creator": "Lucas Peyrin",
"position": [
-288,
-208
],
"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": "c717aae2-6acd-4ab7-9be0-035ce1ec6f4d",
"cid": "Ikx1Y2FzIFBleXJpbiI",
"name": "스티키 노트15",
"type": "n8n-nodes-base.stickyNote",
"notes": "© 2025 Lucas Peyrin",
"creator": "Lucas Peyrin",
"position": [
1232,
-544
],
"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": "7947fc09-8c55-4c78-aecf-9f008ff357a7",
"cid": "Ikx1Y2FzIFBleXJpbiI",
"name": "스티키 노트16",
"type": "n8n-nodes-base.stickyNote",
"notes": "© 2025 Lucas Peyrin",
"creator": "Lucas Peyrin",
"position": [
1552,
-144
],
"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": "f9be8200-420d-4d4a-b964-f4abe544d68c",
"cid": "Ikx1Y2FzIFBleXJpbiI",
"name": "스티키 노트17",
"type": "n8n-nodes-base.stickyNote",
"notes": "© 2025 Lucas Peyrin",
"creator": "Lucas Peyrin",
"position": [
3328,
-176
],
"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": "8909e76d-b953-44d0-a126-e422645862ec",
"cid": "Ikx1Y2FzIFBleXJpbiI",
"name": "스티키 노트18",
"type": "n8n-nodes-base.stickyNote",
"notes": "© 2025 Lucas Peyrin",
"creator": "Lucas Peyrin",
"position": [
2000,
-464
],
"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": "73a60499-87ad-4a91-9e9d-0c13bdb9d06e",
"cid": "Ikx1Y2FzIFBleXJpbiI",
"name": "스티키 노트20",
"type": "n8n-nodes-base.stickyNote",
"notes": "© 2025 Lucas Peyrin",
"creator": "Lucas Peyrin",
"position": [
2656,
320
],
"parameters": {
"color": 3,
"width": 540,
"height": 1280,
"content": "## Was this helpful? Let me know!\n[](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#### ➡️ **[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**🎓 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#### ➡️ **[Book a Coaching Session](https://api.ia2s.app/form/templates/coaching?template=Async%20Portal)**\n\n**💼 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#### ➡️ **[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
}
],
"pinData": {},
"connections": {
"d8bf9a2d-045d-4bbe-8c85-ca1183a4b86e": {
"main": [
[
{
"node": "cf9710a0-8f0b-4370-a35e-39b5878f1cc2",
"type": "main",
"index": 0
}
]
]
},
"9368010f-f32c-43fc-bac1-e90d193ffca4": {
"main": [
[
{
"node": "d98a1678-d297-4cfe-9b3c-9f34b42279a5",
"type": "main",
"index": 0
}
]
]
},
"529a1e67-9e2c-4c59-b402-790bb62a34ea": {
"main": [
[
{
"node": "9d8cc3b8-f4ae-4d2a-9e27-c41b3eb0a708",
"type": "main",
"index": 0
}
]
]
},
"54bdfb27-85ca-44c5-9cdb-3dc92d44000e": {
"main": [
[
{
"node": "ab925a2d-f8b4-4267-8aa6-f6c2eeb1f150",
"type": "main",
"index": 0
}
],
[
{
"node": "d8bf9a2d-045d-4bbe-8c85-ca1183a4b86e",
"type": "main",
"index": 0
}
]
]
},
"9814ef51-c934-4b71-af89-4d6a3df671ff": {
"main": [
[
{
"node": "3bc4a204-04e8-4294-8d52-5fb1ae0d8f95",
"type": "main",
"index": 0
}
]
]
},
"3bc4a204-04e8-4294-8d52-5fb1ae0d8f95": {
"main": [
[
{
"node": "1fee913a-2bc9-4295-83d3-33734b0dd9bb",
"type": "main",
"index": 0
}
]
]
},
"b76cafb7-8d42-40e4-b276-62536e360b06": {
"main": [
[
{
"node": "a9bffc0c-fcdb-49c0-b920-8ed458798e78",
"type": "main",
"index": 0
}
]
]
},
"cf9710a0-8f0b-4370-a35e-39b5878f1cc2": {
"main": [
[
{
"node": "529a1e67-9e2c-4c59-b402-790bb62a34ea",
"type": "main",
"index": 0
}
]
]
},
"d98a1678-d297-4cfe-9b3c-9f34b42279a5": {
"main": [
[
{
"node": "78a48514-f755-482b-a138-904838d54108",
"type": "main",
"index": 0
}
],
[
{
"node": "9c98707e-763a-4022-8996-9ded3f587754",
"type": "main",
"index": 0
}
]
]
},
"dab41bea-d997-4a48-a1b8-9f90f2267743": {
"main": [
[]
]
},
"1fee913a-2bc9-4295-83d3-33734b0dd9bb": {
"main": [
[
{
"node": "b76cafb7-8d42-40e4-b276-62536e360b06",
"type": "main",
"index": 0
}
]
]
},
"7f899a48-c96a-417e-957a-a2fa720f6d73": {
"main": [
[
{
"node": "14e9e8a7-b52b-43a7-a24c-133ed203789f",
"type": "main",
"index": 0
}
]
]
},
"d66ecc05-1b86-4f16-9534-d5c47c76f1bc": {
"main": [
[
{
"node": "9368010f-f32c-43fc-bac1-e90d193ffca4",
"type": "main",
"index": 0
}
]
]
},
"116121f5-8fa4-4d23-80f4-3000511f901a": {
"main": [
[
{
"node": "1ec4313c-df14-4c89-86e7-a51b2462428c",
"type": "main",
"index": 0
}
],
[
{
"node": "529a1e67-9e2c-4c59-b402-790bb62a34ea",
"type": "main",
"index": 0
}
]
]
},
"b23f9aa6-e9c8-441f-8d65-9474b2f9cba6": {
"main": [
[
{
"node": "dab41bea-d997-4a48-a1b8-9f90f2267743",
"type": "main",
"index": 0
}
],
[
{
"node": "ab925a2d-f8b4-4267-8aa6-f6c2eeb1f150",
"type": "main",
"index": 0
}
]
]
},
"1ec4313c-df14-4c89-86e7-a51b2462428c": {
"main": [
[
{
"node": "b23f9aa6-e9c8-441f-8d65-9474b2f9cba6",
"type": "main",
"index": 0
}
],
[
{
"node": "54bdfb27-85ca-44c5-9cdb-3dc92d44000e",
"type": "main",
"index": 0
}
]
]
},
"14e9e8a7-b52b-43a7-a24c-133ed203789f": {
"main": [
[
{
"node": "116121f5-8fa4-4d23-80f4-3000511f901a",
"type": "main",
"index": 0
}
]
]
},
"9c98707e-763a-4022-8996-9ded3f587754": {
"main": [
[
{
"node": "9814ef51-c934-4b71-af89-4d6a3df671ff",
"type": "main",
"index": 0
}
]
]
}
}
}자주 묻는 질문
이 워크플로우를 어떻게 사용하나요?
위의 JSON 구성 코드를 복사하여 n8n 인스턴스에서 새 워크플로우를 생성하고 "JSON에서 가져오기"를 선택한 후, 구성을 붙여넣고 필요에 따라 인증 설정을 수정하세요.
이 워크플로우는 어떤 시나리오에 적합한가요?
고급 - 엔지니어링
유료인가요?
이 워크플로우는 완전히 무료이며 직접 가져와 사용할 수 있습니다. 다만, 워크플로우에서 사용하는 타사 서비스(예: OpenAI API)는 사용자 직접 비용을 지불해야 할 수 있습니다.
관련 워크플로우 추천
🎓 병행 처리를 사용하여 속도가 중요한 작업 흐름 최적화(확산-집합)
🎓 병렬 처리(확산/집합)를 사용하여 속도 중요한 작업 흐름 최적화
If
Set
Code
+
If
Set
Code
34 노드Lucas Peyrin
엔지니어링
시각화 참조 라이브러리에서 n8n 노드를 탐색
可视化 참조 라이브러리에서 n8n 노드를 탐색
If
Ftp
Set
+
If
Ftp
Set
113 노드I versus AI
기타
API 아키텍처 추출기
API 아키텍처 추출기
If
Set
Code
+
If
Set
Code
88 노드Polina Medvedieva
엔지니어링
Overpass 잠재 고객 생성 시스템
OpenStreetMap 데이터를 사용하여 Google Sheets에 비즈니스 유치원 저장
If
Set
Code
+
If
Set
Code
27 노드Akram Kadri
영업
Voyage-Context-3 임베디드와 MongoDB Atlas를 기반으로 한 문서 질문 응답 시스템
기반 Voyage-Context-3 임베디드 및 MongoDB Atlas의 문서 질문 답변 시스템
Set
Code
Wait
+
Set
Code
Wait
53 노드Jimleuk
엔지니어링
Anthropic Claude API를 사용하여 대량으로 튜토리얼 처리
Anthropic Claude API를 사용하여 배치로 튜토리얼 처리
If
Set
Code
+
If
Set
Code
39 노드Greg Evseev
빌딩 블록
워크플로우 정보
난이도
고급
노드 수42
카테고리1
노드 유형13
저자
Lucas Peyrin
@lucaspeyrinInnovative builder with a passion for crafting automation solutions that solve real-world challenges. From streamlining workflows to driving efficiency, my work empowers teams and individuals to achieve more with less effort. Experienced in developing scalable tools and strategies that deliver results with n8n, supabase and cline.
외부 링크
n8n.io에서 보기 →
이 워크플로우 공유