{
  "id": "tBZGm9I9Tfv4Y2uF",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Analyze BeyondPresence Video Calls with GPT-4o-mini and Google Sheets",
  "tags": [
    {
      "id": "9IqkT00CERSl4sHE",
      "name": "Video Call Agents",
      "createdAt": "2025-09-12T10:26:08.271Z",
      "updatedAt": "2025-09-12T10:26:08.271Z"
    }
  ],
  "nodes": [
    {
      "id": "0fe49f1d-8883-4d61-88a5-d8b35068675e",
      "name": "Workflow Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        176,
        0
      ],
      "parameters": {
        "color": 6,
        "width": 496,
        "height": 580,
        "content": "## \ud83c\udfa5 BeyondPresence Webhook Trigger\n\nThis workflow is triggered when a BeyondPresence video agent call ends. The webhook receives comprehensive call data including:\n\n- Call metadata (participant, duration, timestamps)\n- Complete conversation transcript\n- Initial sentiment analysis\n- Call evaluation metrics\n\nMake sure to configure the webhook URL in your BeyondPresence dashboard under Settings \u2192 Webhooks"
      },
      "typeVersion": 1
    },
    {
      "id": "0fd41ccd-19c3-49d9-a3de-2d08b9ea2628",
      "name": "Processing Steps",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        688,
        0
      ],
      "parameters": {
        "color": 4,
        "width": 568,
        "height": 580,
        "content": "## \ud83d\udd0d Data Validation & AI Analysis\n\n1. **Validate & Enrich Data**: Ensures webhook data integrity and adds calculated fields\n2. **AI Call Analysis**: Uses OpenAI/OpenRouter to generate comprehensive call summary\n3. **Parse AI Response**: Extracts structured JSON from AI response with error handling\n\nThe AI analyzes:\n- Main discussion points\n- Action items & follow-ups\n- Sentiment analysis\n- Key decisions made\n- Questions raised"
      },
      "typeVersion": 1
    },
    {
      "id": "e4e8b581-aa56-4ed0-8c75-bb0a26eec3af",
      "name": "Data Storage",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1280,
        0
      ],
      "parameters": {
        "color": 3,
        "width": 392,
        "height": 580,
        "content": "## \ud83d\udcca Google Sheets Setup\n\n**Quick Start**:\n1. Copy our template sheet:  https://docs.google.com/spreadsheets/d/1TO6-jkCtoSFNLJObtN0UyklgdUd3ZxEnUaNvUaBjpvo/copy\n2. After copying, get your new sheet's ID from the URL\n3. Update the Google Sheets node with your ID\nThe template includes all required columns and sample data!"
      },
      "typeVersion": 1
    },
    {
      "id": "0df852bb-3c73-4a5c-acf6-d853b52f5c82",
      "name": "BeyondPresence Webhook",
      "type": "n8n-nodes-base.webhook",
      "position": [
        240,
        400
      ],
      "parameters": {
        "path": "beyondpresence-call-webhook",
        "options": {
          "rawBody": false
        },
        "httpMethod": "POST"
      },
      "typeVersion": 2
    },
    {
      "id": "7b3ebcb3-e292-432f-9db6-af2fba8f13ad",
      "name": "Validate & Enrich Data",
      "type": "n8n-nodes-base.code",
      "position": [
        752,
        400
      ],
      "parameters": {
        "jsCode": "// Validate incoming webhook data\nconst data = $input.first().json;\nconst startedAt = $('BeyondPresence Webhook').first().json.body.call_data.startedAt;\nconst endedAt = $('BeyondPresence Webhook').first().json.body.call_data.endedAt;\nconst errors = [];\n\n// Check required fields\nif (!data.event_type) errors.push(\"Missing event_type\");\nif (!data.call_id) errors.push(\"Missing call_id\");\nif (!data.user) errors.push(\"Missing user data\");\nif (!data.messages || !Array.isArray(data.messages)) errors.push(\"Missing or invalid messages array\");\n\n// Validate call_data structure\nif (data.call_data) {\n  if (!data.user.name) errors.push(\"Missing userName in call_data\");\n  if (!startedAt ) errors.push(\"Missing startedAt timestamp\");\n  if (!endedAt) errors.push(\"Missing endedAt timestamp\");\n}\n\n// If there are errors, throw with details\nif (errors.length > 0) {\n  throw new Error(\"Validation failed: \" + errors.join(\", \"));\n}\n\n// Add calculated fields\nconst startTime = new Date(startedAt);\nconst endTime = new Date(endedAt);\nconst durationMs = endTime - startTime;\nconst durationMinutes = durationMs / 60000;\n\n// Return enriched data\nreturn [{\n  json: {\n    body: data,\n    validation: {\n      isValid: true,\n      timestamp: new Date().toISOString()\n    },\n    calculated: {\n      durationMinutes: durationMinutes.toFixed(2),\n      durationFormatted: formatDuration(durationMs),\n      dayOfWeek: startTime.toLocaleDateString('en-US', { weekday: 'long' }),\n      timeOfDay: getTimeOfDay(startTime),\n      messageCount: data.messages.length,\n      hasActionItems: checkForActionItems(data.messages)\n    }\n  }\n}];\n\n// Helper functions\nfunction formatDuration(ms) {\n  const seconds = Math.floor(ms / 1000);\n  const minutes = Math.floor(seconds / 60);\n  const remainingSeconds = seconds % 60;\n  return `${minutes}m ${remainingSeconds}s`;\n}\n\nfunction getTimeOfDay(date) {\n  const hour = date.getHours();\n  if (hour < 6) return \"Early Morning\";\n  if (hour < 12) return \"Morning\";\n  if (hour < 17) return \"Afternoon\";\n  if (hour < 21) return \"Evening\";\n  return \"Night\";\n}\n\nfunction checkForActionItems(messages) {\n  const actionKeywords = ['will do', 'action item', 'follow up', 'next step', 'todo', 'task'];\n  return messages.some(msg => \n    actionKeywords.some(keyword => \n      msg.message.toLowerCase().includes(keyword)\n    )\n  );\n}"
      },
      "typeVersion": 2,
      "continueOnFail": true
    },
    {
      "id": "4593d36b-6879-42f7-ada2-1853e4c500aa",
      "name": "AI Call Analysis",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        944,
        400
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "id",
          "value": "gpt-4o-mini"
        },
        "options": {
          "topP": 0.9,
          "temperature": 0.3
        },
        "messages": {
          "values": [
            {
              "role": "system",
              "content": "=You are an expert conversation analyst. Analyze the following call data and provide a comprehensive summary.\n\nCONTEXT:\n- Current Date/Time: {{ $now.format('HH:mm dd.mm.yyyy') }}\n- Participant: {{$json.body.user.name }}\n- Call Duration: {{ $json.calculated.durationMinutes }} minutes\n- Time of Day: {{ $json.calculated.timeOfDay }}\n- Day of Week: {{ $json.calculated.dayOfWeek }}\n- Message Count: {{ $json.calculated.messageCount }}\n\nCONVERSATION DATA:\n{{ $json.body.messages.map(m => `[${m.sent_at}] ${m.sender.toUpperCase()}: ${m.message}`).join('\\n') }}\n\nANALYSIS REQUIREMENTS:\n1. Extract factual information only from the provided data\n2. If a section has no relevant content, use \"Not applicable\"\n3. Identify specific commitments, dates, and actions mentioned\n4. Analyze emotional tone and engagement level\n5. Note any technical issues or interruptions\n\nOUTPUT FORMAT (strict JSON):\n```json\n{\n  \"title\": \"Brief, descriptive title (max 80 chars)\",\n  \"metadata\": {\n    \"participant\": \"exact name from data\",\n    \"date\": \"ISO format date\",\n    \"duration_minutes\": \"number as string\",\n    \"topic\": \"main discussion topic\",\n    \"user_sentiment\": \"satisfied/neutral/dissatisfied\",\n    \"engagement_level\": \"high/medium/low\"\n  },\n  \"summary\": \"2-3 sentence executive summary of the conversation\",\n  \"main_points\": [\"up to 5 key discussion points\"],\n  \"action_items\": [\n    {\"task\": \"specific action\", \"owner\": \"person responsible\", \"due_date\": \"ISO date or TBD\"}\n  ],\n  \"follow_ups\": [\"specific next steps mentioned\"],\n  \"key_decisions\": [\"decisions made during call\"],\n  \"questions_raised\": [\"unresolved questions\"],\n  \"stories\": [\"any anecdotes or examples shared\"],\n  \"references\": [\"external resources or documents mentioned\"],\n  \"arguments\": [\n    {\"point\": \"argument made\", \"counterpoint\": \"opposing view if any\"}\n  ],\n  \"related_topics\": [\"topics that came up in discussion\"],\n  \"sentiment_analysis\": {\n    \"overall\": \"positive/neutral/negative\",\n    \"confidence\": \"high/medium/low\",\n    \"turning_points\": [\"moments where sentiment changed\"],\n    \"key_emotional_indicators\": [\"specific phrases indicating emotion\"]\n  },\n  \"recommendations\": [\"suggested actions based on analysis\"]\n}\n```\n\nRules:\n1. Use ONLY data from the provided conversation\n2. For empty sections, use empty arrays [] or \"Not applicable\"\n3. All dates should be in ISO 8601 format\n4. Be specific and actionable in recommendations"
            },
            {
              "content": "=Analyze this BeyondPresence video agent call:\n\nCall Data: {{ $('BeyondPresence Webhook').item.json.body.call_data.toJsonString() }}\nEvaluation: {{ $('BeyondPresence Webhook').item.json.body.evaluation.toJsonString() }}\nMessages: {{ JSON.stringify($json.body.messages) }}\n\nProvide the analysis in the exact JSON format specified."
            }
          ]
        }
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "95b5df2f-9a6c-4e39-9442-5a7228d95bdb",
      "name": "Save to Google Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1504,
        400
      ],
      "parameters": {
        "columns": {
          "value": {
            "Date": "={{ $json.metadata.date }}",
            "Title": "={{ $json.title }}",
            "Topic": "={{ $json.metadata.topic }}",
            "Story 1": "={{ $json.stories }}",
            "Summary": "={{ $json.summary }}",
            "Argument 1": "={{ $json.references[0]  }}",
            "Argument 2": "={{ $json.references[1] }}",
            "Follow Up 1": "={{ $json.follow_ups[0] }}",
            "Follow Up 2": "={{ $json.follow_ups[1] }}",
            "Participant": "={{ $json.metadata.participant }}",
            "Reference 1": "={{ $json.references[0] }}",
            "Reference 2": "={{ $json.references[1]}}",
            "Main Point 1": "={{ $json.main_points[0] }}",
            "Main Point 2": "={{ $json.main_points[1] }}",
            "Main Point 3": "={{ $json.main_points[2] }}",
            "Action Item 1": "={{ $json.action_items[0]}}",
            "Action Item 2": "={{ $json.action_items[1] }}",
            "User Sentiment": "={{ $json.metadata.user_sentiment }}",
            "Related Topic 1": "={{ $json.related_topics[0] }}",
            "Related Topic 2": "={{ $json.related_topics[1] }}",
            "Related Topic 3": "={{ $json.related_topics[2] }}",
            "Sentiment Overall": "={{ $json.sentiment_analysis.overall }}",
            "Duration (minutes)": "={{ $json.metadata.duration_minutes }}",
            "Sentiment Confidence": "={{ $json.sentiment_analysis.confidence }}",
            "Emotional Indicator 1": "={{ $json.sentiment_analysis.key_emotional_indicators[0]}}",
            "Emotional Indicator 2": "={{ $json.sentiment_analysis.key_emotional_indicators[1]}}"
          },
          "schema": [
            {
              "id": "Title",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Title",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Participant",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Participant",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Date",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Duration (minutes)",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Duration (minutes)",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Topic",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Topic",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "User Sentiment",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "User Sentiment",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Summary",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Summary",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Main Point 1",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Main Point 1",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Main Point 2",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Main Point 2",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Main Point 3",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Main Point 3",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Action Item 1",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Action Item 1",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Action Due 1",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Action Due 1",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Action Item 2",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Action Item 2",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Action Due 2",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Action Due 2",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Follow Up 1",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Follow Up 1",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Follow Up 2",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Follow Up 2",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Story 1",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Story 1",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Reference 1",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Reference 1",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Reference 2",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Reference 2",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Argument 1",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Argument 1",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Counterpoint 1",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Counterpoint 1",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Argument 2",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Argument 2",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Counterpoint 2",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Counterpoint 2",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Related Topic 1",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Related Topic 1",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Related Topic 2",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Related Topic 2",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Related Topic 3",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Related Topic 3",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Sentiment Overall",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Sentiment Overall",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Sentiment Confidence",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Sentiment Confidence",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Emotional Indicator 1",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Emotional Indicator 1",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Emotional Indicator 2",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Emotional Indicator 2",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1TO6-jkCtoSFNLJObtN0UyklgdUd3ZxEnUaNvUaBjpvo/edit#gid=0",
          "cachedResultName": "Sheet1"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1TO6-jkCtoSFNLJObtN0UyklgdUd3ZxEnUaNvUaBjpvo",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1TO6-jkCtoSFNLJObtN0UyklgdUd3ZxEnUaNvUaBjpvo/edit?usp=drivesdk",
          "cachedResultName": "BeyondPresence Call Analytics Template"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.6
    },
    {
      "id": "6084e6a2-3ef5-4968-88da-707fd75373b1",
      "name": "Extension Options",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1696,
        0
      ],
      "parameters": {
        "color": 5,
        "width": 320,
        "height": 580,
        "content": "## \ud83d\udd14 Optional Extensions\n\n**Slack Notification**:\n- Send summary to team channel\n- Alert on negative sentiment\n- Notify about action items\n\n**Database Backup**:\n- Store in PostgreSQL/MySQL\n- Enable advanced analytics\n- Create dashboards\n\n**CRM Integration**:\n- Update contact records\n- Create follow-up tasks\n- Log call activities"
      },
      "typeVersion": 1
    },
    {
      "id": "03ead4a1-3b18-4c48-8cc8-13021a7c2de4",
      "name": "Handle webhook event",
      "type": "n8n-nodes-beyondpresence.beyondPresence",
      "position": [
        480,
        400
      ],
      "parameters": {
        "resource": "webhook",
        "eventType": "call_ended",
        "webhookData": "={{ $json.body.toJsonString()  }}"
      },
      "credentials": {
        "beyondPresenceApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "b50096f9-f930-4c06-9052-3fef621a2061",
      "name": "Parse AI Response",
      "type": "n8n-nodes-base.code",
      "position": [
        1312,
        400
      ],
      "parameters": {
        "jsCode": "// Get the content string from the OpenAI response\nconst content = $input.first().json.message.content;\n\n// Use a regular expression to extract the JSON inside the code block\n// This matches content between ```json and ``` or just between ``` and ```\nconst match = content.match(/```(?:json)?\\s*([\\s\\S]*?)```/);\n\nif (!match || !match[1]) {\n  // If no match with code block, try to extract JSON directly from the content\n  // Sometimes the response might have JSON without code blocks\n  const directJsonMatch = content.match(/\\{[\\s\\S]*\\}/);\n  if (directJsonMatch) {\n    try {\n      return [{ json: JSON.parse(directJsonMatch[0]) }];\n    } catch (e) {\n      throw new Error('Found potential JSON but failed to parse it: ' + e.message);\n    }\n  }\n  throw new Error('No JSON code block found in content.');\n}\n\n// Clean up the JSON string\nlet jsonString = match[1]\n  .replace(/\\/\\/.*$/gm, '')     // Remove single-line comments\n  .replace(/\\/\\*[\\s\\S]*?\\*\\//g, '') // Remove multi-line comments\n  .replace(/,(\\s*[}\\]])/g, '$1') // Remove trailing commas\n  .trim();                       // Trim whitespace\n\nlet parsed;\ntry {\n  parsed = JSON.parse(jsonString);\n} catch (e) {\n  // If initial parse fails, try a more aggressive cleaning approach\n  try {\n    // Try to fix common JSON errors\n    jsonString = jsonString\n      .replace(/(['\"])?([a-zA-Z0-9_]+)(['\"])?\\s*:/g, '\"$2\":') // Ensure property names are quoted\n      .replace(/:\\s*'([^']*)'/g, ':\"$1\"');                   // Replace single quotes with double quotes\n    \n    parsed = JSON.parse(jsonString);\n  } catch (fallbackError) {\n    throw new Error('Failed to parse JSON: ' + e.message + \n      '\\nExtracted string:\\n' + jsonString);\n  }\n}\n\nreturn [{ json: parsed }];"
      },
      "typeVersion": 2,
      "continueOnFail": true
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "9212cd25-fc88-4f43-8e21-85e6f7b0f311",
  "connections": {
    "AI Call Analysis": {
      "main": [
        [
          {
            "node": "Parse AI Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse AI Response": {
      "main": [
        [
          {
            "node": "Save to Google Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Handle webhook event": {
      "main": [
        [
          {
            "node": "Validate & Enrich Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "BeyondPresence Webhook": {
      "main": [
        [
          {
            "node": "Handle webhook event",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate & Enrich Data": {
      "main": [
        [
          {
            "node": "AI Call Analysis",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}