{
  "name": "Analyze Retell AI voice call transcripts and sync insights to HubSpot",
  "tags": [],
  "nodes": [
    {
      "id": "a1000001-0000-0000-0000-000000000001",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -480,
        -340
      ],
      "parameters": {
        "color": 6,
        "width": 540,
        "height": 480,
        "content": "## Retell AI Call Analyzer \u2192 HubSpot\n\nAutomatically analyze voice call transcripts and sync AI-powered insights to your CRM.\n\n### How it works\n1. **Webhook:** Receives the call transcript from Retell AI when a call ends.\n2. **Parse:** Extracts transcript text, caller phone number, and call metadata.\n3. **AI:** OpenAI analyzes sentiment, scores the lead 1-10, and lists action items.\n4. **CRM:** Updates the HubSpot contact record with call insights.\n5. **Route:** Hot leads trigger a Slack alert + HubSpot follow-up task. Every call is logged to Sheets.\n\n### Setup steps\n1. **Config:** Open the \"Set user config variables\" node \u2014 enter your Slack channel, score threshold, and Sheet ID.\n2. **Credentials:** Connect OpenAI, HubSpot, Slack, and Google Sheets in each node.\n3. **Retell:** Copy the webhook URL and paste it in your Retell dashboard \u2192 Webhook Settings \u2192 `call_analyzed`.\n4. **Test:** Activate the workflow, make a test call, and check that data flows through."
      },
      "typeVersion": 1
    },
    {
      "id": "a1000001-0000-0000-0000-000000000002",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        40,
        60
      ],
      "parameters": {
        "color": 7,
        "width": 460,
        "height": 200,
        "content": "## 1. Trigger & Config\nRetell sends a webhook when a call finishes. The config node holds your editable settings."
      },
      "typeVersion": 1
    },
    {
      "id": "a1000001-0000-0000-0000-000000000003",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        560,
        60
      ],
      "parameters": {
        "color": 7,
        "width": 460,
        "height": 200,
        "content": "## 2. AI Analysis\nOpenAI reads the full transcript and returns structured JSON: sentiment, lead score, action items, and a summary."
      },
      "typeVersion": 1
    },
    {
      "id": "a1000001-0000-0000-0000-000000000004",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1100,
        60
      ],
      "parameters": {
        "color": 7,
        "width": 620,
        "height": 200,
        "content": "## 3. CRM & Routing\nUpdates the HubSpot contact. Qualified leads trigger a Slack alert and a HubSpot follow-up task."
      },
      "typeVersion": 1
    },
    {
      "id": "a1000001-0000-0000-0000-000000000005",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1100,
        480
      ],
      "parameters": {
        "color": 7,
        "width": 320,
        "height": 160,
        "content": "## 4. Logging\nEvery call is appended to Google Sheets for reporting and tracking."
      },
      "typeVersion": 1
    },
    {
      "id": "a1000001-1000-0000-0000-000000000001",
      "name": "Receive Retell call webhook",
      "type": "n8n-nodes-base.webhook",
      "position": [
        80,
        300
      ],
      "parameters": {
        "path": "retell-call-analyzed",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 2
    },
    {
      "id": "a1000001-1000-0000-0000-000000000002",
      "name": "Set user config variables",
      "type": "n8n-nodes-base.set",
      "position": [
        300,
        300
      ],
      "parameters": {
        "mode": "manual",
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "cfg-1",
              "name": "slackChannel",
              "type": "string",
              "value": "#sales-alerts"
            },
            {
              "id": "cfg-2",
              "name": "leadScoreThreshold",
              "type": "number",
              "value": "7"
            },
            {
              "id": "cfg-3",
              "name": "googleSheetId",
              "type": "string",
              "value": "YOUR_GOOGLE_SHEET_ID_HERE"
            },
            {
              "id": "cfg-4",
              "name": "sheetName",
              "type": "string",
              "value": "Call Log"
            }
          ]
        },
        "duplicateItem": false,
        "includeOtherFields": true
      },
      "typeVersion": 3.4
    },
    {
      "id": "a1000001-1000-0000-0000-000000000003",
      "name": "Parse call transcript and metadata",
      "type": "n8n-nodes-base.code",
      "position": [
        520,
        300
      ],
      "parameters": {
        "jsCode": "// Parse Retell call_analyzed webhook payload\nconst input = $input.first().json;\nconst body = input.body || input;\n\nconst callId = body.call_id || body.call?.call_id || 'unknown';\nconst agentId = body.agent_id || body.call?.agent_id || 'unknown';\nconst fromNumber = body.from_number || body.call?.from_number || 'unknown';\nconst toNumber = body.to_number || body.call?.to_number || 'unknown';\nconst callDuration = body.call_duration_ms || body.duration_ms || 0;\nconst callStatus = body.call_status || body.status || 'completed';\nconst disconnectReason = body.disconnect_reason || 'unknown';\n\n// Extract transcript\nlet transcript = '';\nif (body.transcript) {\n  transcript = body.transcript;\n} else if (body.call?.transcript) {\n  transcript = body.call.transcript;\n} else if (Array.isArray(body.transcript_object)) {\n  transcript = body.transcript_object\n    .map(t => `${t.role}: ${t.content}`)\n    .join('\\n');\n}\n\n// Pass config variables through\nconst slackChannel = input.slackChannel || '#sales-alerts';\nconst leadScoreThreshold = input.leadScoreThreshold || 7;\nconst googleSheetId = input.googleSheetId || '';\nconst sheetName = input.sheetName || 'Call Log';\n\nreturn [{\n  json: {\n    callId,\n    agentId,\n    fromNumber,\n    toNumber,\n    callDurationSeconds: Math.round(callDuration / 1000),\n    callStatus,\n    disconnectReason,\n    transcript,\n    slackChannel,\n    leadScoreThreshold,\n    googleSheetId,\n    sheetName,\n    analyzedAt: new Date().toISOString()\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "a1000001-1000-0000-0000-000000000004",
      "name": "Analyze call with OpenAI",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        760,
        300
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4o-mini"
        },
        "options": {
          "maxTokens": 1500,
          "temperature": 0.2
        },
        "messages": {
          "values": [
            {
              "content": "=You are a sales call analyst. Analyze this voice call transcript and return a JSON object with these fields:\n\n1. \"sentiment\": one of \"positive\", \"neutral\", \"negative\"\n2. \"leadScore\": integer 1-10 (10 = very qualified buyer)\n3. \"summary\": 2-3 sentence call summary\n4. \"actionItems\": array of strings listing follow-up actions\n5. \"keyTopics\": array of main topics discussed\n6. \"buyingSignals\": array of phrases indicating purchase intent\n7. \"objections\": array of concerns or objections raised\n8. \"recommendedNextStep\": one short sentence\n\nTranscript:\n{{ $json.transcript }}\n\nCall duration: {{ $json.callDurationSeconds }} seconds\nCall status: {{ $json.callStatus }}\n\nReturn ONLY valid JSON, no markdown formatting."
            }
          ]
        },
        "resource": "chat"
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.8
    },
    {
      "id": "a1000001-1000-0000-0000-000000000005",
      "name": "Extract and structure AI analysis",
      "type": "n8n-nodes-base.code",
      "position": [
        1000,
        300
      ],
      "parameters": {
        "jsCode": "// Extract and structure the AI analysis response\nconst input = $input.first().json;\nconst prev = $('Parse call transcript and metadata').first().json;\n\nlet analysis;\ntry {\n  const content = input.message?.content || input.text || input.content || JSON.stringify(input);\n  const jsonStr = content.replace(/```json\\n?/g, '').replace(/```\\n?/g, '').trim();\n  analysis = JSON.parse(jsonStr);\n} catch (e) {\n  analysis = {\n    sentiment: 'neutral',\n    leadScore: 5,\n    summary: 'AI analysis could not be parsed. Manual review recommended.',\n    actionItems: ['Review call recording manually'],\n    keyTopics: [],\n    buyingSignals: [],\n    objections: [],\n    recommendedNextStep: 'Follow up with prospect'\n  };\n}\n\nreturn [{\n  json: {\n    ...prev,\n    sentiment: analysis.sentiment,\n    leadScore: analysis.leadScore,\n    summary: analysis.summary,\n    actionItems: analysis.actionItems || [],\n    keyTopics: analysis.keyTopics || [],\n    buyingSignals: analysis.buyingSignals || [],\n    objections: analysis.objections || [],\n    recommendedNextStep: analysis.recommendedNextStep || 'Follow up',\n    isQualified: analysis.leadScore >= (prev.leadScoreThreshold || 7)\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "a1000001-1000-0000-0000-000000000006",
      "name": "Update HubSpot contact with insights",
      "type": "n8n-nodes-base.hubspot",
      "position": [
        1280,
        200
      ],
      "parameters": {
        "resource": "contact",
        "contactId": "={{ $json.fromNumber }}",
        "operation": "update",
        "additionalFields": {
          "properties": [
            {
              "key": "last_call_summary",
              "value": "={{ $json.summary }}"
            },
            {
              "key": "lead_score",
              "value": "={{ $json.leadScore }}"
            },
            {
              "key": "last_call_sentiment",
              "value": "={{ $json.sentiment }}"
            },
            {
              "key": "last_call_date",
              "value": "={{ $json.analyzedAt }}"
            }
          ]
        }
      },
      "credentials": {
        "hubspotApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "a1000001-1000-0000-0000-000000000007",
      "name": "Check if lead is qualified",
      "type": "n8n-nodes-base.if",
      "position": [
        1280,
        400
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "cond-1",
              "operator": {
                "type": "boolean",
                "operation": "true"
              },
              "leftValue": "={{ $json.isQualified }}",
              "rightValue": true
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "a1000001-1000-0000-0000-000000000008",
      "name": "Alert sales team about hot lead",
      "type": "n8n-nodes-base.slack",
      "position": [
        1540,
        300
      ],
      "parameters": {
        "text": "=:fire: *Hot Lead Detected!*\n\n*Phone:* {{ $('Extract and structure AI analysis').first().json.fromNumber }}\n*Lead Score:* {{ $('Extract and structure AI analysis').first().json.leadScore }}/10\n*Sentiment:* {{ $('Extract and structure AI analysis').first().json.sentiment }}\n\n*Summary:* {{ $('Extract and structure AI analysis').first().json.summary }}\n\n*Buying Signals:*\n{{ $('Extract and structure AI analysis').first().json.buyingSignals.join('\\n\u2022 ') }}\n\n*Recommended Next Step:* {{ $('Extract and structure AI analysis').first().json.recommendedNextStep }}",
        "channel": {
          "__rl": true,
          "mode": "name",
          "value": "={{ $('Extract and structure AI analysis').first().json.slackChannel }}"
        },
        "resource": "message",
        "otherOptions": {}
      },
      "credentials": {
        "slackApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "a1000001-1000-0000-0000-000000000009",
      "name": "Create follow-up task in HubSpot",
      "type": "n8n-nodes-base.hubspot",
      "position": [
        1540,
        480
      ],
      "parameters": {
        "resource": "task",
        "operation": "create",
        "additionalFields": {
          "body": "={{ $('Extract and structure AI analysis').first().json.recommendedNextStep }}\n\nAction Items:\n{{ $('Extract and structure AI analysis').first().json.actionItems.join('\\n- ') }}",
          "status": "NOT_STARTED",
          "subject": "=Follow up with hot lead: {{ $('Extract and structure AI analysis').first().json.fromNumber }}",
          "priority": "HIGH"
        }
      },
      "credentials": {
        "hubspotApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "a1000001-1000-0000-0000-000000000010",
      "name": "Log call analysis to tracking sheet",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1280,
        600
      ],
      "parameters": {
        "columns": {
          "value": {
            "Date": "={{ $('Extract and structure AI analysis').first().json.analyzedAt }}",
            "Call ID": "={{ $('Extract and structure AI analysis').first().json.callId }}",
            "Summary": "={{ $('Extract and structure AI analysis').first().json.summary }}",
            "Qualified": "={{ $('Extract and structure AI analysis').first().json.isQualified }}",
            "Sentiment": "={{ $('Extract and structure AI analysis').first().json.sentiment }}",
            "Lead Score": "={{ $('Extract and structure AI analysis').first().json.leadScore }}",
            "From Number": "={{ $('Extract and structure AI analysis').first().json.fromNumber }}",
            "Action Items": "={{ $('Extract and structure AI analysis').first().json.actionItems.join('; ') }}",
            "Duration (sec)": "={{ $('Extract and structure AI analysis').first().json.callDurationSeconds }}"
          },
          "mappingMode": "defineBelow"
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "={{ $('Extract and structure AI analysis').first().json.sheetName }}"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Extract and structure AI analysis').first().json.googleSheetId }}"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "a1000001-1000-0000-0000-000000000011",
      "name": "Respond to Retell webhook",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        1540,
        140
      ],
      "parameters": {
        "respondWith": "json",
        "responseBody": "={\"status\": \"processed\", \"callId\": \"{{ $('Parse call transcript and metadata').first().json.callId }}\"}"
      },
      "typeVersion": 1.1
    }
  ],
  "settings": {
    "executionOrder": "v1"
  },
  "staticData": null,
  "connections": {
    "Analyze call with OpenAI": {
      "main": [
        [
          {
            "node": "Extract and structure AI analysis",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set user config variables": {
      "main": [
        [
          {
            "node": "Parse call transcript and metadata",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check if lead is qualified": {
      "main": [
        [
          {
            "node": "Alert sales team about hot lead",
            "type": "main",
            "index": 0
          },
          {
            "node": "Create follow-up task in HubSpot",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    },
    "Receive Retell call webhook": {
      "main": [
        [
          {
            "node": "Set user config variables",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract and structure AI analysis": {
      "main": [
        [
          {
            "node": "Update HubSpot contact with insights",
            "type": "main",
            "index": 0
          },
          {
            "node": "Check if lead is qualified",
            "type": "main",
            "index": 0
          },
          {
            "node": "Log call analysis to tracking sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse call transcript and metadata": {
      "main": [
        [
          {
            "node": "Analyze call with OpenAI",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update HubSpot contact with insights": {
      "main": [
        [
          {
            "node": "Respond to Retell webhook",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}