{
  "name": "Call recordings, transcripts & summaries to Slack + HubSpot",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "call-intelligence",
        "options": {}
      },
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        40,
        400
      ],
      "id": "11111111-1111-4111-8111-000000000001",
      "name": "Call notification"
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 3
                },
                "conditions": [
                  {
                    "id": "c0000001-0000-4000-8000-000000000001",
                    "leftValue": "={{ $json.body.status }}",
                    "rightValue": "recording_created",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "recording_created"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 3
                },
                "conditions": [
                  {
                    "id": "c0000001-0000-4000-8000-000000000002",
                    "leftValue": "={{ $json.body.status }}",
                    "rightValue": "transcript_created",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "transcript_created"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 3
                },
                "conditions": [
                  {
                    "id": "c0000001-0000-4000-8000-000000000003",
                    "leftValue": "={{ $json.body.status }}",
                    "rightValue": "summary_created",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "summary_created"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 3
                },
                "conditions": [
                  {
                    "id": "c0000001-0000-4000-8000-000000000004",
                    "leftValue": "={{ $json.body.status }}",
                    "rightValue": "ended",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "call ended"
            }
          ]
        },
        "looseTypeValidation": "",
        "options": {}
      },
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.4,
      "position": [
        280,
        400
      ],
      "id": "11111111-1111-4111-8111-000000000002",
      "name": "Switch"
    },
    {
      "parameters": {
        "url": "={{ $json.body.recording_url }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {
          "response": {
            "response": {
              "responseFormat": "file",
              "outputPropertyName": "data"
            }
          },
          "timeout": 180000
        }
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        600,
        0
      ],
      "id": "11111111-1111-4111-8111-000000000003",
      "name": "Download recording",
      "retryOnFail": true,
      "maxTries": 5,
      "waitBetweenTries": 5000
    },
    {
      "parameters": {
        "resource": "file",
        "operation": "upload",
        "binaryPropertyName": "data",
        "options": {
          "channelId": "",
          "fileName": "={{ $('Call notification').first().json.body.call_id }}.wav"
        }
      },
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.3,
      "position": [
        820,
        0
      ],
      "id": "11111111-1111-4111-8111-000000000004",
      "name": "Upload recording to Slack"
    },
    {
      "parameters": {
        "modelId": {
          "__rl": true,
          "value": "claude-sonnet-5",
          "mode": "list",
          "cachedResultName": "claude-sonnet-5"
        },
        "messages": {
          "values": [
            {
              "content": "=Summarize this phone call for a CRM note.\n\nRules:\n- Reply in the language the transcript is in.\n- Pull out every date, time, number, amount, agreement and name that is mentioned.\n- Finish with an \"Action items\" list, or \"Action items: none\".\n- Never invent anything that is not in the transcript.\n\nCall ID: {{ $json.body.call_id }}\n\nTranscript:\n{{ $json.body.call_transcription }}"
            }
          ]
        },
        "simplify": false,
        "options": {
          "system": "You are a CRM assistant that summarizes customer phone calls. Be factual and concise. Never invent details that are not in the transcript.",
          "maxTokens": 2000
        }
      },
      "type": "@n8n/n8n-nodes-langchain.anthropic",
      "typeVersion": 1,
      "position": [
        600,
        260
      ],
      "id": "11111111-1111-4111-8111-000000000005",
      "name": "Analyse transcript"
    },
    {
      "parameters": {
        "jsCode": "// Single convergence point for all three events.\n// Each branch reaches back to the webhook by name, so the shape of whatever\n// node ran just before this one does not matter.\nconst body = $('Call notification').first().json.body;\nconst callId = body.call_id;\nconst br = (s) => String(s).replace(/\\n/g, '<br/>');\n\nlet slack_text;\nlet note_body;\n\nif (body.status === 'recording_created') {\n  slack_text = ':microphone: *Call recording* \\u2014 `' + callId + '`';\n  note_body =\n    '<p><strong>Call recording</strong></p>' +\n    '<p><a href=\"' + body.recording_url + '\">' + body.recording_url + '</a></p>';\n} else if (body.status === 'transcript_created') {\n  const analysis = $('Analyse transcript').first().json.content.map((c) => c.text).join('\\n');\n  slack_text = ':memo: *Call analysis* \\u2014 `' + callId + '`\\n' + analysis;\n  note_body =\n    '<p><strong>Call analysis</strong></p><p>' + br(analysis) + '</p>' +\n    '<p><strong>Transcript</strong></p><p>' + br(body.call_transcription) + '</p>';\n} else {\n  slack_text = ':speech_balloon: *Call summary* \\u2014 `' + callId + '`\\n' + body.summary;\n  note_body = '<p><strong>Call summary</strong></p><p>' + br(body.summary) + '</p>';\n}\n\nreturn [{ json: { call_id: callId, slack_text, note_body } }];\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1120,
        400
      ],
      "id": "11111111-1111-4111-8111-000000000006",
      "name": "Build event"
    },
    {
      "parameters": {
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "",
          "cachedResultName": ""
        },
        "text": "={{ $json.slack_text }}",
        "otherOptions": {}
      },
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.3,
      "position": [
        1400,
        260
      ],
      "id": "11111111-1111-4111-8111-000000000007",
      "name": "Post to Slack"
    },
    {
      "parameters": {
        "jsCode": "// Only the hang-up event carries phone numbers, and each webhook POST is its\n// own execution -- so park the number here, keyed by call_id, for the later\n// events to read back.\nconst b = $input.first().json.body;\n\n// Outbound: the customer is who we dialled. Inbound: the customer is calling us.\nconst customer = b.direction === 'outbound' ? b.destination.number : b.caller.number;\n\nreturn [{ json: { call_id: b.call_id, customer_number: customer } }];\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        600,
        640
      ],
      "id": "11111111-1111-4111-8111-000000000008",
      "name": "Extract customer number"
    },
    {
      "parameters": {
        "operation": "upsert",
        "dataTableId": {
          "__rl": true,
          "mode": "name",
          "value": "call_numbers"
        },
        "matchType": "allConditions",
        "filters": {
          "conditions": [
            {
              "keyName": "call_id",
              "condition": "eq",
              "keyValue": "={{ $json.call_id }}"
            }
          ]
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "call_id": "={{ $json.call_id }}",
            "customer_number": "={{ $json.customer_number }}"
          },
          "matchingColumns": [
            "call_id"
          ],
          "schema": [
            {
              "id": "call_id",
              "displayName": "call_id",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "customer_number",
              "displayName": "customer_number",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.dataTable",
      "typeVersion": 1.1,
      "position": [
        820,
        640
      ],
      "id": "11111111-1111-4111-8111-000000000009",
      "name": "Store call number"
    },
    {
      "parameters": {
        "operation": "get",
        "dataTableId": {
          "__rl": true,
          "mode": "name",
          "value": "call_numbers"
        },
        "matchType": "allConditions",
        "filters": {
          "conditions": [
            {
              "keyName": "call_id",
              "condition": "eq",
              "keyValue": "={{ $json.call_id }}"
            }
          ]
        },
        "returnAll": false,
        "limit": 1
      },
      "type": "n8n-nodes-base.dataTable",
      "typeVersion": 1.1,
      "position": [
        1400,
        520
      ],
      "id": "11111111-1111-4111-8111-000000000010",
      "name": "Look up call number"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.hubapi.com/crm/v3/objects/contacts/search",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "hubspotAppToken",
        "options": {},
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ filterGroups: [ { filters: [ { propertyName: 'phone', operator: 'EQ', value: $json.customer_number } ] }, { filters: [ { propertyName: 'mobilephone', operator: 'EQ', value: $json.customer_number } ] } ], properties: ['phone', 'mobilephone'], limit: 1 }) }}"
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1620,
        520
      ],
      "id": "11111111-1111-4111-8111-000000000011",
      "name": "Find HubSpot contact"
    },
    {
      "parameters": {
        "method": "GET",
        "url": "=https://api.hubapi.com/crm/v4/objects/contacts/{{ $json.results[0].id }}/associations/companies",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "hubspotAppToken",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1840,
        520
      ],
      "id": "11111111-1111-4111-8111-000000000012",
      "name": "Get contact's company"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.hubapi.com/crm/v3/objects/notes",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "hubspotAppToken",
        "options": {},
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ properties: { hs_timestamp: $now.toMillis(), hs_note_body: $('Build event').first().json.note_body }, associations: [ { to: { id: $json.results[0].toObjectId }, types: [ { associationCategory: 'HUBSPOT_DEFINED', associationTypeId: 190 } ] } ] }) }}"
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2060,
        520
      ],
      "id": "11111111-1111-4111-8111-000000000013",
      "name": "Create HubSpot note"
    },
    {
      "parameters": {
        "content": "## 📞 Call recordings, transcripts & summaries → Slack + HubSpot\n\nYour phone system posts a webhook every time a call ends, and again when the\nrecording, transcript and summary are ready. This workflow turns those into a\nSlack message and a note on the caller's company in HubSpot.\n\n### Set this up first\n1. **Credentials** — pick your own on every coloured node: Anthropic, Slack (x2), HubSpot (x3).\n2. **Slack channel** — set it on *Post to Slack* and on *Upload recording to Slack*.\n3. **Data table** — create one named `call_numbers` with two text columns: `call_id`, `customer_number`.\n4. **Recording download** — add a Header Auth credential (`Authorization` / `Bearer <token>`) to *Download recording*.\n5. **Webhook URL** — copy it off the trigger and paste it into your call-notification settings.\n\n### Expected payload\nEvery event arrives as `body.status` plus `body.call_id`:\n`ended` (with `direction`, `caller.number`, `destination.number`) ·\n`recording_created` (`recording_url`) ·\n`transcript_created` (`call_transcription`) ·\n`summary_created` (`summary`)\n\nAdjust the Switch and the Code nodes if your provider names things differently.",
        "height": 560,
        "width": 480,
        "color": 4
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -560,
        120
      ],
      "id": "11111111-1111-4111-8111-000000000020",
      "name": "Note: setup"
    },
    {
      "parameters": {
        "content": "### 🎙️ The recording\nDownload the audio and drop it in Slack, so you can just press play.\n\n⚠️ *Download recording* needs its own Header Auth credential.",
        "height": 170,
        "width": 460,
        "color": 3
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        560,
        -190
      ],
      "id": "11111111-1111-4111-8111-000000000021",
      "name": "Note: recording"
    },
    {
      "parameters": {
        "content": "### 📝 The transcript\nClaude reads it and pulls out dates, names, numbers and action items.\n\nThe audio is never sent to Claude — only this text.",
        "height": 120,
        "width": 340,
        "color": 5
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        560,
        120
      ],
      "id": "11111111-1111-4111-8111-000000000022",
      "name": "Note: transcript"
    },
    {
      "parameters": {
        "content": "### 💬 The summary\nIf your provider writes its own summary, it passes straight through.",
        "height": 100,
        "width": 380,
        "color": 5
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        600,
        400
      ],
      "id": "11111111-1111-4111-8111-000000000023",
      "name": "Note: summary"
    },
    {
      "parameters": {
        "content": "### 👤 Who was on the phone?\nOnly the hang-up event carries the number. Park it under the `call_id`\nso the lanes above can find it again later.",
        "height": 100,
        "width": 460,
        "color": 5
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        560,
        520
      ],
      "id": "11111111-1111-4111-8111-000000000024",
      "name": "Note: caller"
    },
    {
      "parameters": {
        "content": "### 📣 Tell the team, then file it\nAll three lanes meet at **Build event**: one Slack message, plus one\nnote on the caller's HubSpot company.\n\nNo matching contact → Slack still fires, the HubSpot half stops.",
        "height": 200,
        "width": 500,
        "color": 5
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1340,
        20
      ],
      "id": "11111111-1111-4111-8111-000000000025",
      "name": "Note: file it"
    }
  ],
  "connections": {
    "Call notification": {
      "main": [
        [
          {
            "node": "Switch",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Switch": {
      "main": [
        [
          {
            "node": "Download recording",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Analyse transcript",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Build event",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Extract customer number",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Download recording": {
      "main": [
        [
          {
            "node": "Upload recording to Slack",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Upload recording to Slack": {
      "main": [
        [
          {
            "node": "Build event",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Analyse transcript": {
      "main": [
        [
          {
            "node": "Build event",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build event": {
      "main": [
        [
          {
            "node": "Post to Slack",
            "type": "main",
            "index": 0
          },
          {
            "node": "Look up call number",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract customer number": {
      "main": [
        [
          {
            "node": "Store call number",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Look up call number": {
      "main": [
        [
          {
            "node": "Find HubSpot contact",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Find HubSpot contact": {
      "main": [
        [
          {
            "node": "Get contact's company",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get contact's company": {
      "main": [
        [
          {
            "node": "Create HubSpot note",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "nodeGroups": [
    {
      "name": "1 · Route by event",
      "nodeIds": [
        "11111111-1111-4111-8111-000000000002"
      ]
    },
    {
      "name": "2 · Recording → Slack",
      "nodeIds": [
        "11111111-1111-4111-8111-000000000003",
        "11111111-1111-4111-8111-000000000004"
      ]
    },
    {
      "name": "3 · Transcript → Claude",
      "nodeIds": [
        "11111111-1111-4111-8111-000000000005"
      ]
    },
    {
      "name": "4 · Remember who called",
      "nodeIds": [
        "11111111-1111-4111-8111-000000000008",
        "11111111-1111-4111-8111-000000000009"
      ]
    },
    {
      "name": "5 · Post & log to HubSpot",
      "nodeIds": [
        "11111111-1111-4111-8111-000000000006",
        "11111111-1111-4111-8111-000000000007",
        "11111111-1111-4111-8111-000000000010",
        "11111111-1111-4111-8111-000000000011",
        "11111111-1111-4111-8111-000000000012",
        "11111111-1111-4111-8111-000000000013"
      ]
    }
  ]
}