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": "[DEV] post-call / transcript-field-extractor / v2",
"description": null,
"active": true,
"nodes": [
{
"id": "1",
"name": "Webhook Trigger",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2.1,
"position": [
0,
300
],
"parameters": {
"httpMethod": "POST",
"path": "extract-transcript-v2",
"responseMode": "responseNode",
"options": {},
"authentication": "headerAuth"
}
},
{
"id": "2",
"name": "Fetch ElevenLabs Data",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
220,
300
],
"parameters": {
"jsCode": "// Fetch conversation + agent using this.helpers.httpRequest\nconst items = $input.all();\nconst item = items[0];\nconst conversationId = item.json.body.conversation_id;\nconst apiKey = '<REDACTED:elevenlabs-api-key>';\n\nif (!conversationId) {\n return [{\n json: {\n error: true,\n errorCode: 'MISSING_CONVERSATION_ID',\n errorMessage: 'conversation_id is required in request body',\n webhookBody: item.json.body\n }\n }];\n}\n\ntry {\n const conv = await this.helpers.httpRequest({\n method: 'GET',\n url: `https://api.elevenlabs.io/v1/convai/conversations/${conversationId}`,\n headers: { 'xi-api-key': apiKey }\n });\n\n const agent = await this.helpers.httpRequest({\n method: 'GET',\n url: `https://api.elevenlabs.io/v1/convai/agents/${conv.agent_id}`,\n headers: { 'xi-api-key': apiKey }\n });\n\n return [{\n json: {\n error: false,\n conversation: conv,\n agent: agent,\n webhookBody: item.json.body\n }\n }];\n} catch (e) {\n const statusCode = e.response?.status || e.statusCode || 500;\n let errorMessage = e.message;\n \n if (statusCode === 404) {\n errorMessage = `Conversation not found: ${conversationId}`;\n } else if (statusCode === 401) {\n errorMessage = 'Invalid ElevenLabs API key';\n }\n \n return [{\n json: {\n error: true,\n errorCode: `ELEVENLABS_API_${statusCode}`,\n errorMessage: errorMessage,\n conversationId: conversationId,\n webhookBody: item.json.body\n }\n }];\n}"
}
},
{
"id": "7",
"name": "Check for Errors",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
440,
300
],
"parameters": {
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"conditions": [
{
"id": "error-check",
"leftValue": "={{ $json.error }}",
"rightValue": true,
"operator": {
"type": "boolean",
"operation": "equals"
}
}
],
"combinator": "and"
},
"options": {}
}
},
{
"id": "8",
"name": "Error Response",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.4,
"position": [
660,
200
],
"parameters": {
"respondWith": "json",
"responseCode": "={{ $json.errorCode.includes('404') ? 404 : 400 }}",
"responseBody": "={{ { success: false, error: $json.errorCode, message: $json.errorMessage, conversation_id: $json.conversationId || null, timestamp: new Date().toISOString() } }}"
}
},
{
"id": "3",
"name": "Assemble 5-Component Bulk Prompt",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
660,
400
],
"parameters": {
"jsCode": "const { conversation: conv, agent, webhookBody } = $json;\n\n// ============================================================================\n// COMPONENT A: CONVERSATION TRANSCRIPT RAW\n// ============================================================================\nlet transcriptRaw = '';\nif (Array.isArray(conv.transcript)) {\n transcriptRaw = conv.transcript\n .filter(t => t.message)\n .map(t => `${t.role.toUpperCase()}: ${t.message}`)\n .join('\\n');\n} else {\n transcriptRaw = conv.transcript || '';\n}\n\nif (!transcriptRaw || transcriptRaw.length === 0) {\n return {\n json: {\n error: true,\n errorCode: 'EMPTY_TRANSCRIPT',\n errorMessage: 'Conversation has no transcript data',\n conversationId: conv.conversation_id,\n agentId: conv.agent_id\n }\n };\n}\n\n// ============================================================================\n// COMPONENT B: EXTRACTOR AGENT'S SYSTEM PROMPT\n// ============================================================================\nconst agentSystemPrompt = agent.conversation_config?.agent?.prompt?.prompt || 'No agent prompt available';\n\n// ============================================================================\n// COMPONENT C: DESIRED RESPONSE STRUCTURED SCHEMA (field names and types)\n// ============================================================================\n// Use custom schema if provided, otherwise use the 22-field template\nconst defaultSchema = {\n \"requestor\": {\n \"requestor_company_name\": { \"type\": \"string\" },\n \"requestor_first_name\": { \"type\": \"string\" },\n \"requestor_last_name\": { \"type\": \"string\" }\n },\n \"contact\": {\n \"contact_first_name\": { \"type\": \"string\" },\n \"contact_last_name\": { \"type\": \"string\" },\n \"contact_phone_number\": { \"type\": \"string\" },\n \"contact_preferred_followup_channel\": { \"type\": \"string\" },\n \"contact_preferred_followup_time\": { \"type\": \"string\" },\n \"requested_service_address\": { \"type\": \"string\" },\n \"requestor_is_contact\": { \"type\": \"boolean\" }\n },\n \"request\": {\n \"existing_request\": { \"type\": \"boolean\" },\n \"request_affected_asset\": { \"type\": \"string\" },\n \"request_deadline\": { \"type\": \"string\" },\n \"request_description\": { \"type\": \"string\" },\n \"request_summary\": { \"type\": \"string\" }\n },\n \"routing\": {\n \"conversation_transfer_destination\": { \"type\": \"string\" },\n \"conversation_transfer_reason\": { \"type\": \"string\" },\n \"conversation_transferred\": { \"type\": \"boolean\" },\n \"requestor_requested_specific_person\": { \"type\": \"string\" },\n \"routing_department\": { \"type\": \"string\" },\n \"routing_site_location\": { \"type\": \"string\" },\n \"routing_urgency\": { \"type\": \"string\" }\n }\n};\n\nconst responseSchema = webhookBody.extraction_schema || defaultSchema;\n\n// ============================================================================\n// COMPONENT D: MULTI-PROMPT JSON TEMPLATE (detailed extraction instructions)\n// ============================================================================\nconst extractionInstructions = {\n \"requestor\": {\n \"requestor_company_name\": {\n \"type\": \"string\",\n \"description\": \"Extract requestor's employer/company name from self-identification phrases. Valid triggers: 'I'm with...', 'calling from...', 'I work for...'. Critical exclusions: (a) Never extract service provider name from agent greeting, (b) Ignore company being called. Return null when no company affiliation stated.\"\n },\n \"requestor_first_name\": {\n \"type\": \"string\",\n \"description\": \"Extract first name of person calling from self-introduction. Target phrases: 'This is [name]', 'My name is [name]'. Extract ONLY first name component. Return null if no first name provided.\"\n },\n \"requestor_last_name\": {\n \"type\": \"string\",\n \"description\": \"Extract ONLY last name of person calling. Exclude first names, titles/honorifics. Capture compound surnames completely (e.g., 'De La Vega'). Return null when no last name provided.\"\n }\n },\n \"contact\": {\n \"contact_first_name\": {\n \"type\": \"string\",\n \"description\": \"Extract first name of person designated for follow-up. Default: same as requestor_first_name. Override only when requestor explicitly designates alternate contact.\"\n },\n \"contact_last_name\": {\n \"type\": \"string\",\n \"description\": \"Extract last name of person designated for follow-up. Default: same as requestor's last name.\"\n },\n \"contact_phone_number\": {\n \"type\": \"string\",\n \"description\": \"Extract final confirmed callback phone number in E.164 format (+1XXXXXXXXXX). If user corrected a number, use correction only. Convert number words to digits. Return null if count < 10 digits or no number found.\"\n },\n \"contact_preferred_followup_channel\": {\n \"type\": \"string\",\n \"description\": \"Valid values: phone, sms, email. Detection triggers: 'call me', 'text me', 'send me an email'. Default to 'phone' when unclear.\"\n },\n \"contact_preferred_followup_time\": {\n \"type\": \"string\",\n \"description\": \"Extract follow-up timing preferences. Examples: 'tomorrow afternoon', 'after 6 PM only', 'weekdays before noon'. Return null when none mentioned.\"\n },\n \"requested_service_address\": {\n \"type\": \"string\",\n \"description\": \"Extract physical location WHERE WORK HAPPENS (dispatch destination). NOT the branch being contacted. Return null if no dispatch required or requestor coming to company location.\"\n },\n \"requestor_is_contact\": {\n \"type\": \"boolean\",\n \"description\": \"Is requestor the designated contact for follow-up? Default: TRUE. Set FALSE only when requestor makes explicit statement designating alternate contact.\"\n }\n },\n \"request\": {\n \"existing_request\": {\n \"type\": \"boolean\",\n \"description\": \"Is this regarding an existing request/ticket? Detection: reference to case/ticket numbers, 'following up on', 'checking status of'. Default FALSE when unclear.\"\n },\n \"request_affected_asset\": {\n \"type\": \"string\",\n \"description\": \"Extract specific equipment, device, software, or system involved. Include model numbers, asset IDs if stated. Return null when no specific asset mentioned.\"\n },\n \"request_deadline\": {\n \"type\": \"string\",\n \"description\": \"Extract requestor-specified date or timeframe. Examples: 'need this fixed by Friday', 'within 48 hours'. Return null when none mentioned.\"\n },\n \"request_description\": {\n \"type\": \"string\",\n \"description\": \"Generate single factual paragraph (80-120 words) using ONLY explicitly stated information. Inverted-pyramid format. Plain language, active voice. No future commitments.\"\n },\n \"request_summary\": {\n \"type\": \"string\",\n \"description\": \"Generate precise ticket title (<80 chars). Format: '[Equipment/Issue] - [Location]'. Avoid generic terms like 'issue', 'problem'. Include equipment ID if provided.\"\n }\n },\n \"routing\": {\n \"conversation_transfer_destination\": {\n \"type\": \"string\",\n \"description\": \"If transferred, capture destination label. Examples: 'Main operator', 'Billing department'. Return null if no transfer.\"\n },\n \"conversation_transfer_reason\": {\n \"type\": \"string\",\n \"description\": \"Short summary (\u00e2\u2030\u00a4120 chars) explaining WHY transferred. Return null if no transfer.\"\n },\n \"conversation_transferred\": {\n \"type\": \"boolean\",\n \"description\": \"Was conversation transferred to live person? Set TRUE only when actual transfer mechanism invoked. Default FALSE.\"\n },\n \"requestor_requested_specific_person\": {\n \"type\": \"string\",\n \"description\": \"Name of specific person requestor asks to speak with. Return null if no specific person requested by name.\"\n },\n \"routing_department\": {\n \"type\": \"string\",\n \"description\": \"EXACTLY one from: Service, Field Service, Sales, Marketing, Purchasing, Fulfillment, Shipping, Billing, Finance, Accounting, HR, Payroll, IT, IT Support, Operations, Contracts, Administration, General. Default 'General' when unclear.\"\n },\n \"routing_site_location\": {\n \"type\": \"string\",\n \"description\": \"Site or branch location for internal routing. Return null if none mentioned.\"\n },\n \"routing_urgency\": {\n \"type\": \"string\",\n \"description\": \"Valid values: emergency (safety/critical), soon (needs quick attention), routine (standard), estimate (quote only). Default 'routine' when unclear.\"\n }\n }\n};\n\n// Allow override from webhook body\nconst fieldInstructions = webhookBody.extraction_instructions || extractionInstructions;\n\n// ============================================================================\n// COMPONENT E: AGENT CONFIGURATION SPECIFICATION (full agent config)\n// ============================================================================\nconst agentConfig = {\n agent_id: agent.agent_id,\n name: agent.name,\n conversation_config: agent.conversation_config,\n platform_settings: agent.platform_settings,\n metadata: agent.metadata\n};\n\n// ============================================================================\n// ASSEMBLE THE 5-COMPONENT BULK PROMPT\n// ============================================================================\nconst bulkPrompt = `You are a precision data extraction system. Your task is to extract structured fields from a voice call transcript.\n\n================================================================================\nCOMPONENT A: CONVERSATION TRANSCRIPT (RAW)\n================================================================================\n${transcriptRaw}\n\n================================================================================\nCOMPONENT B: AGENT SYSTEM PROMPT (Context for understanding agent behavior)\n================================================================================\n${agentSystemPrompt}\n\n================================================================================\nCOMPONENT C: RESPONSE SCHEMA (Required output structure)\n================================================================================\n${JSON.stringify(responseSchema, null, 2)}\n\n================================================================================\nCOMPONENT D: EXTRACTION INSTRUCTIONS (Field-level micro-prompts)\n================================================================================\n${JSON.stringify(fieldInstructions, null, 2)}\n\n================================================================================\nCOMPONENT E: AGENT CONFIGURATION (Full agent specification)\n================================================================================\n${JSON.stringify(agentConfig, null, 2)}\n\n================================================================================\nEXTRACTION TASK\n================================================================================\nUsing the transcript (A), agent context (B, E), and the detailed extraction instructions (D), extract all fields defined in the schema (C).\n\nRULES:\n1. Use null for any field that cannot be determined from the transcript\n2. Follow the exact type specifications (string, boolean)\n3. Apply the micro-prompt instructions in Component D precisely\n4. Return ONLY valid JSON matching the schema structure\n5. Do not add explanations or commentary - return pure JSON\n\nOUTPUT: Return the extracted data as a JSON object with the exact structure from Component C.`;\n\n// Build Gemini request body\nconst geminiBody = JSON.stringify({\n contents: [{ parts: [{ text: bulkPrompt }] }],\n generationConfig: { \n temperature: 0.1, \n responseMimeType: 'application/json' \n }\n});\n\nreturn {\n json: {\n error: false,\n bulkPrompt: bulkPrompt,\n geminiBody: geminiBody,\n conversationId: conv.conversation_id,\n agentId: conv.agent_id,\n agentName: agent.name,\n transcriptLength: transcriptRaw.length,\n schemaFieldCount: Object.keys(fieldInstructions).reduce((acc, section) => acc + Object.keys(fieldInstructions[section]).length, 0),\n components: {\n A_transcript: true,\n B_agentPrompt: true,\n C_responseSchema: true,\n D_extractionInstructions: true,\n E_agentConfig: true\n }\n }\n};"
}
},
{
"id": "9",
"name": "Check Transcript Error",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
880,
400
],
"parameters": {
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"conditions": [
{
"id": "transcript-error-check",
"leftValue": "={{ $json.error }}",
"rightValue": true,
"operator": {
"type": "boolean",
"operation": "equals"
}
}
],
"combinator": "and"
},
"options": {}
}
},
{
"id": "10",
"name": "Empty Transcript Response",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.4,
"position": [
1100,
300
],
"parameters": {
"respondWith": "json",
"responseCode": 400,
"responseBody": "={{ { success: false, error: $json.errorCode, message: $json.errorMessage, conversation_id: $json.conversationId, agent_id: $json.agentId, timestamp: new Date().toISOString() } }}"
}
},
{
"id": "4",
"name": "Call Gemini 3 Pro",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1100,
500
],
"parameters": {
"method": "POST",
"url": "https://generativelanguage.googleapis.com/v1beta/models/gemini-3-pro-preview:generateContent?key=<REDACTED:google-api-key>",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ $json.geminiBody }}",
"options": {}
},
"retryOnFail": true,
"maxTries": 3,
"waitBetweenTries": 2000
},
{
"id": "5",
"name": "Parse Response",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1320,
500
],
"parameters": {
"jsCode": "const prev = $('Assemble 5-Component Bulk Prompt').item.json;\nconst response = $json;\nlet extracted = {};\n\ntry {\n const text = response.candidates[0].content.parts[0].text;\n extracted = JSON.parse(text);\n} catch(e) {\n extracted = { error: 'Parse failed', raw: response };\n}\n\nreturn {\n json: {\n success: true,\n conversation_id: prev.conversationId,\n agent_id: prev.agentId,\n agent_name: prev.agentName,\n extracted_fields: extracted,\n model: 'gemini-3-pro',\n architecture: '5-component-bulk-prompt',\n components_used: prev.components,\n schema_field_count: prev.schemaFieldCount,\n transcript_chars: prev.transcriptLength,\n timestamp: new Date().toISOString()\n }\n};"
}
},
{
"id": "6",
"name": "Success Response",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.4,
"position": [
1540,
500
],
"parameters": {
"respondWith": "json",
"responseBody": "={{ $json }}"
}
}
],
"connections": {
"Webhook Trigger": {
"main": [
[
{
"node": "Fetch ElevenLabs Data",
"type": "main",
"index": 0
}
]
]
},
"Fetch ElevenLabs Data": {
"main": [
[
{
"node": "Check for Errors",
"type": "main",
"index": 0
}
]
]
},
"Check for Errors": {
"main": [
[
{
"node": "Error Response",
"type": "main",
"index": 0
}
],
[
{
"node": "Assemble 5-Component Bulk Prompt",
"type": "main",
"index": 0
}
]
]
},
"Assemble 5-Component Bulk Prompt": {
"main": [
[
{
"node": "Check Transcript Error",
"type": "main",
"index": 0
}
]
]
},
"Check Transcript Error": {
"main": [
[
{
"node": "Empty Transcript Response",
"type": "main",
"index": 0
}
],
[
{
"node": "Call Gemini 3 Pro",
"type": "main",
"index": 0
}
]
]
},
"Call Gemini 3 Pro": {
"main": [
[
{
"node": "Parse Response",
"type": "main",
"index": 0
}
]
]
},
"Parse Response": {
"main": [
[
{
"node": "Success Response",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1",
"saveDataErrorExecution": "all",
"saveDataSuccessExecution": "all",
"saveManualExecutions": true,
"saveExecutionProgress": true,
"callerPolicy": "workflowsFromSameOwner",
"availableInMCP": true
},
"tags": [
"DEV"
]
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
[DEV] post-call / transcript-field-extractor / v2. Uses httpRequest. Webhook trigger; 10 nodes.
Source: https://github.com/wranngle/n8n_showcase/blob/main/workflows/post-call/transcript-field-extractor.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 .
eek-Go v2 (Batch-Then-Review). Uses httpRequest. Webhook trigger; 75 nodes.
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
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