{
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "nodes": [
    {
      "id": "fd07007c-3fe1-424a-83d1-79831351d3ce",
      "name": "Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -544,
        -528
      ],
      "parameters": {
        "color": 4,
        "width": 652,
        "height": 1380,
        "content": "## Zoom Sales Call to HubSpot CRM Notes Updater \u2014 WayinVideo + GPT-4o-mini + HubSpot API + Sheets\n\nFor sales teams, account managers, and agencies who run client calls and want to automatically log structured notes in HubSpot CRM without manually writing notes after every call. Paste any Zoom, Google Meet, or Teams recording URL via the form. WayinVideo transcribes the full call with speaker labels and timestamps. GPT-4o-mini extracts nine structured CRM fields: call summary, pain points identified, objections raised, key quotes, next steps, follow-up date, deal stage recommendation, sentiment, and confidence score. A formatted CRM note is created on the matching HubSpot contact (looked up by email). A follow-up task is also created in HubSpot with the agreed next steps. Everything is logged to Google Sheets with the HubSpot Note ID, Task ID, and Contact ID.\n\n## How it works\n- **1. Form \u2014 Call Recording + CRM Details** collects the recording URL, contact email, contact name, company name, call purpose, call date, and sales rep name\n- **2. WayinVideo \u2014 Submit Transcription** submits the URL for speaker-labeled transcription\n- **3. Wait \u2014 90 Seconds** gives the API initial processing time\n- **4. WayinVideo \u2014 Get Transcript Results** polls for the task result\n- **5. IF \u2014 Transcription Complete?** checks for SUCCEEDED \u2014 if not, retries via 30-second wait\n- **7. Code \u2014 Format Transcript** formats each segment with speaker label and timestamp\n- **8. AI Agent \u2014 Extract CRM Note** uses GPT-4o-mini to extract 9 structured fields including deal stage, sentiment, and confidence score\n- **10. Code \u2014 Parse CRM Output** extracts all 9 sections via regex, builds the full HubSpot note body, and calculates the follow-up timestamp\n- **11. HTTP \u2014 Search HubSpot Contact** searches HubSpot for the contact by email address\n- **12. Code \u2014 Extract HubSpot Contact ID** extracts the contact ID and throws an error if the contact is not found\n- **13. HTTP \u2014 Create HubSpot Note** creates an engagement note associated with the HubSpot contact\n- **14. HTTP \u2014 Create HubSpot Task** creates a follow-up task associated with the same contact\n- **15. Google Sheets \u2014 Log CRM Entry** logs all 16 fields including HubSpot Note ID, Task ID, deal stage, sentiment, and confidence score\n\n## Set up steps\n1. In **2. WayinVideo \u2014 Submit Transcription** and **4. WayinVideo \u2014 Get Transcript Results** \u2014 replace YOUR_WAYINVIDEO_API_KEY\n2. In **9. OpenAI \u2014 GPT-4o-mini Model** \u2014 connect your OpenAI credential\n3. In **11. HTTP \u2014 Search HubSpot Contact**, **13. HTTP \u2014 Create HubSpot Note**, and **14. HTTP \u2014 Create HubSpot Task** \u2014 replace YOUR_HUBSPOT_PRIVATE_APP_TOKEN (create in HubSpot Settings \u2192 Integrations \u2192 Private Apps, required scopes: crm.objects.contacts.write, crm.objects.notes.write, crm.objects.tasks.write)\n4. In **15. Google Sheets \u2014 Log CRM Entry** \u2014 connect your Google Sheets OAuth2 credential and replace YOUR_GOOGLE_SHEET_ID\n5. Create a Google Sheet tab named CRM Call Log with columns: Contact Email, Contact Name, Company, Call Purpose, Call Date, Sales Rep, Call Duration (min), HubSpot Contact ID, HubSpot Note ID, HubSpot Task ID, Deal Stage, Sentiment, Confidence Score, Follow-up Date, Recording URL, Created On"
      },
      "typeVersion": 1
    },
    {
      "id": "e8fe9917-61e5-4c68-8122-d1b88cb6ee5f",
      "name": "Section \u2014 Form Input",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        208,
        -192
      ],
      "parameters": {
        "color": 5,
        "width": 260,
        "height": 548,
        "content": "## Form Input\nSales rep submits recording URL, contact email, contact name, company name, call purpose, call date, and sales rep name. All seven fields feed into the transcript formatter and AI CRM extraction prompt."
      },
      "typeVersion": 1
    },
    {
      "id": "1f303525-92e4-4409-8e64-7fc25ad249dc",
      "name": "Section \u2014 Transcription Submit and Poll",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        544,
        -80
      ],
      "parameters": {
        "color": 6,
        "width": 660,
        "height": 628,
        "content": "## WayinVideo Transcription Submit and Poll\nSubmits the call recording URL for speaker-labeled transcription. Waits 90 seconds for initial processing. Polls the results endpoint until status equals SUCCEEDED."
      },
      "typeVersion": 1
    },
    {
      "id": "a3d0c060-72c1-482a-b335-28bac736cf21",
      "name": "Section \u2014 Status Check and Retry Loop",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1248,
        -176
      ],
      "parameters": {
        "color": 6,
        "width": 276,
        "height": 628,
        "content": "## Transcription Status Check and Retry Loop\nIF checks for SUCCEEDED status. TRUE proceeds to transcript formatting. FALSE waits 30 seconds and polls again. Loop continues until the transcript is ready."
      },
      "typeVersion": 1
    },
    {
      "id": "7b023ba5-0444-42fd-9eaa-0154a440d201",
      "name": "Section \u2014 Transcript Formatting and AI CRM Extraction",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1600,
        -224
      ],
      "parameters": {
        "color": 6,
        "width": 564,
        "height": 756,
        "content": "## Transcript Formatting and AI CRM Extraction\nFormats transcript with speaker labels and timestamps. GPT-4o-mini extracts 9 CRM fields: call summary, pain points, objections, key quotes, next steps, follow-up date, deal stage recommendation, sentiment, and confidence score."
      },
      "typeVersion": 1
    },
    {
      "id": "6f068217-00eb-4c09-9eeb-b5ad3dbedf86",
      "name": "Section \u2014 HubSpot Contact Lookup, Note and Task Creation, and Sheet Log",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2208,
        -160
      ],
      "parameters": {
        "color": 4,
        "width": 1460,
        "height": 452,
        "content": "## HubSpot Contact Lookup, Note and Task Creation, and Sheet Log\nSearches HubSpot for the contact by email. Extracts the contact ID \u2014 throws if not found. Creates an engagement note on the contact. Creates a follow-up task. Logs all 16 fields to Google Sheets including HubSpot Note ID and Task ID."
      },
      "typeVersion": 1
    },
    {
      "id": "1c552b42-c101-47b7-9a40-83afe23baf9f",
      "name": "1. Form \u2014 Call Recording + CRM Details",
      "type": "n8n-nodes-base.formTrigger",
      "position": [
        304,
        48
      ],
      "parameters": {
        "options": {},
        "formTitle": "Sales Call to HubSpot CRM Note Logger",
        "formFields": {
          "values": [
            {
              "fieldLabel": "Call Recording URL",
              "placeholder": "https://zoom.us/rec/xxxxxxx or Google Meet / Loom link",
              "requiredField": true
            },
            {
              "fieldLabel": "Contact Email",
              "placeholder": "e.g. user@example.com",
              "requiredField": true
            },
            {
              "fieldLabel": "Contact Name",
              "placeholder": "e.g. John Smith",
              "requiredField": true
            },
            {
              "fieldLabel": "Company Name",
              "placeholder": "e.g. Acme Corp",
              "requiredField": true
            },
            {
              "fieldLabel": "Call Purpose",
              "placeholder": "e.g. Discovery Call, Demo, Follow-up, Negotiation, Closing",
              "requiredField": true
            },
            {
              "fieldLabel": "Call Date",
              "placeholder": "e.g. 2025-04-29",
              "requiredField": true
            },
            {
              "fieldLabel": "Sales Rep Name",
              "placeholder": "e.g. Sarah Johnson",
              "requiredField": true
            }
          ]
        },
        "formDescription": "Paste your sales call recording URL. AI will transcribe the full call, extract key CRM insights, and automatically log a structured note to HubSpot."
      },
      "typeVersion": 2.2
    },
    {
      "id": "eec75a70-2185-4af6-ae24-60b552e08b74",
      "name": "2. WayinVideo \u2014 Submit Transcription",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        592,
        48
      ],
      "parameters": {
        "url": "https://wayinvideo-api.wayin.ai/api/v2/transcripts",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"video_url\": \"{{ $json['Call Recording URL'] }}\",\n  \"target_lang\": \"en\"\n}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "Bearer YOUR_TOKEN_HERE"
            },
            {
              "name": "x-wayinvideo-api-version",
              "value": "v2"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "f90ab924-8068-4909-a158-d3d800766ddc",
      "name": "3. Wait \u2014 90 Seconds",
      "type": "n8n-nodes-base.wait",
      "position": [
        832,
        48
      ],
      "parameters": {
        "amount": 90
      },
      "typeVersion": 1.1
    },
    {
      "id": "d6d81949-2ce6-4505-bf3c-bac563059164",
      "name": "4. WayinVideo \u2014 Get Transcript Results",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1072,
        48
      ],
      "parameters": {
        "url": "=https://wayinvideo-api.wayin.ai/api/v2/transcripts/results/{{ $('2. WayinVideo \u2014 Submit Transcription').item.json.data.id }}",
        "options": {},
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "Bearer YOUR_TOKEN_HERE"
            },
            {
              "name": "x-wayinvideo-api-version",
              "value": "v2"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "922c4c6d-a1c1-4c59-b341-6cf23a0fd3e0",
      "name": "5. IF \u2014 Transcription Complete?",
      "type": "n8n-nodes-base.if",
      "position": [
        1312,
        48
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "status-check",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.data.status }}",
              "rightValue": "SUCCEEDED"
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "4d8024ae-cb62-4240-ac88-7a9a7636964c",
      "name": "6. Wait \u2014 30 Seconds Retry",
      "type": "n8n-nodes-base.wait",
      "position": [
        1312,
        240
      ],
      "parameters": {
        "amount": 30
      },
      "typeVersion": 1.1
    },
    {
      "id": "451b270b-5b70-4cb8-9ca7-b5e1010b80d2",
      "name": "7. Code \u2014 Format Transcript",
      "type": "n8n-nodes-base.code",
      "position": [
        1648,
        32
      ],
      "parameters": {
        "jsCode": "// Get transcript from WayinVideo\nconst segments = $('4. WayinVideo \u2014 Get Transcript Results').item.json.data.transcript || [];\n\nif (segments.length === 0) {\n  throw new Error('Transcript is empty \u2014 check if the recording URL is valid and publicly accessible.');\n}\n\n// Format transcript with speaker labels and timestamps\nconst formattedTranscript = segments\n  .map(s => {\n    const timeInSeconds = Math.floor(s.start / 1000);\n    const minutes = Math.floor(timeInSeconds / 60);\n    const seconds = timeInSeconds % 60;\n    const timestamp = `${minutes}:${seconds.toString().padStart(2, '0')}`;\n    return `[${s.speaker} | ${timestamp}] ${s.text.trim()}`;\n  })\n  .join('\\n');\n\n// Duration\nconst lastSegment = segments[segments.length - 1];\nconst totalDurationMs = lastSegment?.end || 0;\nconst totalMinutes = Math.round(totalDurationMs / 60000);\n\n// Speakers\nconst speakers = [...new Set(segments.map(s => s.speaker))];\n\nreturn [{\n  json: {\n    formattedTranscript,\n    totalMinutes,\n    speakerCount: speakers.length,\n    speakers: speakers.join(', '),\n    contactEmail: $('1. Form \u2014 Call Recording + CRM Details').item.json['Contact Email'],\n    contactName: $('1. Form \u2014 Call Recording + CRM Details').item.json['Contact Name'],\n    companyName: $('1. Form \u2014 Call Recording + CRM Details').item.json['Company Name'],\n    callPurpose: $('1. Form \u2014 Call Recording + CRM Details').item.json['Call Purpose'],\n    callDate: $('1. Form \u2014 Call Recording + CRM Details').item.json['Call Date'],\n    salesRep: $('1. Form \u2014 Call Recording + CRM Details').item.json['Sales Rep Name'],\n    recordingUrl: $('1. Form \u2014 Call Recording + CRM Details').item.json['Call Recording URL']\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "db1eb973-8b57-417b-8f59-cd90ae56b58a",
      "name": "8. AI Agent \u2014 Extract CRM Note",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        1888,
        32
      ],
      "parameters": {
        "text": "={{ $json.formattedTranscript }}",
        "options": {
          "systemMessage": "=You are an expert sales analyst who extracts structured CRM data from sales call transcripts.\n\nYour job is to read this sales call transcript and extract everything a sales rep needs to log in their CRM \u2014 in a clean, structured format ready to paste into HubSpot.\n\n---\n\n## CALL DETAILS\n- Contact Name: {{ $json.contactName }}\n- Company: {{ $json.companyName }}\n- Call Purpose: {{ $json.callPurpose }}\n- Call Date: {{ $json.callDate }}\n- Sales Rep: {{ $json.salesRep }}\n- Call Duration: {{ $json.totalMinutes }} minutes\n- Speakers Detected: {{ $json.speakers }}\n\n---\n\n## EXTRACTION RULES\n1. Extract ONLY from what was actually discussed in the call \u2014 no invented content\n2. Be specific and factual \u2014 avoid vague statements\n3. Pain points must be actual problems the prospect mentioned, not assumptions\n4. Objections must be actual objections raised \u2014 not hypothetical ones\n5. Next steps must be specific actions with owners if mentioned\n6. Follow-up date should be taken from what was agreed in the call \u2014 use TBD if not mentioned\n7. Deal stage must be one of: Prospecting / Qualification / Demo Scheduled / Proposal Sent / Negotiation / Closed Won / Closed Lost\n8. Sentiment must be one of: Very Positive / Positive / Neutral / Negative / Very Negative\n\n---\n\n## OUTPUT FORMAT\nReturn your response in this exact structure \u2014 use these exact labels:\n\nCALL_SUMMARY:\n[Write 3-4 sentences summarizing what was discussed, what the prospect is looking for, and where the conversation ended]\n\nPAIN_POINTS:\n- [Pain point 1 \u2014 specific problem the prospect mentioned]\n- [Pain point 2]\n- [Pain point 3 if applicable]\n[Write 'None identified' if no pain points were mentioned]\n\nOBJECTIONS_RAISED:\n- [Objection 1 \u2014 exact concern raised]\n- [Objection 2 if applicable]\n[Write 'None raised' if no objections]\n\nKEY_QUOTES:\n- [Most important thing the prospect said \u2014 direct or near-direct quote]\n- [Second key quote if available]\n[Write 'None captured' if no notable quotes]\n\nNEXT_STEPS:\n- [Next step 1 with owner \u2014 e.g. 'Sales Rep to send proposal by Friday']\n- [Next step 2 with owner]\n[Write 'Not discussed' if no next steps agreed]\n\nFOLLOW_UP_DATE:\n[Date agreed for next touchpoint \u2014 or 'TBD' if not mentioned]\n\nDEAL_STAGE:\n[One of: Prospecting / Qualification / Demo Scheduled / Proposal Sent / Negotiation / Closed Won / Closed Lost]\n\nSENTIMENT:\n[One of: Very Positive / Positive / Neutral / Negative / Very Negative]\n\nCONFIDENCE_SCORE:\n[Rate how likely this deal is to progress \u2014 1 to 10 \u2014 based on the conversation tone and signals]\n\n---\n\nNow read the call transcript and extract the CRM note:"
        },
        "promptType": "define"
      },
      "typeVersion": 3.1
    },
    {
      "id": "d23ed4e9-0394-4f95-82dd-ee9d89253645",
      "name": "9. OpenAI \u2014 GPT-4o-mini Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        1888,
        224
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4o-mini"
        },
        "options": {},
        "builtInTools": {}
      },
      "typeVersion": 1.3
    },
    {
      "id": "32a72f62-5a9d-45ef-a1ed-36737b352b6f",
      "name": "10. Code \u2014 Parse CRM Output",
      "type": "n8n-nodes-base.code",
      "position": [
        2288,
        32
      ],
      "parameters": {
        "jsCode": "// Parse AI output into structured CRM fields\nconst output = $input.first().json.output || '';\n\n// Extract each section\nconst callSummaryMatch = output.match(/CALL_SUMMARY:\\s*([\\s\\S]*?)(?=\\nPAIN_POINTS:|$)/);\nconst painPointsMatch = output.match(/PAIN_POINTS:\\s*([\\s\\S]*?)(?=\\nOBJECTIONS_RAISED:|$)/);\nconst objectionsMatch = output.match(/OBJECTIONS_RAISED:\\s*([\\s\\S]*?)(?=\\nKEY_QUOTES:|$)/);\nconst keyQuotesMatch = output.match(/KEY_QUOTES:\\s*([\\s\\S]*?)(?=\\nNEXT_STEPS:|$)/);\nconst nextStepsMatch = output.match(/NEXT_STEPS:\\s*([\\s\\S]*?)(?=\\nFOLLOW_UP_DATE:|$)/);\nconst followUpMatch = output.match(/FOLLOW_UP_DATE:\\s*([\\s\\S]*?)(?=\\nDEAL_STAGE:|$)/);\nconst dealStageMatch = output.match(/DEAL_STAGE:\\s*([\\s\\S]*?)(?=\\nSENTIMENT:|$)/);\nconst sentimentMatch = output.match(/SENTIMENT:\\s*([\\s\\S]*?)(?=\\nCONFIDENCE_SCORE:|$)/);\nconst confidenceMatch = output.match(/CONFIDENCE_SCORE:\\s*([\\s\\S]*)$/);\n\nconst callSummary = callSummaryMatch ? callSummaryMatch[1].trim() : '';\nconst painPoints = painPointsMatch ? painPointsMatch[1].trim() : '';\nconst objections = objectionsMatch ? objectionsMatch[1].trim() : '';\nconst keyQuotes = keyQuotesMatch ? keyQuotesMatch[1].trim() : '';\nconst nextSteps = nextStepsMatch ? nextStepsMatch[1].trim() : '';\nconst followUpDate = followUpMatch ? followUpMatch[1].trim() : 'TBD';\nconst dealStage = dealStageMatch ? dealStageMatch[1].trim() : 'Qualification';\nconst sentiment = sentimentMatch ? sentimentMatch[1].trim() : 'Neutral';\nconst confidenceScore = confidenceMatch ? confidenceMatch[1].trim() : '5';\n\n// Build full HubSpot note body\nconst noteBody = [\n  `CALL NOTE \u2014 ${$('7. Code \u2014 Format Transcript').item.json.callPurpose}`,\n  `Date: ${$('7. Code \u2014 Format Transcript').item.json.callDate} | Duration: ${$('7. Code \u2014 Format Transcript').item.json.totalMinutes} min | Rep: ${$('7. Code \u2014 Format Transcript').item.json.salesRep}`,\n  `Recording: ${$('7. Code \u2014 Format Transcript').item.json.recordingUrl}`,\n  ``,\n  `SUMMARY`,\n  callSummary,\n  ``,\n  `PAIN POINTS`,\n  painPoints,\n  ``,\n  `OBJECTIONS`,\n  objections,\n  ``,\n  `KEY QUOTES`,\n  keyQuotes,\n  ``,\n  `NEXT STEPS`,\n  nextSteps,\n  ``,\n  `DEAL INTELLIGENCE`,\n  `Stage: ${dealStage} | Sentiment: ${sentiment} | Confidence: ${confidenceScore}/10`,\n  `Follow-up: ${followUpDate}`\n].join('\\n');\n\n// Calculate follow-up timestamp for HubSpot task (7 days from now if TBD)\nconst followUpTimestamp = followUpDate !== 'TBD'\n  ? new Date(followUpDate).getTime()\n  : Date.now() + (7 * 24 * 60 * 60 * 1000);\n\nreturn [{\n  json: {\n    callSummary,\n    painPoints,\n    objections,\n    keyQuotes,\n    nextSteps,\n    followUpDate,\n    dealStage,\n    sentiment,\n    confidenceScore,\n    noteBody,\n    followUpTimestamp,\n    contactEmail: $('7. Code \u2014 Format Transcript').item.json.contactEmail,\n    contactName: $('7. Code \u2014 Format Transcript').item.json.contactName,\n    companyName: $('7. Code \u2014 Format Transcript').item.json.companyName,\n    callPurpose: $('7. Code \u2014 Format Transcript').item.json.callPurpose,\n    callDate: $('7. Code \u2014 Format Transcript').item.json.callDate,\n    salesRep: $('7. Code \u2014 Format Transcript').item.json.salesRep,\n    recordingUrl: $('7. Code \u2014 Format Transcript').item.json.recordingUrl,\n    totalMinutes: $('7. Code \u2014 Format Transcript').item.json.totalMinutes\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "7515c1b7-374c-4719-896f-c13c5069607b",
      "name": "11. HTTP \u2014 Search HubSpot Contact",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        2528,
        32
      ],
      "parameters": {
        "url": "https://api.hubapi.com/crm/v3/objects/contacts/search",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"filterGroups\": [\n    {\n      \"filters\": [\n        {\n          \"propertyName\": \"email\",\n          \"operator\": \"EQ\",\n          \"value\": \"{{ $json.contactEmail }}\"\n        }\n      ]\n    }\n  ],\n  \"properties\": [\"email\", \"firstname\", \"lastname\", \"company\", \"hs_object_id\"]\n}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "Bearer YOUR_TOKEN_HERE"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "79cc5c41-a1b8-4cee-ba1c-031440413df3",
      "name": "12. Code \u2014 Extract HubSpot Contact ID",
      "type": "n8n-nodes-base.code",
      "position": [
        2768,
        32
      ],
      "parameters": {
        "jsCode": "// Extract HubSpot contact ID from search results\nconst results = $json.results || [];\n\nif (results.length === 0) {\n  throw new Error(`Contact not found in HubSpot for email: ${$('10. Code \u2014 Parse CRM Output').item.json.contactEmail}. Please add the contact to HubSpot first.`);\n}\n\nconst contact = results[0];\nconst contactId = contact.id;\n\n// Pass through all CRM data with contact ID added\nconst crmData = $('10. Code \u2014 Parse CRM Output').item.json;\n\nreturn [{\n  json: {\n    ...crmData,\n    hubspotContactId: contactId\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "c75f9f3d-7a8a-44b1-a01e-959373dd7aa6",
      "name": "13. HTTP \u2014 Create HubSpot Note",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        3008,
        32
      ],
      "parameters": {
        "url": "https://api.hubapi.com/crm/v3/objects/notes",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"properties\": {\n    \"hs_note_body\": \"{{ $json.noteBody }}\",\n    \"hs_timestamp\": \"{{ new Date($json.callDate).getTime() }}\"\n  },\n  \"associations\": [\n    {\n      \"to\": {\n        \"id\": \"{{ $json.hubspotContactId }}\"\n      },\n      \"types\": [\n        {\n          \"associationCategory\": \"HUBSPOT_DEFINED\",\n          \"associationTypeId\": 202\n        }\n      ]\n    }\n  ]\n}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "Bearer YOUR_TOKEN_HERE"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "e64ff10f-a056-4a0a-8c99-493524fc5a37",
      "name": "14. HTTP \u2014 Create HubSpot Task",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        3248,
        32
      ],
      "parameters": {
        "url": "https://api.hubapi.com/crm/v3/objects/tasks",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"properties\": {\n    \"hs_task_subject\": \"Follow-up: {{ $('10. Code \u2014 Parse CRM Output').item.json.contactName }} \u2014 {{ $('10. Code \u2014 Parse CRM Output').item.json.companyName }}\",\n    \"hs_task_body\": \"Follow-up actions from {{ $('10. Code \u2014 Parse CRM Output').item.json.callPurpose }} on {{ $('10. Code \u2014 Parse CRM Output').item.json.callDate }}:\\n\\n{{ $('10. Code \u2014 Parse CRM Output').item.json.nextSteps }}\",\n    \"hs_timestamp\": \"{{ $('10. Code \u2014 Parse CRM Output').item.json.followUpTimestamp }}\",\n    \"hs_task_status\": \"NOT_STARTED\",\n    \"hs_task_priority\": \"HIGH\",\n    \"hs_task_type\": \"CALL\"\n  },\n  \"associations\": [\n    {\n      \"to\": {\n        \"id\": \"{{ $('12. Code \u2014 Extract HubSpot Contact ID').item.json.hubspotContactId }}\"\n      },\n      \"types\": [\n        {\n          \"associationCategory\": \"HUBSPOT_DEFINED\",\n          \"associationTypeId\": 204\n        }\n      ]\n    }\n  ]\n}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "Bearer YOUR_TOKEN_HERE"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "660828f2-01e8-42a1-8565-62b1fd4cb87a",
      "name": "15. Google Sheets \u2014 Log CRM Entry",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        3488,
        32
      ],
      "parameters": {
        "columns": {
          "value": {
            "Company": "={{ $('10. Code \u2014 Parse CRM Output').item.json.companyName }}",
            "Call Date": "={{ $('10. Code \u2014 Parse CRM Output').item.json.callDate }}",
            "Sales Rep": "={{ $('10. Code \u2014 Parse CRM Output').item.json.salesRep }}",
            "Sentiment": "={{ $('10. Code \u2014 Parse CRM Output').item.json.sentiment }}",
            "Created On": "={{ $now.toFormat('dd MMMM yyyy HH:mm') }}",
            "Deal Stage": "={{ $('10. Code \u2014 Parse CRM Output').item.json.dealStage }}",
            "Call Purpose": "={{ $('10. Code \u2014 Parse CRM Output').item.json.callPurpose }}",
            "Contact Name": "={{ $('10. Code \u2014 Parse CRM Output').item.json.contactName }}",
            "Contact Email": "={{ $('10. Code \u2014 Parse CRM Output').item.json.contactEmail }}",
            "Recording URL": "={{ $('10. Code \u2014 Parse CRM Output').item.json.recordingUrl }}",
            "Follow-up Date": "={{ $('10. Code \u2014 Parse CRM Output').item.json.followUpDate }}",
            "HubSpot Note ID": "={{ $('13. HTTP \u2014 Create HubSpot Note').item.json.id }}",
            "HubSpot Task ID": "={{ $json.id }}",
            "Confidence Score": "={{ $('10. Code \u2014 Parse CRM Output').item.json.confidenceScore }}",
            "HubSpot Contact ID": "={{ $('12. Code \u2014 Extract HubSpot Contact ID').item.json.hubspotContactId }}",
            "Call Duration (min)": "={{ $('10. Code \u2014 Parse CRM Output').item.json.totalMinutes }}"
          },
          "schema": [],
          "mappingMode": "defineBelow",
          "matchingColumns": []
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "CRM Call Log"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_GOOGLE_SHEET_ID"
        }
      },
      "typeVersion": 4.5
    }
  ],
  "connections": {
    "3. Wait \u2014 90 Seconds": {
      "main": [
        [
          {
            "node": "4. WayinVideo \u2014 Get Transcript Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "6. Wait \u2014 30 Seconds Retry": {
      "main": [
        [
          {
            "node": "4. WayinVideo \u2014 Get Transcript Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "10. Code \u2014 Parse CRM Output": {
      "main": [
        [
          {
            "node": "11. HTTP \u2014 Search HubSpot Contact",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "7. Code \u2014 Format Transcript": {
      "main": [
        [
          {
            "node": "8. AI Agent \u2014 Extract CRM Note",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "9. OpenAI \u2014 GPT-4o-mini Model": {
      "ai_languageModel": [
        [
          {
            "node": "8. AI Agent \u2014 Extract CRM Note",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "13. HTTP \u2014 Create HubSpot Note": {
      "main": [
        [
          {
            "node": "14. HTTP \u2014 Create HubSpot Task",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "14. HTTP \u2014 Create HubSpot Task": {
      "main": [
        [
          {
            "node": "15. Google Sheets \u2014 Log CRM Entry",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "8. AI Agent \u2014 Extract CRM Note": {
      "main": [
        [
          {
            "node": "10. Code \u2014 Parse CRM Output",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "5. IF \u2014 Transcription Complete?": {
      "main": [
        [
          {
            "node": "7. Code \u2014 Format Transcript",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "6. Wait \u2014 30 Seconds Retry",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "11. HTTP \u2014 Search HubSpot Contact": {
      "main": [
        [
          {
            "node": "12. Code \u2014 Extract HubSpot Contact ID",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "2. WayinVideo \u2014 Submit Transcription": {
      "main": [
        [
          {
            "node": "3. Wait \u2014 90 Seconds",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "12. Code \u2014 Extract HubSpot Contact ID": {
      "main": [
        [
          {
            "node": "13. HTTP \u2014 Create HubSpot Note",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "1. Form \u2014 Call Recording + CRM Details": {
      "main": [
        [
          {
            "node": "2. WayinVideo \u2014 Submit Transcription",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "4. WayinVideo \u2014 Get Transcript Results": {
      "main": [
        [
          {
            "node": "5. IF \u2014 Transcription Complete?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}