{
  "nodes": [
    {
      "parameters": {
        "content": "## When a call wraps up\n- Vapi sends over the call report. The caller goes into GoHighLevel with the AI summary attached.\n- Message taken or missed? Dana gets a text. Booked or handed off? It just goes in the log.",
        "height": 200,
        "width": 440
      },
      "id": "sticky-section-a",
      "name": "When A Call Wraps Up",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        620,
        -40
      ]
    },
    {
      "parameters": {
        "content": "## Booking while still on the phone\n- Mid-call, the Vapi agent hits this endpoint the moment a caller wants an estimate visit.\n- The appointment lands on the GoHighLevel calendar and the agent reads the confirmation back to the caller.",
        "height": 200,
        "width": 460
      },
      "id": "sticky-section-b",
      "name": "Booking While Still On The Phone",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        0,
        620
      ]
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "demo/vapi-end-of-call",
        "responseMode": "responseNode",
        "options": {}
      },
      "id": "webhook-end-of-call",
      "name": "A customer call just ended",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        0,
        260
      ]
    },
    {
      "parameters": {
        "jsCode": "// Reads Vapi's end-of-call report. Also copes with flat payloads\n// and Zapier-forwarded versions where the nesting got squashed.\nconst body = $json.body ?? $json;\nconst msg = body.message ?? body;\nconst analysis = msg.analysis ?? body.analysis ?? {};\nconst structured = analysis.structuredData ?? body.structuredData ?? {};\nconst call = msg.call ?? body.call ?? {};\n\nconst name = structured.name ?? structured.callerName ?? body.callerName ?? body.name ?? 'Unknown caller';\nconst rawPhone = structured.phone ?? call.customer?.number ?? body.customerNumber ?? body.phone ?? body.from ?? '';\nconst phone = String(rawPhone).replace(/[^\\d+]/g, '');\nconst summary = analysis.summary ?? body.summary ?? msg.summary ?? body.callSummary ?? '';\n\n// Work out how the call ended.\nconst endedReason = String(msg.endedReason ?? body.endedReason ?? body.status ?? '').toLowerCase();\nconst stated = String(structured.outcome ?? body.outcome ?? '').toLowerCase();\n\nlet outcome = 'missed';\nif (stated.includes('book') || endedReason.includes('book')) {\n  outcome = 'booked';\n} else if (stated.includes('transfer') || endedReason.includes('transfer') || endedReason.includes('forward')) {\n  outcome = 'transferred';\n} else if (\n  stated.includes('message') ||\n  (summary && !endedReason.includes('no-answer') && !endedReason.includes('failed') && !endedReason.includes('error'))\n) {\n  outcome = 'message';\n}\n\nconst labels = {\n  booked: 'Booked on the calendar',\n  transferred: 'Handed to a person',\n  message: 'Message taken',\n  missed: 'Missed or failed'\n};\n\nreturn [{ json: {\n  name,\n  phone,\n  summary,\n  outcome,\n  outcomeLabel: labels[outcome],\n  receivedAt: new Date().toISOString()\n} }];"
      },
      "id": "code-read-call-report",
      "name": "Pull out who called and how it went",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        220,
        260
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://services.leadconnectorhq.com/contacts/upsert",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Version",
              "value": "2021-07-28"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ locationId: 'bsHomeSvcDemo01', name: $json.name, phone: $json.phone, source: 'Vapi voice agent', tags: ['voice-agent-call'], customFields: [{ id: 'cfBsCallSummary01', value: $json.summary }] }) }}",
        "options": {}
      },
      "id": "http-save-caller",
      "name": "Save the caller in GoHighLevel",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        440,
        260
      ],
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "leftValue": "={{ $('Pull out who called and how it went').item.json.outcome }}",
                    "rightValue": "message",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "Message taken"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "leftValue": "={{ $('Pull out who called and how it went').item.json.outcome }}",
                    "rightValue": "missed",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "Missed or failed"
            }
          ]
        },
        "options": {
          "fallbackOutput": "extra",
          "renameFallbackOutput": "Booked or handed off"
        }
      },
      "id": "switch-call-outcome",
      "name": "How did the call end?",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.4,
      "position": [
        660,
        260
      ]
    },
    {
      "parameters": {
        "resource": "sms",
        "operation": "send",
        "from": "+15550149000",
        "to": "+15550147788",
        "message": "=Phone agent took a message. {{ $('Pull out who called and how it went').item.json.name }} at {{ $('Pull out who called and how it went').item.json.phone || 'an unknown number' }}. {{ $('Pull out who called and how it went').item.json.summary }}",
        "options": {}
      },
      "id": "twilio-text-dana-message",
      "name": "Text Dana the message",
      "type": "n8n-nodes-base.twilio",
      "typeVersion": 1,
      "position": [
        900,
        140
      ],
      "credentials": {
        "twilioApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "resource": "sms",
        "operation": "send",
        "from": "+15550149000",
        "to": "+15550147788",
        "message": "=The phone agent could not finish a call from {{ $('Pull out who called and how it went').item.json.phone || 'an unknown number' }}. Worth ringing them back before it goes cold.",
        "options": {}
      },
      "id": "twilio-text-dana-missed",
      "name": "Text Dana to ring them back",
      "type": "n8n-nodes-base.twilio",
      "typeVersion": 1,
      "position": [
        900,
        380
      ],
      "credentials": {
        "twilioApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// Every path funnels through here so one row per call lands in the log.\nconst c = $('Pull out who called and how it went').item.json;\nreturn [{ json: {\n  'Caller': c.name + ' ' + (c.phone || 'unknown number'),\n  'Outcome': c.outcomeLabel,\n  'Summary': c.summary || 'No summary came through',\n  'Time': c.receivedAt\n} }];"
      },
      "id": "code-log-row",
      "name": "Get the call ready for the log",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1140,
        260
      ]
    },
    {
      "parameters": {
        "resource": "record",
        "operation": "create",
        "base": {
          "__rl": true,
          "mode": "id",
          "value": "appBlueSummit001"
        },
        "table": {
          "__rl": true,
          "mode": "id",
          "value": "tblLeads"
        },
        "columns": {
          "mappingMode": "autoMapInputData",
          "value": {},
          "matchingColumns": [],
          "schema": []
        },
        "options": {}
      },
      "id": "airtable-log-call",
      "name": "Log the call in Airtable",
      "type": "n8n-nodes-base.airtable",
      "typeVersion": 2.2,
      "position": [
        1360,
        260
      ],
      "credentials": {
        "airtableTokenApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ ok: true }) }}",
        "options": {}
      },
      "id": "respond-report-received",
      "name": "Tell Vapi we got the report",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        1580,
        260
      ]
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "demo/vapi-book",
        "responseMode": "responseNode",
        "options": {}
      },
      "id": "webhook-book",
      "name": "Caller wants an estimate visit",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        0,
        860
      ]
    },
    {
      "parameters": {
        "jsCode": "// The Vapi assistant (asst_frontdesk_bs01) calls this tool mid-call.\n// Arguments can arrive as an object or as a JSON string, and some\n// setups send the fields flat, so all three shapes work here.\nconst body = $json.body ?? $json;\nconst msg = body.message ?? body;\nconst toolCall = (msg.toolCalls ?? msg.toolCallList ?? body.toolCalls ?? [])[0] ?? {};\nlet args = toolCall.function?.arguments ?? toolCall.arguments ?? body;\nif (typeof args === 'string') {\n  try { args = JSON.parse(args); } catch (e) { args = {}; }\n}\n\nconst name = args.name ?? body.name ?? 'Caller';\nconst phone = String(args.phone ?? body.phone ?? '').replace(/[^\\d+]/g, '');\nconst startTime = args.startTime ?? args.datetime ?? args.time ?? '';\nconst requestedTime = args.requestedTime ?? args.day ?? args.time ?? startTime;\nconst toolCallId = toolCall.id ?? body.toolCallId ?? 'demo-tool-call';\n\nreturn [{ json: { name, phone, startTime: startTime || requestedTime, requestedTime, toolCallId } }];"
      },
      "id": "code-read-booking",
      "name": "Read the booking request",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        220,
        860
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://services.leadconnectorhq.com/contacts/upsert",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Version",
              "value": "2021-07-28"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ locationId: 'bsHomeSvcDemo01', name: $json.name, phone: $json.phone, source: 'Vapi voice agent booking', tags: ['voice-agent-call'] }) }}",
        "options": {}
      },
      "id": "http-save-booking-contact",
      "name": "Add the caller to GoHighLevel",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        440,
        860
      ],
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://services.leadconnectorhq.com/calendars/events/appointments",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Version",
              "value": "2021-07-28"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ calendarId: 'calBsEstimates01', locationId: 'bsHomeSvcDemo01', contactId: $json.contact.id, startTime: $('Read the booking request').item.json.startTime, title: 'Estimate visit for ' + $('Read the booking request').item.json.name }) }}",
        "options": {}
      },
      "id": "http-create-appointment",
      "name": "Put the visit on the calendar",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        660,
        860
      ],
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ results: [{ toolCallId: $('Read the booking request').item.json.toolCallId, result: 'You are on the schedule for ' + $('Read the booking request').item.json.requestedTime + '. We will send a text to confirm.' }] }) }}",
        "options": {}
      },
      "id": "respond-booking-result",
      "name": "Read the confirmation back to the caller",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        900,
        860
      ]
    }
  ],
  "connections": {
    "A customer call just ended": {
      "main": [
        [
          {
            "node": "Pull out who called and how it went",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Pull out who called and how it went": {
      "main": [
        [
          {
            "node": "Save the caller in GoHighLevel",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Save the caller in GoHighLevel": {
      "main": [
        [
          {
            "node": "How did the call end?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "How did the call end?": {
      "main": [
        [
          {
            "node": "Text Dana the message",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Text Dana to ring them back",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Get the call ready for the log",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Text Dana the message": {
      "main": [
        [
          {
            "node": "Get the call ready for the log",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Text Dana to ring them back": {
      "main": [
        [
          {
            "node": "Get the call ready for the log",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get the call ready for the log": {
      "main": [
        [
          {
            "node": "Log the call in Airtable",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Log the call in Airtable": {
      "main": [
        [
          {
            "node": "Tell Vapi we got the report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Caller wants an estimate visit": {
      "main": [
        [
          {
            "node": "Read the booking request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read the booking request": {
      "main": [
        [
          {
            "node": "Add the caller to GoHighLevel",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Add the caller to GoHighLevel": {
      "main": [
        [
          {
            "node": "Put the visit on the calendar",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Put the visit on the calendar": {
      "main": [
        [
          {
            "node": "Read the confirmation back to the caller",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}