AutomationFlowsWeb Scraping › Ask a Human (http Door for the Runner)

Ask a Human (http Door for the Runner)

Ask a human (HTTP door for the runner). Uses httpRequest. Webhook trigger; 6 nodes.

Webhook trigger★★★★☆ complexity6 nodesHTTP Request
Web Scraping Trigger: Webhook Nodes: 6 Complexity: ★★★★☆ Added:

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
{
  "id": "aicp-ask-webhook",
  "name": "Ask a human (HTTP door for the runner)",
  "settings": {
    "errorWorkflow": "aicp-error-trigger"
  },
  "nodes": [
    {
      "id": "hook",
      "name": "POST /ask-human",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        200,
        240
      ],
      "parameters": {
        "httpMethod": "POST",
        "path": "ask-human",
        "responseMode": "onReceived",
        "responseData": "accepted",
        "options": {}
      }
    },
    {
      "id": "auth",
      "name": "Authenticate + validate",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        430,
        240
      ],
      "parameters": {
        "jsCode": "// FAIL CLOSED on the token. This endpoint blocks an agent run and pages a human,\n// so an unauthenticated caller on the docker network could spend a person's\n// attention on demand.\nconst req = $input.first().json || {};\nconst headers = req.headers || {};\nconst want = $env.RUNNER_TOKEN || '';\nconst got = headers['x-runner-token'] || headers['X-Runner-Token'] || '';\n\nif (!want) {\n  throw new Error('ask-webhook: RUNNER_TOKEN is not set in n8n \u2014 refusing to serve an unauthenticated question endpoint.');\n}\nif (!got || got !== want) {\n  throw new Error('ask-webhook: bad or missing x-runner-token');\n}\n\nconst b = req.body || {};\nconst question = (b.question || '').trim();\nif (!question) {\n  throw new Error('ask-webhook: `question` is required \u2014 refusing to ask a human nothing.');\n}\n\n// The ticket is a KEY, and keys from callers deserve an allowlist rather than a\n// sanitiser. UUID or nothing.\nconst ticket = String(b.ticket || '').trim();\nif (!/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(ticket)) {\n  throw new Error('ask-webhook: `ticket` must be a UUID.');\n}\n\n// Bound the wait: the agent is blocked while this runs.\nconst MAX_MIN = 10;\nconst minutes = Math.min(Number(b.timeoutMinutes) > 0 ? Number(b.timeoutMinutes) : MAX_MIN, MAX_MIN);\n\nreturn [{ json: {\n  ticket, question,\n  context: b.context || '',\n  source: b.source || 'agent',\n  timeoutHours: minutes / 60,\n} }];"
      }
    },
    {
      "id": "slack",
      "name": "Start Slack watch",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        660,
        380
      ],
      "onError": "continueRegularOutput",
      "parameters": {
        "url": "=http://lanes:8081/slack-ask",
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "ticket",
              "value": "={{ $json.ticket }}"
            },
            {
              "name": "question",
              "value": "={{ $json.question }}"
            },
            {
              "name": "context",
              "value": "={{ $json.context }}"
            },
            {
              "name": "minutes",
              "value": "={{ Math.round($json.timeoutHours * 60) }}"
            }
          ]
        },
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "x-lanes-token",
              "value": "={{ $env.LANES_TOKEN }}"
            }
          ]
        },
        "options": {
          "timeout": 20000
        }
      }
    },
    {
      "id": "ask",
      "name": "Ask the human",
      "type": "n8n-nodes-base.executeWorkflow",
      "typeVersion": 1.2,
      "position": [
        660,
        240
      ],
      "parameters": {
        "source": "database",
        "workflowId": {
          "__rl": true,
          "value": "aicp-ask-question",
          "mode": "id"
        },
        "workflowInputs": {
          "mappingMode": "defineBelow",
          "value": {
            "question": "={{ $json.question }}",
            "context": "={{ $json.context }}",
            "source": "={{ $json.source }}",
            "timeoutHours": "={{ $json.timeoutHours }}"
          },
          "matchingColumns": [],
          "schema": [
            {
              "id": "question",
              "displayName": "question",
              "type": "string",
              "required": true,
              "defaultMatch": false,
              "display": true,
              "canBeUsedToMatch": true
            },
            {
              "id": "context",
              "displayName": "context",
              "type": "string",
              "required": true,
              "defaultMatch": false,
              "display": true,
              "canBeUsedToMatch": true
            },
            {
              "id": "source",
              "displayName": "source",
              "type": "string",
              "required": true,
              "defaultMatch": false,
              "display": true,
              "canBeUsedToMatch": true
            },
            {
              "id": "timeoutHours",
              "displayName": "timeoutHours",
              "type": "number",
              "required": true,
              "defaultMatch": false,
              "display": true,
              "canBeUsedToMatch": true
            }
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": true
        },
        "options": {
          "waitForSubWorkflow": true
        }
      }
    },
    {
      "id": "shape",
      "name": "Shape answer",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        890,
        240
      ],
      "parameters": {
        "jsCode": "// Shape the answer for delivery. `answered` stays separate from `answer` all the\n// way to the agent: \"nobody answered\" and \"answered with nothing\" are different\n// facts, and a caller that cannot tell them apart treats silence as data.\nconst a = $input.first().json || {};\nconst t = $('Authenticate + validate').first().json || {};\nreturn [{ json: {\n  ticket: t.ticket,\n  provider: 'claude',\n  answered: a.answered === true,\n  answer: a.answer || '',\n  timedOut: a.timedOut === true,\n  note: a.note || '',\n} }];"
      }
    },
    {
      "id": "deliver",
      "name": "Deliver via router",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1120,
        240
      ],
      "parameters": {
        "method": "POST",
        "url": "http://router:8080/ask/answer",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "x-router-token",
              "value": "={{ $env.ROUTER_TOKEN }}"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify($json) }}",
        "options": {
          "timeout": 30000
        }
      }
    }
  ],
  "connections": {
    "POST /ask-human": {
      "main": [
        [
          {
            "node": "Authenticate + validate",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Authenticate + validate": {
      "main": [
        [
          {
            "node": "Start Slack watch",
            "type": "main",
            "index": 0
          },
          {
            "node": "Ask the human",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Ask the human": {
      "main": [
        [
          {
            "node": "Shape answer",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Shape answer": {
      "main": [
        [
          {
            "node": "Deliver via router",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "meta": {
    "description": "HTTP door for a runner to raise a question mid-run. Responds immediately, starts a Slack thread watch alongside the form, and hands the answer back through the router rather than straight to a runner."
  }
}
Pro

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

About this workflow

Ask a human (HTTP door for the runner). Uses httpRequest. Webhook trigger; 6 nodes.

Source: https://github.com/jgobuilds/ai-control-plane-public/blob/main/n8n-workflows/ask-webhook.workflow.json — 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

This n8n template provides enterprise-level version control for your workflows using GitHub integration. Stop losing hours to broken workflows and manual exports – get proper commit history, visual di

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

This flow creates dummy files for every item added in your *Arrs (Radarr/Sonarr) with the tag .

HTTP Request, Ssh
Web Scraping

eek-Go v2 (Batch-Then-Review). Uses httpRequest. Webhook trigger; 75 nodes.

HTTP Request
Web Scraping

This workflow receives webhook requests from a content calendar and uses the X API v2 to publish text posts, threads, image/video posts, and polls, as well as delete existing posts and run a credentia

HTTP Request
Web Scraping

This workflow acts as a central API gateway for all technical indicator agents in the Binance Spot Market Quant AI system. It listens for incoming webhook requests and dynamically routes them to the c

HTTP Request