{
  "nodes": [
    {
      "id": "6a47226b-b440-4741-ac96-e0c018786666",
      "name": "Receive Call Recording Upload",
      "type": "n8n-nodes-base.webhook",
      "position": [
        592,
        432
      ],
      "webhookId": "call-analyzer-webhook",
      "parameters": {
        "path": "call-upload",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 2
    },
    {
      "id": "0172d15d-ef94-4d4e-b7e7-cd24b3149600",
      "name": "Validate Call Metadata and Audio",
      "type": "n8n-nodes-base.code",
      "position": [
        816,
        432
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const body = $input.item.json.body || $input.item.json;\n\nconst required = ['callId', 'repEmail', 'repName', 'contactEmail', 'contactName', 'companyName'];\nconst missing = required.filter(f => !body[f]);\nif (missing.length > 0) {\n  throw new Error(`Missing required call metadata: ${missing.join(', ')}`);\n}\n\nconst emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\nif (!emailRegex.test(body.repEmail)) {\n  throw new Error(`Invalid rep email: ${body.repEmail}`);\n}\nif (!emailRegex.test(body.contactEmail)) {\n  throw new Error(`Invalid contact email: ${body.contactEmail}`);\n}\n\nconst audioUrl = body.audioUrl || body.audio_url || null;\nif (!audioUrl && !$input.item.binary?.audio) {\n  throw new Error('No audio provided: supply audioUrl or binary audio attachment');\n}\n\nconst durationSecs = parseInt(body.callDurationSecs) || 0;\nif (durationSecs > 7200) {\n  throw new Error(`Call too long: ${durationSecs}s exceeds 2-hour maximum for processing`);\n}\n\nconst validStages = ['prospecting', 'qualification', 'discovery', 'demo', 'proposal', 'negotiation', 'closed_won', 'closed_lost'];\nconst dealStage = body.dealStage?.toLowerCase().replace(/ /g, '_') || 'unknown';\n\nconst callRecord = {\n  callId: body.callId.trim(),\n  repEmail: body.repEmail.trim().toLowerCase(),\n  repName: body.repName.trim(),\n  contactEmail: body.contactEmail.trim().toLowerCase(),\n  contactName: body.contactName.trim(),\n  companyName: body.companyName.trim(),\n  dealStage: validStages.includes(dealStage) ? dealStage : 'unknown',\n  callDurationSecs: durationSecs,\n  audioUrl,\n  receivedAt: new Date().toISOString(),\n  status: 'VALIDATED'\n};\n\nreturn { json: { callRecord } };"
      },
      "typeVersion": 2
    },
    {
      "id": "80e939d8-6099-41ea-8ea1-c3a88da1e03f",
      "name": "Fetch Audio File from URL",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1040,
        432
      ],
      "parameters": {
        "url": "={{ $json.callRecord.audioUrl }}",
        "options": {
          "timeout": 30000,
          "response": {
            "response": {
              "responseFormat": "file"
            }
          }
        }
      },
      "typeVersion": 4.2,
      "continueOnFail": true
    },
    {
      "id": "1f508b12-4ba4-4dcc-b1fc-a0164530cc5a",
      "name": "Transcribe Audio with OpenAI Whisper",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1264,
        432
      ],
      "parameters": {
        "url": "https://api.openai.com/v1/audio/transcriptions",
        "method": "POST",
        "options": {
          "timeout": 120000
        },
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "multipartFormData",
        "authentication": "predefinedCredentialType",
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "=Bearer {{ $credentials.apiKey }}"
            }
          ]
        },
        "nodeCredentialType": "openAiApi"
      },
      "credentials": {
        "openAiApi": {
          "id": "1vLQlwKIezvFH8UB",
          "name": "OpenAi David"
        }
      },
      "typeVersion": 4.2,
      "continueOnFail": true
    },
    {
      "id": "61fd5aa1-f5c4-4e0d-a5f0-5e8682d8ec73",
      "name": "Wait — Transcription Buffer (15 sec)",
      "type": "n8n-nodes-base.wait",
      "position": [
        1488,
        432
      ],
      "webhookId": "wait-transcription-buffer",
      "parameters": {
        "amount": 15
      },
      "typeVersion": 1.1
    },
    {
      "id": "cfaf7087-38a1-4d6f-8eb8-134dde1774c4",
      "name": "Parse and Structure Transcript",
      "type": "n8n-nodes-base.code",
      "position": [
        1712,
        432
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const whisperResponse = $('Transcribe Audio with OpenAI Whisper').item.json;\nconst callRecord = $('Validate Call Metadata and Audio').item.json.callRecord;\n\nif (!whisperResponse || whisperResponse.error) {\n  throw new Error(`Whisper transcription failed: ${whisperResponse?.error?.message || 'Unknown error'}`);\n}\n\nconst fullText = whisperResponse.text || '';\nif (!fullText.trim()) {\n  throw new Error('Transcription returned empty text. Audio may be silent or corrupted.');\n}\n\nreturn {\n  json: {\n    callRecord,\n    transcript: {\n      fullText,\n      language: whisperResponse.language || callRecord.language\n    }\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "9010cd1a-7b5f-4c19-a5b1-c18cdd435e55",
      "name": "Fetch CRM Contact History",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1936,
        432
      ],
      "parameters": {
        "url": "=https://api.hubapi.com/crm/v3/contacts/search",
        "method": "POST",
        "options": {
          "timeout": 10000
        },
        "jsonBody": "={\n  \"filterGroups\": [{\n    \"filters\": [{\n      \"propertyName\": \"email\",\n      \"operator\": \"EQ\",\n      \"value\": \"{{ $json.callRecord.contactEmail }}\"\n    }]\n  }],\n  \"properties\": [\"email\", \"firstname\", \"lastname\", \"company\", \"dealstage\"]\n}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "nodeCredentialType": "hubspotApi"
      },
      "credentials": {
        "hubspotApi": {
          "id": "W7t1pOQwFXrMGtqj",
          "name": "HubSpot account -test"
        }
      },
      "typeVersion": 4.2,
      "continueOnFail": true
    },
    {
      "id": "fd05714e-1a80-45d9-b1bd-473df7adb57f",
      "name": "GPT-4 Call Intelligence Analysis",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        2160,
        432
      ],
      "parameters": {
        "text": "=You are an expert sales call analyst. Analyze this sales call transcript and extract structured intelligence for CRM enrichment.\n\n**Call Metadata:**\n- Call ID: {{ $json.callRecord.callId }}\n- Sales Rep: {{ $json.callRecord.repName }}\n- Contact: {{ $json.callRecord.contactName }} at {{ $json.callRecord.companyName }}\n- Deal Stage: {{ $json.callRecord.dealStage }}\n\n**Call Transcript:**\n{{ $json.transcript.fullText }}\n\n**Response Format (JSON only):**\n{\n  \"overallSentiment\": \"positive | neutral | negative | mixed\",\n  \"primaryIntent\": \"evaluation | complaint | purchase_ready | information_gathering | negotiation | churn_risk | renewal\",\n  \"callSummary\": \"3-4 sentence plain English summary of the call\",\n  \"objections\": [\n    {\n      \"type\": \"price | timing | competition | authority | need | trust\",\n      \"verbatim\": \"exact or close quote from transcript\"\n    }\n  ],\n  \"nextSteps\": [\n    {\n      \"action\": \"description of next step\",\n      \"owner\": \"rep | prospect | both\",\n      \"deadline\": \"date or timeframe or null\"\n    }\n  ],\n  \"crmUpdates\": {\n    \"dealStage\": \"recommended stage\",\n    \"contactNotes\": \"2-3 sentence CRM note to log\"\n  }\n}",
        "options": {
          "systemMessage": "You are a sales call intelligence analyst. Respond with valid JSON only."
        },
        "promptType": "define"
      },
      "typeVersion": 1.6
    },
    {
      "id": "e82ed233-d4d9-4cff-a6b5-8691b2b68bc2",
      "name": "GPT-4 Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        2232,
        656
      ],
      "parameters": {
        "model": "gpt-4o",
        "options": {
          "maxTokens": 4000,
          "temperature": 0.2
        }
      },
      "credentials": {
        "openAiApi": {
          "id": "1vLQlwKIezvFH8UB",
          "name": "OpenAi David"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "3d588a16-4e9d-49b8-8c9f-4a434fa05ead",
      "name": "Parse GPT-4 Intelligence Output",
      "type": "n8n-nodes-base.code",
      "position": [
        2512,
        432
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const aiResponse = $input.item.json;\nlet aiText = aiResponse.response || aiResponse.output || aiResponse.text || '';\n\nconst cleanText = aiText.trim();\n\nlet intelligence;\ntry {\n  intelligence = JSON.parse(cleanText);\n} catch (e) {\n  throw new Error(`Failed to parse GPT-4 call intelligence: ${e.message}. Raw: ${cleanText.substring(0, 300)}`);\n}\n\nreturn {\n  json: {\n    intelligence\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "a73f3a33-23df-481d-b600-20510aa54ef9",
      "name": "Update CRM Contact and Deal",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        2736,
        336
      ],
      "parameters": {
        "url": "=https://api.hubapi.com/crm/v3/contacts/{{ $json.crmContactId || 'search' }}",
        "method": "PATCH",
        "options": {
          "timeout": 15000
        },
        "jsonBody": "={\n  \"properties\": {\n    \"hs_lead_status\": \"{{ $json.intelligence.recommendedDealStage }}\",\n    \"notes_last_contacted\": \"{{ new Date().toISOString() }}\",\n    \"hubspot_owner_id\": \"{{ $json.callRecord.repEmail }}\",\n    \"call_sentiment_score\": \"{{ $json.intelligence.sentimentScore }}\",\n    \"last_call_id\": \"{{ $json.callRecord.callId }}\",\n    \"last_call_summary\": \"{{ $json.intelligence.callSummary }}\"\n  }\n}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "nodeCredentialType": "hubspotApi"
      },
      "credentials": {
        "hubspotApi": {
          "id": "W7t1pOQwFXrMGtqj",
          "name": "HubSpot account -test"
        }
      },
      "typeVersion": 4.2,
      "continueOnFail": true
    },
    {
      "id": "23b60433-4148-472b-ad0f-894bd1653b79",
      "name": "Log Call Activity in CRM",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        2736,
        528
      ],
      "parameters": {
        "url": "https://api.hubapi.com/crm/v3/objects/calls",
        "method": "POST",
        "options": {
          "timeout": 10000
        },
        "jsonBody": "={\n  \"properties\": {\n    \"hs_call_title\": \"Sales Call — {{ $json.callRecord.companyName }} | {{ $json.intelligence.primaryIntent }}\",\n    \"hs_call_body\": \"{{ $json.intelligence.crmUpdates.contactNotes }}\\n\\nNext Steps:\\n{{ ($json.intelligence.nextSteps || []).map((ns, i) => (i+1) + '. ' + ns.action).join('\\n') }}\",\n    \"hs_call_duration\": \"{{ $json.callRecord.callDurationSecs * 1000 }}\",\n    \"hs_call_direction\": \"OUTBOUND\",\n    \"hs_call_status\": \"COMPLETED\",\n    \"hs_timestamp\": \"{{ new Date($json.callRecord.callDate).getTime() }}\"\n  },\n  \"associations\": [\n    {\n      \"to\": { \"id\": \"{{ $json.crmContactId }}\" },\n      \"types\": [{ \"associationCategory\": \"HUBSPOT_DEFINED\", \"associationTypeId\": 194 }]\n    }\n  ]\n}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "nodeCredentialType": "hubspotApi"
      },
      "credentials": {
        "hubspotApi": {
          "id": "W7t1pOQwFXrMGtqj",
          "name": "HubSpot account -test"
        }
      },
      "typeVersion": 4.2,
      "continueOnFail": true
    },
    {
      "id": "f78882a7-647b-4cf3-83a8-61340b455519",
      "name": "Send Call Intelligence Summary to Sales Team",
      "type": "n8n-nodes-base.emailSend",
      "position": [
        2960,
        336
      ],
      "webhookId": "a30d7263-2e75-4db1-9862-0c9fa1d55af8",
      "parameters": {
        "options": {},
        "subject": "=[Call Intelligence] {{ $json.callRecord.companyName }} — {{ $json.intelligence.overallSentiment.toUpperCase() }} | {{ $json.callRecord.callId }}",
        "toEmail": "=",
        "fromEmail": "="
      },
      "credentials": {
        "smtp": {
          "id": "G1kyF8cSWTZ4vouN",
          "name": "SMTP -test"
        }
      },
      "typeVersion": 2.1,
      "continueOnFail": true
    },
    {
      "id": "29209d55-d9c2-4b04-8945-7d7e05d8f4c9",
      "name": "Write to Call Analytics Audit Log",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        2960,
        528
      ],
      "parameters": {
        "columns": {
          "value": {},
          "schema": [],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "id",
          "value": "="
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "="
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "sldw2qK7iVusK0rY",
          "name": "Google Sheets account"
        }
      },
      "typeVersion": 4.5,
      "continueOnFail": true
    },
    {
      "id": "f315cf24-d388-49b2-b0f9-f1fe799507a6",
      "name": "Build Final Processing Response",
      "type": "n8n-nodes-base.code",
      "position": [
        3184,
        432
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const data = $input.item.json;\n\nreturn {\n  json: {\n    success: true,\n    callId: data.callRecord.callId,\n    transcriptionStats: {\n      wordCount: data.transcript.wordCount,\n      durationSecs: data.transcript.totalDurationSecs,\n      language: data.transcript.language\n    },\n    callIntelligence: {\n      sentiment: data.intelligence.overallSentiment,\n      primaryIntent: data.intelligence.primaryIntent,\n      recommendedStage: data.intelligence.recommendedDealStage\n    },\n    crmUpdated: true,\n    processedAt: data.processedAt\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "fb526474-5ad5-4c69-8447-d36a4661da49",
      "name": "Return Processing Confirmation",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        3408,
        432
      ],
      "parameters": {
        "options": {
          "responseHeaders": {
            "entries": [
              {
                "name": "Content-Type",
                "value": "application/json"
              },
              {
                "name": "X-Processing-ID",
                "value": "={{ $json.processingId }}"
              },
              {
                "name": "X-Call-Sentiment",
                "value": "={{ $json.callIntelligence.sentiment }}"
              }
            ]
          },
          "respondWith": "json",
          "responseBody": "={{ JSON.stringify($json, null, 2) }}"
        },
        "typeVersion": 1
      }
    }
  ],
  "connections": {
    "Receive Call Recording Upload": {
      "main": [
        [
          {
            "node": "Validate Call Metadata and Audio",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate Call Metadata and Audio": {
      "main": [
        [
          {
            "node": "Fetch Audio File from URL",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Audio File from URL": {
      "main": [
        [
          {
            "node": "Transcribe Audio with OpenAI Whisper",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Transcribe Audio with OpenAI Whisper": {
      "main": [
        [
          {
            "node": "Wait — Transcription Buffer (15 sec)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait — Transcription Buffer (15 sec)": {
      "main": [
        [
          {
            "node": "Parse and Structure Transcript",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse and Structure Transcript": {
      "main": [
        [
          {
            "node": "Fetch CRM Contact History",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch CRM Contact History": {
      "main": [
        [
          {
            "node": "GPT-4 Call Intelligence Analysis",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GPT-4 Call Intelligence Analysis": {
      "main": [
        [
          {
            "node": "GPT-4 Model",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GPT-4 Model": {
      "main": [
        [
          {
            "node": "Parse GPT-4 Intelligence Output",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse GPT-4 Intelligence Output": {
      "main": [
        [
          {
            "node": "Update CRM Contact and Deal",
            "type": "main",
            "index": 0
          },
          {
            "node": "Log Call Activity in CRM",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update CRM Contact and Deal": {
      "main": [
        [
          {
            "node": "Send Call Intelligence Summary to Sales Team",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Call Intelligence Summary to Sales Team": {
      "main": [
        [
          {
            "node": "Build Final Processing Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Final Processing Response": {
      "main": [
        [
          {
            "node": "Return Processing Confirmation",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}