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": "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"
}
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 →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
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
This flow creates dummy files for every item added in your *Arrs (Radarr/Sonarr) with the tag .
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
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.
📡 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