This workflow follows the Agent → OpenRouter Chat 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 →
{
"name": "Firecrawl Web Crawler Agent (Webhook)",
"settings": {
"executionOrder": "v1"
},
"nodes": [
{
"parameters": {
"content": "## Firecrawl Web Crawler Agent \u2014 Webhook variant\n**What it does:** Programmatic POST endpoint exposing the same tool-using Firecrawl agent as `01-main-firecrawl-agent`, but for machine-to-machine use. Returns structured JSON.\n\n**Endpoint:** `POST /webhook/firecrawl-research`\n\n**Request body:**\n```\n{\n \"prompt\": \"Research what Firecrawl offers and compare with Apify\",\n \"output_schema\": { // optional JSON Schema \u2014 agent conforms output to this\n \"type\": \"object\",\n \"required\": [\"summary\", \"sources\"],\n \"properties\": {\n \"summary\": { \"type\": \"string\" },\n \"sources\": { \"type\": \"array\" }\n }\n },\n \"session_id\": \"optional-stable-id\", // default: auto-generated per request\n \"credit_cap\": 200 // default 200; soft cap enforced by the agent\n}\n```\n\n**Response:**\n```\n{\n \"session_id\": \"...\",\n \"result\": { /* output_schema-conforming, or default 5-section report */ },\n \"credits_used_this_request\": 17\n}\n```\n\n### Required credentials\nSame as `01-main-firecrawl-agent`: Firecrawl API (HTTP Header Auth), OpenRouter API, Postgres (writable, `firecrawl_agent_rw` role).\n\n### Sub-workflows required (import first, then re-link)\nIdentical to the chat variant \u2014 this workflow shares the same 9 sub-workflows.\n\n### Auth\nThe default n8n webhook is unauthenticated. Enable `Header Auth` on the webhook node (a shared secret) or put n8n behind an API gateway / Cloudflare Worker for production use.",
"height": 760,
"width": 800,
"color": 6
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-700,
-820
],
"id": "sticky-fc-webhook-header",
"name": "README"
},
{
"parameters": {
"httpMethod": "POST",
"path": "firecrawl-research",
"responseMode": "lastNode",
"options": {
"allowedOrigins": "*",
"rawBody": false
}
},
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
-680,
0
],
"id": "fc-wh-trigger",
"name": "POST /firecrawl-research"
},
{
"parameters": {
"jsCode": "const body = $input.first().json.body || $input.first().json;\nconst prompt = (body.prompt || '').toString().trim();\nif (!prompt) throw new Error('Request body must include a `prompt` string.');\n\nconst output_schema = body.output_schema || null;\nconst session_id = (body.session_id || $execution.id || 'no-session').toString();\nlet credit_cap = Number(body.credit_cap || 200);\nif (!Number.isFinite(credit_cap) || credit_cap < 0) credit_cap = 200;\n\nreturn [{ json: {\n prompt,\n output_schema,\n session_id,\n credit_cap,\n has_schema: output_schema !== null\n} }];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-460,
0
],
"id": "fc-wh-prep",
"name": "Parse Request"
},
{
"parameters": {
"promptType": "define",
"text": "={{ $json.prompt }}{{ $json.output_schema ? ('\\n\\nReturn ONLY valid JSON conforming to this schema:\\n' + JSON.stringify($json.output_schema)) : '' }}",
"options": {
"maxIterations": 12,
"systemMessage": "You are a senior web research analyst powered by Firecrawl, invoked via a programmatic webhook.\n\nThe caller's request is in the user message. If their request includes a JSON schema at the end, your response MUST be a single JSON object conforming to that schema \u2014 no markdown fences, no explanatory prose, just the JSON.\n\nIf no schema was provided, return a JSON object with exactly these fields:\n```\n{\n \"headline\": \"string \u2014 one-sentence key finding with the most important numbers\",\n \"summary\": \"string \u2014 2-4 short paragraphs with inline [n] citations\",\n \"evidence\": [{\"finding\": \"...\", \"source\": \"[n]\", \"confidence\": \"high|medium|low\"}],\n \"sources\": [{\"n\": 1, \"title\": \"...\", \"url\": \"...\", \"scraped_at\": \"ISO-8601\"}],\n \"observations\": [\"string\", ...],\n \"credits_used_this_request\": <number from your last check_credits call>\n}\n```\n\n## Your tools (identical to the chat variant)\n\n**Discovery (cheap):**\n- `search_web(query, limit?, scrape_results?)` \u2014 2 credits per 10 results\n- `map_site(url, search?, limit?)` \u2014 1 credit flat (use this BEFORE crawl or batch_scrape)\n- `check_credits()` \u2014 0 credits. Call every 5 tool calls and BEFORE any crawl/batch/extract.\n\n**Scrape (credits apply):**\n- `scrape_url(url, formats?, only_main_content?, force_refresh?)` \u2014 1 credit, 24h cached\n- `batch_scrape(urls[], formats?)` \u2014 up to 25 URLs, 1 credit each\n- `crawl_site(url, max_pages?, max_depth?, include_patterns?, exclude_patterns?)` \u2014 caps at 50 pages, depth 3\n- `extract_data(urls[], prompt?, schema?)` \u2014 5 credits per URL\n\n**Advanced:**\n- `firecrawl_autopilot(prompt, url?, max_wait?)` \u2014 Firecrawl's server-side agent\n- `browser_session(action, session_id?, code?, language?, url?)` \u2014 persistent browser\n- `think(thought)` \u2014 scratchpad\n\n## WINNING PATTERNS \u2014 follow in order\n1. **Map-first:** for any site-level question, `map_site` \u2192 filter via `think` \u2192 `batch_scrape`. Never crawl blind.\n2. **Cache-aware:** trust `cache_hit: true` \u2014 don't re-scrape.\n3. **Credit discipline:** call `check_credits` every ~5 tools and BEFORE any crawl/batch/extract. If `over_budget` or `near_cap`, STOP and summarize with what you have.\n4. **Citation-first:** every factual claim needs an inline `[n]` mapped to the Sources array.\n5. **Tool decision table:**\n - Single URL \u2192 scrape_url\n - Find pages \u2192 search_web\n - Site section \u2192 map_site + batch_scrape\n - Structured fields \u2192 extract_data with schema\n - JS-heavy / login-gated \u2192 browser_session or firecrawl_autopilot\n\n## Hard rules\n- No writes, no destructive ops. You read the web + read/write ledger and cache.\n- Never invent URLs. Never invent facts. Everything traceable.\n- If a tool errors: `think` \u2192 diagnose \u2192 retry ONCE. After 2 failures, include the error in observations and return what you have.\n- maxIterations is 12. Budget accordingly.\n- Your entire response must be a single JSON object. No markdown fences. No explanatory text outside the JSON.",
"returnIntermediateSteps": false
}
},
"type": "@n8n/n8n-nodes-langchain.agent",
"typeVersion": 1.9,
"position": [
-240,
0
],
"id": "fc-wh-agent",
"name": "Firecrawl Web Agent (Webhook)"
},
{
"parameters": {
"model": "anthropic/claude-sonnet-4.6",
"options": {
"temperature": 0.2
}
},
"type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
"typeVersion": 1,
"position": [
-400,
260
],
"id": "fc-wh-llm",
"name": "OpenRouter Chat Model"
},
{
"parameters": {
"name": "search_web",
"description": "Searches the open web via Firecrawl. Returns up to `limit` ranked results. Costs 2 credits per 10 results. Inputs: query, limit (default 5, max 20), scrape_results (default false).",
"workflowId": {
"__rl": true,
"value": "Fc04SubSearch01x",
"mode": "id"
},
"workflowInputs": {
"mappingMode": "defineBelow",
"value": {
"query": "={{ $fromAI('query', 'Search query.', 'string') }}",
"limit": "={{ $fromAI('limit', 'Max results (1-20).', 'number') }}",
"scrape_results": "={{ $fromAI('scrape_results', 'Scrape each result (+1 credit each).', 'boolean') }}",
"session_id": "={{ $('Parse Request').first().json.session_id }}"
},
"matchingColumns": [],
"schema": [
{
"id": "query",
"displayName": "query",
"required": true,
"type": "string",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
},
{
"id": "limit",
"displayName": "limit",
"required": false,
"type": "number",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
},
{
"id": "scrape_results",
"displayName": "scrape_results",
"required": false,
"type": "boolean",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
},
{
"id": "session_id",
"displayName": "session_id",
"required": false,
"type": "string",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
}
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
}
},
"type": "@n8n/n8n-nodes-langchain.toolWorkflow",
"typeVersion": 2.1,
"position": [
-100,
260
],
"id": "fc-wh-tool-search",
"name": "search_web"
},
{
"parameters": {
"name": "map_site",
"description": "Returns every URL on a site. 1 credit flat. Use BEFORE crawl_site or batch_scrape. Inputs: url, search (optional filter), limit (default 500).",
"workflowId": {
"__rl": true,
"value": "Fc05SubMapSite01",
"mode": "id"
},
"workflowInputs": {
"mappingMode": "defineBelow",
"value": {
"url": "={{ $fromAI('url', 'Site root URL.', 'string') }}",
"search": "={{ $fromAI('search', 'Optional path keyword filter.', 'string') }}",
"limit": "={{ $fromAI('limit', 'Max URLs returned.', 'number') }}",
"session_id": "={{ $('Parse Request').first().json.session_id }}"
},
"matchingColumns": [],
"schema": [
{
"id": "url",
"displayName": "url",
"required": true,
"type": "string",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
},
{
"id": "search",
"displayName": "search",
"required": false,
"type": "string",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
},
{
"id": "limit",
"displayName": "limit",
"required": false,
"type": "number",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
},
{
"id": "session_id",
"displayName": "session_id",
"required": false,
"type": "string",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
}
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
}
},
"type": "@n8n/n8n-nodes-langchain.toolWorkflow",
"typeVersion": 2.1,
"position": [
80,
260
],
"id": "fc-wh-tool-map",
"name": "map_site"
},
{
"parameters": {
"name": "scrape_url",
"description": "Scrapes one URL. 1 credit. 24h Postgres cache (cache_hit=true means 0 credits). Inputs: url, formats (default ['markdown','links']), only_main_content (default true), force_refresh (default false).",
"workflowId": {
"__rl": true,
"value": "Fc03SubScrapeC01",
"mode": "id"
},
"workflowInputs": {
"mappingMode": "defineBelow",
"value": {
"url": "={{ $fromAI('url', 'URL to scrape.', 'string') }}",
"formats": "={{ $fromAI('formats', 'Output formats array.', 'json') }}",
"only_main_content": "={{ $fromAI('only_main_content', 'Strip nav/footer/ads.', 'boolean') }}",
"force_refresh": "={{ $fromAI('force_refresh', 'Bypass 24h cache.', 'boolean') }}",
"session_id": "={{ $('Parse Request').first().json.session_id }}"
},
"matchingColumns": [],
"schema": [
{
"id": "url",
"displayName": "url",
"required": true,
"type": "string",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
},
{
"id": "formats",
"displayName": "formats",
"required": false,
"type": "array",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
},
{
"id": "only_main_content",
"displayName": "only_main_content",
"required": false,
"type": "boolean",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
},
{
"id": "force_refresh",
"displayName": "force_refresh",
"required": false,
"type": "boolean",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
},
{
"id": "session_id",
"displayName": "session_id",
"required": false,
"type": "string",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
}
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
}
},
"type": "@n8n/n8n-nodes-langchain.toolWorkflow",
"typeVersion": 2.1,
"position": [
260,
260
],
"id": "fc-wh-tool-scrape",
"name": "scrape_url"
},
{
"parameters": {
"name": "batch_scrape",
"description": "Scrapes up to 25 URLs in parallel. 1 credit per successful URL. Inputs: urls (array, max 25), formats (default ['markdown']), only_main_content (default true).",
"workflowId": {
"__rl": true,
"value": "Fc07SubBatchSc01",
"mode": "id"
},
"workflowInputs": {
"mappingMode": "defineBelow",
"value": {
"urls": "={{ $fromAI('urls', 'Array of URLs, max 25.', 'json') }}",
"formats": "={{ $fromAI('formats', 'Formats array.', 'json') }}",
"only_main_content": "={{ $fromAI('only_main_content', 'Strip nav/footer/ads.', 'boolean') }}",
"session_id": "={{ $('Parse Request').first().json.session_id }}"
},
"matchingColumns": [],
"schema": [
{
"id": "urls",
"displayName": "urls",
"required": true,
"type": "array",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
},
{
"id": "formats",
"displayName": "formats",
"required": false,
"type": "array",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
},
{
"id": "only_main_content",
"displayName": "only_main_content",
"required": false,
"type": "boolean",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
},
{
"id": "session_id",
"displayName": "session_id",
"required": false,
"type": "string",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
}
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
}
},
"type": "@n8n/n8n-nodes-langchain.toolWorkflow",
"typeVersion": 2.1,
"position": [
440,
260
],
"id": "fc-wh-tool-batch",
"name": "batch_scrape"
},
{
"parameters": {
"name": "crawl_site",
"description": "Bounded recursive crawl. Max 50 pages, depth 3. 1 credit per page. Prefer map_site + batch_scrape. Inputs: url, max_pages (default 25), max_depth (default 2), include_patterns, exclude_patterns.",
"workflowId": {
"__rl": true,
"value": "Fc06SubCrawlB01x",
"mode": "id"
},
"workflowInputs": {
"mappingMode": "defineBelow",
"value": {
"url": "={{ $fromAI('url', 'Site/starting URL.', 'string') }}",
"max_pages": "={{ $fromAI('max_pages', 'Max pages to scrape.', 'number') }}",
"max_depth": "={{ $fromAI('max_depth', 'Max link-depth.', 'number') }}",
"include_patterns": "={{ $fromAI('include_patterns', 'URL glob includes.', 'json') }}",
"exclude_patterns": "={{ $fromAI('exclude_patterns', 'URL glob excludes.', 'json') }}",
"session_id": "={{ $('Parse Request').first().json.session_id }}"
},
"matchingColumns": [],
"schema": [
{
"id": "url",
"displayName": "url",
"required": true,
"type": "string",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
},
{
"id": "max_pages",
"displayName": "max_pages",
"required": false,
"type": "number",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
},
{
"id": "max_depth",
"displayName": "max_depth",
"required": false,
"type": "number",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
},
{
"id": "include_patterns",
"displayName": "include_patterns",
"required": false,
"type": "array",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
},
{
"id": "exclude_patterns",
"displayName": "exclude_patterns",
"required": false,
"type": "array",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
},
{
"id": "session_id",
"displayName": "session_id",
"required": false,
"type": "string",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
}
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
}
},
"type": "@n8n/n8n-nodes-langchain.toolWorkflow",
"typeVersion": 2.1,
"position": [
620,
260
],
"id": "fc-wh-tool-crawl",
"name": "crawl_site"
},
{
"parameters": {
"name": "extract_data",
"description": "LLM structured extraction. 5 credits per URL, max 10 URLs. Inputs: urls, prompt (natural-language) OR schema (JSON Schema). Returns JSON + schema_valid flag.",
"workflowId": {
"__rl": true,
"value": "Fc08SubExtract01",
"mode": "id"
},
"workflowInputs": {
"mappingMode": "defineBelow",
"value": {
"urls": "={{ $fromAI('urls', 'URLs to extract from.', 'json') }}",
"prompt": "={{ $fromAI('prompt', 'What to extract (NL description).', 'string') }}",
"schema": "={{ $fromAI('schema', 'Optional JSON Schema for output.', 'json') }}",
"session_id": "={{ $('Parse Request').first().json.session_id }}"
},
"matchingColumns": [],
"schema": [
{
"id": "urls",
"displayName": "urls",
"required": true,
"type": "array",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
},
{
"id": "prompt",
"displayName": "prompt",
"required": false,
"type": "string",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
},
{
"id": "schema",
"displayName": "schema",
"required": false,
"type": "object",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
},
{
"id": "session_id",
"displayName": "session_id",
"required": false,
"type": "string",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
}
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
}
},
"type": "@n8n/n8n-nodes-langchain.toolWorkflow",
"typeVersion": 2.1,
"position": [
800,
260
],
"id": "fc-wh-tool-extract",
"name": "extract_data"
},
{
"parameters": {
"name": "firecrawl_autopilot",
"description": "Delegates to Firecrawl's server-side agent. Variable credits (5-30 typical). Inputs: prompt (required), url (optional), max_wait (default 180, max 300).",
"workflowId": {
"__rl": true,
"value": "Fc09SubFcAgent01",
"mode": "id"
},
"workflowInputs": {
"mappingMode": "defineBelow",
"value": {
"prompt": "={{ $fromAI('prompt', 'Research goal.', 'string') }}",
"url": "={{ $fromAI('url', 'Starting URL hint.', 'string') }}",
"max_wait": "={{ $fromAI('max_wait', 'Seconds to wait (max 300).', 'number') }}",
"session_id": "={{ $('Parse Request').first().json.session_id }}"
},
"matchingColumns": [],
"schema": [
{
"id": "prompt",
"displayName": "prompt",
"required": true,
"type": "string",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
},
{
"id": "url",
"displayName": "url",
"required": false,
"type": "string",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
},
{
"id": "max_wait",
"displayName": "max_wait",
"required": false,
"type": "number",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
},
{
"id": "session_id",
"displayName": "session_id",
"required": false,
"type": "string",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
}
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
}
},
"type": "@n8n/n8n-nodes-langchain.toolWorkflow",
"typeVersion": 2.1,
"position": [
980,
260
],
"id": "fc-wh-tool-autopilot",
"name": "firecrawl_autopilot"
},
{
"parameters": {
"name": "browser_session",
"description": "Persistent browser sandbox. Actions: create, execute, delete, list. ALWAYS delete when done.",
"workflowId": {
"__rl": true,
"value": "Fc10SubSandbox01",
"mode": "id"
},
"workflowInputs": {
"mappingMode": "defineBelow",
"value": {
"action": "={{ $fromAI('action', 'create|execute|delete|list.', 'string') }}",
"session_id": "={{ $fromAI('session_id', 'Sandbox session id.', 'string') }}",
"code": "={{ $fromAI('code', 'Code to execute.', 'string') }}",
"language": "={{ $fromAI('language', 'javascript|python|bash.', 'string') }}",
"url": "={{ $fromAI('url', 'Starting URL for create.', 'string') }}",
"session_id_caller": "={{ $('Parse Request').first().json.session_id }}"
},
"matchingColumns": [],
"schema": [
{
"id": "action",
"displayName": "action",
"required": true,
"type": "string",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
},
{
"id": "session_id",
"displayName": "session_id",
"required": false,
"type": "string",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
},
{
"id": "code",
"displayName": "code",
"required": false,
"type": "string",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
},
{
"id": "language",
"displayName": "language",
"required": false,
"type": "string",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
},
{
"id": "url",
"displayName": "url",
"required": false,
"type": "string",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
},
{
"id": "session_id_caller",
"displayName": "session_id_caller",
"required": false,
"type": "string",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
}
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
}
},
"type": "@n8n/n8n-nodes-langchain.toolWorkflow",
"typeVersion": 2.1,
"position": [
1160,
260
],
"id": "fc-wh-tool-sandbox",
"name": "browser_session"
},
{
"parameters": {
"name": "check_credits",
"description": "Aggregate credit spend for this session. 0 credits. Call every 5 tools and BEFORE any crawl/batch/extract.",
"workflowId": {
"__rl": true,
"value": "Fc11SubCredits01",
"mode": "id"
},
"workflowInputs": {
"mappingMode": "defineBelow",
"value": {
"session_id": "={{ $('Parse Request').first().json.session_id }}",
"credit_cap": "={{ $('Parse Request').first().json.credit_cap }}"
},
"matchingColumns": [],
"schema": [
{
"id": "session_id",
"displayName": "session_id",
"required": false,
"type": "string",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
},
{
"id": "credit_cap",
"displayName": "credit_cap",
"required": false,
"type": "number",
"display": true,
"canBeUsedToMatch": true,
"defaultMatch": false
}
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
}
},
"type": "@n8n/n8n-nodes-langchain.toolWorkflow",
"typeVersion": 2.1,
"position": [
1340,
260
],
"id": "fc-wh-tool-credits",
"name": "check_credits"
},
{
"parameters": {
"name": "think",
"description": "Internal scratchpad. Input: thought (string). Returns the thought with a timestamp. Use generously.",
"language": "javaScript",
"jsCode": "const t = typeof query === 'string' ? query : (query && query.thought) ? query.thought : JSON.stringify(query);\nreturn `Thought recorded at ${new Date().toISOString()}: ${t}`;",
"specifyInputSchema": true,
"schemaType": "manual",
"inputSchema": "{\n \"type\": \"object\",\n \"properties\": {\n \"thought\": { \"type\": \"string\", \"description\": \"What you're planning, filtering, or diagnosing.\" }\n },\n \"required\": [\"thought\"]\n}"
},
"type": "@n8n/n8n-nodes-langchain.toolCode",
"typeVersion": 1.3,
"position": [
1520,
260
],
"id": "fc-wh-tool-think",
"name": "think"
},
{
"parameters": {
"jsCode": "const agentOut = $input.first().json;\nconst req = $('Parse Request').first().json;\n\n// agentOut.output is the agent's final answer (stringified JSON in most cases)\nlet result = agentOut.output || agentOut;\nif (typeof result === 'string') {\n // Try to parse JSON from the agent output \u2014 strip fences if present\n let s = result.trim().replace(/^```(?:json)?\\s*/i, '').replace(/```\\s*$/, '');\n try { result = JSON.parse(s); } catch { /* keep as string */ }\n}\n\nreturn [{ json: {\n session_id: req.session_id,\n prompt: req.prompt,\n output_schema_provided: req.has_schema,\n result\n} }];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-20,
0
],
"id": "fc-wh-shape",
"name": "Shape Response"
}
],
"connections": {
"POST /firecrawl-research": {
"main": [
[
{
"node": "Parse Request",
"type": "main",
"index": 0
}
]
]
},
"Parse Request": {
"main": [
[
{
"node": "Firecrawl Web Agent (Webhook)",
"type": "main",
"index": 0
}
]
]
},
"OpenRouter Chat Model": {
"ai_languageModel": [
[
{
"node": "Firecrawl Web Agent (Webhook)",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"search_web": {
"ai_tool": [
[
{
"node": "Firecrawl Web Agent (Webhook)",
"type": "ai_tool",
"index": 0
}
]
]
},
"map_site": {
"ai_tool": [
[
{
"node": "Firecrawl Web Agent (Webhook)",
"type": "ai_tool",
"index": 0
}
]
]
},
"scrape_url": {
"ai_tool": [
[
{
"node": "Firecrawl Web Agent (Webhook)",
"type": "ai_tool",
"index": 0
}
]
]
},
"batch_scrape": {
"ai_tool": [
[
{
"node": "Firecrawl Web Agent (Webhook)",
"type": "ai_tool",
"index": 0
}
]
]
},
"crawl_site": {
"ai_tool": [
[
{
"node": "Firecrawl Web Agent (Webhook)",
"type": "ai_tool",
"index": 0
}
]
]
},
"extract_data": {
"ai_tool": [
[
{
"node": "Firecrawl Web Agent (Webhook)",
"type": "ai_tool",
"index": 0
}
]
]
},
"firecrawl_autopilot": {
"ai_tool": [
[
{
"node": "Firecrawl Web Agent (Webhook)",
"type": "ai_tool",
"index": 0
}
]
]
},
"browser_session": {
"ai_tool": [
[
{
"node": "Firecrawl Web Agent (Webhook)",
"type": "ai_tool",
"index": 0
}
]
]
},
"check_credits": {
"ai_tool": [
[
{
"node": "Firecrawl Web Agent (Webhook)",
"type": "ai_tool",
"index": 0
}
]
]
},
"think": {
"ai_tool": [
[
{
"node": "Firecrawl Web Agent (Webhook)",
"type": "ai_tool",
"index": 0
}
]
]
},
"Firecrawl Web Agent (Webhook)": {
"main": [
[
{
"node": "Shape Response",
"type": "main",
"index": 0
}
]
]
}
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Firecrawl Web Crawler Agent (Webhook). Uses agent, lmChatOpenRouter, toolWorkflow, toolCode. Webhook trigger; 16 nodes.
Source: https://github.com/MinaSaad1/n8n-firecrawl-web-crawler-agent/blob/main/workflows/02-webhook-firecrawl-agent.json — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
🧪 LABR - nuevo asistente (REPARADO). Uses httpRequest, postgres, postgresTool, toolCalculator. Webhook trigger; 63 nodes.
🧪 LABR - nuevo asistente (REPARADO). Uses httpRequest, postgres, postgresTool, toolCalculator. Webhook trigger; 63 nodes.
🧪 LABR - nuevo asistente (REPARADO). Uses httpRequest, postgres, postgresTool, toolCalculator. Webhook trigger; 63 nodes.
🧪 LABR - nuevo asistente (REPARADO). Uses httpRequest, postgres, postgresTool, toolCode. Webhook trigger; 62 nodes.
🧪 LABR - nuevo asistente (REPARADO). Uses httpRequest, postgres, postgresTool, toolCode. Webhook trigger; 62 nodes.