AutomationFlowsWeb Scraping › Retell Transcript Pipeline to FastAPI

Retell Transcript Pipeline to FastAPI

Original n8n title: Clara Answers — Transcript Pipeline

Clara Answers — Transcript Pipeline. Uses httpRequest, writeFile. Webhook trigger; 15 nodes.

Webhook trigger★★★★☆ complexity15 nodesHTTP RequestWrite File
Web Scraping Trigger: Webhook Nodes: 15 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
{
  "name": "Clara Answers \u2014 Transcript Pipeline",
  "nodes": [
    {
      "parameters": {
        "path": "retell-webhook",
        "responseMode": "responseNode",
        "options": {}
      },
      "id": "node-webhook-retell",
      "name": "Retell Webhook Trigger",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 1.1,
      "position": [
        240,
        300
      ],
      "notes": "Receives POST from Retell AI call_ended event.\nConfigure in Retell Dashboard \u2192 Agent \u2192 Webhook URL.\nExpected: event, call_id, transcript, metadata.call_type, metadata.account_id"
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "minutes",
              "minutesInterval": 5
            }
          ]
        }
      },
      "id": "node-schedule-inbox",
      "name": "Inbox Poller (Every 5min)",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.1,
      "position": [
        240,
        500
      ],
      "notes": "Polls data/inbox/ for new transcript files (batch mode)."
    },
    {
      "parameters": {
        "jsCode": "// Route: Retell webhook vs file-based trigger\nconst items = $input.all();\nconst item = items[0];\n\nlet source = 'unknown';\nlet transcript = null;\nlet call_type = 'demo';\nlet account_id = null;\nlet company_name = null;\n\nif (item.json.body) {\n  // Retell webhook path\n  const body = item.json.body;\n  source = 'retell_webhook';\n  transcript = body.transcript || '';\n  call_type = (body.metadata && body.metadata.call_type) || 'demo';\n  account_id = (body.metadata && body.metadata.account_id) || null;\n  company_name = (body.metadata && body.metadata.company_name) || null;\n  \n  if (body.event !== 'call_ended') {\n    return [{ json: { skip: true, reason: 'not_call_ended' } }];\n  }\n  if (!transcript) {\n    return [{ json: { skip: true, reason: 'empty_transcript' } }];\n  }\n}\n\nreturn [{\n  json: {\n    source,\n    transcript,\n    call_type,\n    account_id,\n    company_name,\n    skip: false\n  }\n}];"
      },
      "id": "node-normalize",
      "name": "Normalize Input",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        480,
        300
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": false,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "skip-check",
              "leftValue": "={{ $json.skip }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "equal"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "node-skip-check",
      "name": "Skip Check",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        720,
        300
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "http://localhost:8000/transcripts/submit",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "transcript_text",
              "value": "={{ $json.transcript }}"
            },
            {
              "name": "transcript_type",
              "value": "={{ $json.call_type }}"
            },
            {
              "name": "account_id",
              "value": "={{ $json.account_id }}"
            },
            {
              "name": "company_name",
              "value": "={{ $json.company_name }}"
            }
          ]
        },
        "options": {
          "timeout": 30000
        }
      },
      "id": "node-submit-to-fastapi",
      "name": "Submit to FastAPI",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        960,
        200
      ],
      "notes": "Sends normalized transcript to FastAPI /transcripts/submit.\nFastAPI runs the full pipeline asynchronously."
    },
    {
      "parameters": {
        "jsCode": "// Wait 8 seconds then poll for results\nawait new Promise(r => setTimeout(r, 8000));\n\nconst accountId = $('Submit to FastAPI').first().json.account_id;\nreturn [{ json: { account_id: accountId } }];"
      },
      "id": "node-wait-for-pipeline",
      "name": "Wait for Pipeline",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1200,
        200
      ],
      "notes": "Brief wait to allow async pipeline to complete before result check."
    },
    {
      "parameters": {
        "method": "GET",
        "url": "=http://localhost:8000/accounts/{{ $json.account_id }}",
        "options": {}
      },
      "id": "node-check-results",
      "name": "Check Results",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1440,
        200
      ]
    },
    {
      "parameters": {
        "conditions": {
          "conditions": [
            {
              "id": "v2-check",
              "leftValue": "={{ Object.keys($json.versions || {}).includes('v2') }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "equal"
              }
            }
          ],
          "combinator": "and"
        }
      },
      "id": "node-v2-exists",
      "name": "Has v2?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        1680,
        200
      ]
    },
    {
      "parameters": {
        "method": "GET",
        "url": "=http://localhost:8000/accounts/{{ $json.account_id }}/changelog",
        "options": {}
      },
      "id": "node-fetch-changelog",
      "name": "Fetch Changelog",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1920,
        120
      ],
      "notes": "Fetches markdown diff for v1\u2192v2 transition."
    },
    {
      "parameters": {
        "filePath": "=changelogs/{{ $json.account_id }}_pipeline_log.json",
        "dataPropertyName": "data",
        "options": {}
      },
      "id": "node-log-success",
      "name": "Log Success",
      "type": "n8n-nodes-base.writeFile",
      "typeVersion": 1,
      "position": [
        2160,
        200
      ]
    },
    {
      "parameters": {
        "jsCode": "// Scan data/inbox for unprocessed .txt files\nconst fs = require('fs');\nconst path = require('path');\n\nconst inboxDir = 'data/inbox';\nconst processedLog = 'data/processed.json';\n\nlet processed = [];\nif (fs.existsSync(processedLog)) {\n  processed = JSON.parse(fs.readFileSync(processedLog, 'utf8'));\n}\n\nif (!fs.existsSync(inboxDir)) {\n  return [{ json: { files: [] } }];\n}\n\nconst files = fs.readdirSync(inboxDir)\n  .filter(f => f.endsWith('.txt') && !processed.includes(f))\n  .map(f => ({\n    filename: f,\n    filepath: path.join(inboxDir, f),\n    call_type: f.startsWith('onboarding') ? 'onboarding' : 'demo',\n    transcript: fs.readFileSync(path.join(inboxDir, f), 'utf8')\n  }));\n\nreturn files.map(f => ({ json: f }));"
      },
      "id": "node-scan-inbox",
      "name": "Scan Inbox",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        480,
        500
      ],
      "notes": "Reads data/inbox/ for .txt files not yet processed.\nFilenames starting with 'onboarding' are treated as onboarding type."
    },
    {
      "parameters": {
        "method": "POST",
        "url": "http://localhost:8000/transcripts/submit",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "transcript_text",
              "value": "={{ $json.transcript }}"
            },
            {
              "name": "transcript_type",
              "value": "={{ $json.call_type }}"
            }
          ]
        }
      },
      "id": "node-batch-submit",
      "name": "Batch Submit Files",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        720,
        500
      ]
    },
    {
      "parameters": {
        "jsCode": "// Mark file as processed\nconst fs = require('fs');\nconst processedLog = 'data/processed.json';\nconst filename = $('Scan Inbox').first().json.filename;\n\nlet processed = [];\nif (fs.existsSync(processedLog)) {\n  processed = JSON.parse(fs.readFileSync(processedLog, 'utf8'));\n}\n\nif (!processed.includes(filename)) {\n  processed.push(filename);\n  fs.writeFileSync(processedLog, JSON.stringify(processed, null, 2));\n}\n\nreturn [{ json: { marked: filename } }];"
      },
      "id": "node-mark-processed",
      "name": "Mark Processed",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        960,
        500
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={ \"status\": \"received\", \"message\": \"Pipeline triggered\" }",
        "options": {}
      },
      "id": "node-webhook-response",
      "name": "Webhook Response",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1,
      "position": [
        960,
        360
      ],
      "notes": "Returns 200 to Retell immediately so it doesn't retry."
    },
    {
      "parameters": {
        "jsCode": "// Log skip event\nconsole.log('[SKIP]', $json.reason);\nreturn [{ json: { skipped: true, reason: $json.reason } }];"
      },
      "id": "node-skip-log",
      "name": "Log Skip",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        960,
        440
      ]
    }
  ],
  "connections": {
    "Retell Webhook Trigger": {
      "main": [
        [
          {
            "node": "Normalize Input",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Inbox Poller (Every 5min)": {
      "main": [
        [
          {
            "node": "Scan Inbox",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Input": {
      "main": [
        [
          {
            "node": "Skip Check",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Skip Check": {
      "main": [
        [
          {
            "node": "Log Skip",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Submit to FastAPI",
            "type": "main",
            "index": 0
          },
          {
            "node": "Webhook Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Submit to FastAPI": {
      "main": [
        [
          {
            "node": "Wait for Pipeline",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait for Pipeline": {
      "main": [
        [
          {
            "node": "Check Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Results": {
      "main": [
        [
          {
            "node": "Has v2?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Has v2?": {
      "main": [
        [
          {
            "node": "Fetch Changelog",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Log Success",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Changelog": {
      "main": [
        [
          {
            "node": "Log Success",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Scan Inbox": {
      "main": [
        [
          {
            "node": "Batch Submit Files",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Batch Submit Files": {
      "main": [
        [
          {
            "node": "Mark Processed",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1",
    "saveManualExecutions": true,
    "callerPolicy": "workflowsFromSameOwner",
    "errorWorkflow": ""
  },
  "staticData": null,
  "tags": [
    "clara",
    "pipeline",
    "retell"
  ],
  "triggerCount": 2,
  "updatedAt": "2024-01-01T00:00:00.000Z",
  "versionId": "clara-v1"
}
Pro

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

About this workflow

Clara Answers — Transcript Pipeline. Uses httpRequest, writeFile. Webhook trigger; 15 nodes.

Source: https://github.com/PranavTJ-05/clara-ai/blob/3ab3cd8cea890e79abcb4959d493b5c50adc0965/workflows/clara_pipeline.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

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
Web Scraping

Sign PDF documents with legally-compliant digital signatures using X.509 certificates. Supports multiple PAdES signature levels (B, T, LT, LTA) with optional visible stamps.

Execute Command, HTTP Request, Read Write File +1
Web Scraping

📡 This workflow serves as the central Alpha Vantage API fetcher for Tesla trading indicators, delivering cleaned 20-point JSON outputs for three timeframes: , , and . It is required by the following a

HTTP Request