AutomationFlowsAI & RAG › Voice Agent — Complete Ghl Integration

Voice Agent — Complete Ghl Integration

Voice Agent — Complete GHL Integration. Uses httpRequest. Webhook trigger; 37 nodes.

Webhook trigger★★★★★ complexity37 nodesHTTP Request
AI & RAG Trigger: Webhook Nodes: 37 Complexity: ★★★★★ Added:

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "name": "Voice Agent \u2014 Complete GHL Integration",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "ghl-voice-agent",
        "responseMode": "responseNode",
        "options": {}
      },
      "id": "0eb95e2e-5f0f-4ef7-8886-fe08bc0c05d4",
      "name": "VAPI Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2.1,
      "position": [
        0,
        0
      ]
    },
    {
      "parameters": {
        "jsCode": "const body = $input.first().json.body;\nconst toolCall = body.message.toolCalls[0];\nconst args = toolCall.function[\"arguments\"];\nconst parsed = typeof args === \"string\" ? JSON.parse(args) : args;\nconst nameParts = (parsed.name || '').trim().split(' ');\n\nfunction normalizeTime(t) {\n  if (!t) return '';\n  t = String(t).trim();\n  const upper = t.toUpperCase();\n  const isPM = /PM/i.test(upper);\n  const isAM = /AM/i.test(upper);\n  const cleaned = t.replace(/\\s*(AM|PM)\\s*/gi, '').trim();\n  const parts = cleaned.split(':');\n  let h = parseInt(parts[0], 10);\n  let m = parts[1] ? parseInt(parts[1], 10) : 0;\n  if (isNaN(h)) return '';\n  if (isNaN(m)) m = 0;\n  const hasLeadingZero = parts[0].length === 2 && parts[0][0] === '0';\n  if (isAM) {\n    if (h === 12) h = 0;\n  } else if (isPM) {\n    if (h !== 12) h += 12;\n  } else if (!hasLeadingZero && h >= 1 && h <= 9) {\n    h += 12;\n  }\n  return String(h).padStart(2, '0') + ':' + String(m).padStart(2, '0');\n}\n\nfunction cleanPhone(p) {\n  if (!p) return '';\n  p = String(p).trim();\n  const hasPlus = p.startsWith('+');\n  const digits = p.replace(/\\D/g, '');\n  return hasPlus ? '+' + digits : digits;\n}\n\nreturn [{ json: { callId: toolCall.id, name: parsed.name || '', firstName: nameParts[0] || '', lastName: nameParts.slice(1).join(' ') || '', phone: cleanPhone(parsed.phone), service: parsed.service || 'Consultation', date: parsed.date || '', time: normalizeTime(parsed.time) } }];"
      },
      "id": "b4acd97c-5554-4c60-8fe6-e69a9bd18ac8",
      "name": "Extract Book Params",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        224,
        0
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://services.leadconnectorhq.com/contacts/upsert",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Version",
              "value": "2021-07-28"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ \"firstName\": $('Extract Book Params').item.json.firstName, \"lastName\": $('Extract Book Params').item.json.lastName, \"phone\": $('Extract Book Params').item.json.phone, \"locationId\": \"YOUR_LOCATION_ID\", \"tags\": [\"voice-booking-ghl\"] }) }}",
        "options": {}
      },
      "id": "0fa146a8-3d61-4a96-aaa4-48e94528316f",
      "name": "Create GHL Contact1",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [
        448,
        0
      ],
      "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"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ \"calendarId\": \"YOUR_CALENDAR_ID\", \"locationId\": \"YOUR_LOCATION_ID\", \"contactId\": $json.contact.id, \"title\": $('Extract Book Params').item.json.service + ' - ' + $('Extract Book Params').item.json.name, \"appointmentStatus\": \"confirmed\", \"startTime\": $('Extract Book Params').item.json.date + 'T' + $('Extract Book Params').item.json.time + ':00+05:00', \"endTime\": DateTime.fromISO($('Extract Book Params').item.json.date + 'T' + $('Extract Book Params').item.json.time + ':00', { zone: 'Asia/Karachi' }).plus({ minutes: 30 }).toISO() }) }}",
        "options": {}
      },
      "id": "072574dd-14d4-4bd9-9586-91d55b8f705b",
      "name": "Book GHL Calendar",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [
        672,
        0
      ],
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://services.leadconnectorhq.com/opportunities/",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Version",
              "value": "2021-07-28"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ \"pipelineId\": \"YOUR_PIPELINE_ID\", \"locationId\": \"YOUR_LOCATION_ID\", \"contactId\": $('Create GHL Contact1').item.json.contact.id, \"name\": $('Extract Book Params').item.json.service + ' - ' + $('Extract Book Params').item.json.name, \"status\": \"open\", \"pipelineStageId\": \"YOUR_BOOKED_STAGE_ID\" }) }}",
        "options": {}
      },
      "id": "f2ec8f94-fa72-455b-a90c-6e7effc24ecc",
      "name": "Move Pipeline Booked",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [
        912,
        -64
      ],
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "jsCode": "const callId = $('Extract Book Params').first().json.callId;\nconst date = $('Extract Book Params').first().json.date;\nconst time = $('Extract Book Params').first().json.time;\nconst name = $('Extract Book Params').first().json.firstName;\nreturn [{ json: { results: [{ toolCallId: callId, result: 'Done! ' + name + ', your appointment is confirmed for ' + date + ' at ' + time + '. See you then!' }] } }];"
      },
      "id": "ffd1e6f7-e0a0-4627-a30b-8cc8b5b10d44",
      "name": "Respond Book",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1440,
        -64
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ { \"results\": $json.results } }}",
        "options": {}
      },
      "id": "737556e9-8363-4f19-b291-b112beaa5480",
      "name": "Respond Book Webhook",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.5,
      "position": [
        1680,
        -64
      ]
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "ghl-complete-voice-agent",
        "responseMode": "responseNode",
        "options": {}
      },
      "id": "06ae45de-0ba1-430a-9ec8-fffe00633959",
      "name": "GHL Voice Agent Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2.1,
      "position": [
        0,
        224
      ]
    },
    {
      "parameters": {
        "jsCode": "const body = $input.first().json.body;\nconst toolCall = body.message.toolCalls[0];\nconst args = toolCall.function[\"arguments\"];\nconst parsed = typeof args === \"string\" ? JSON.parse(args) : args;\nreturn [{ json: { callId: toolCall.id, date: parsed.date || '' } }];"
      },
      "id": "18248119-904d-4998-9350-90a974e70dc7",
      "name": "Extract Availability Params",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        224,
        224
      ]
    },
    {
      "parameters": {
        "url": "=https://services.leadconnectorhq.com/calendars/YOUR_CALENDAR_ID/free-slots?startDate={{ new Date($('Extract Availability Params').item.json.date + 'T00:00:00').getTime() }}&endDate={{ new Date($('Extract Availability Params').item.json.date + 'T23:59:59').getTime() }}&timezone=Asia%2FKarachi",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Version",
              "value": "2021-07-28"
            }
          ]
        },
        "options": {}
      },
      "id": "3c41ac23-eade-4e5e-9e4d-8aa694b048ed",
      "name": "Get GHL Available Slots",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [
        448,
        224
      ],
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const items = $input.all().map(i => i.json);\nconst callId = $('Extract Availability Params').first().json.callId;\n\nlet allSlots = [];\nfor (const data of items) {\n  const keys = Object.keys(data).filter(k => k.match(/^\\d{4}-\\d{2}-\\d{2}$/));\n  for (const key of keys) {\n    const dayData = data[key];\n    if (dayData && dayData.slots) {\n      allSlots = allSlots.concat(dayData.slots);\n    }\n  }\n}\n\n// Convert to PKT (UTC+5) and filter business hours 9AM-5PM\nfunction toPKT(dt) {\n  const d = new Date(dt);\n  return new Date(d.getTime() + 5 * 60 * 60 * 1000);\n}\n\nfunction fmt(dt) {\n  const pkt = toPKT(dt);\n  let h = pkt.getUTCHours();\n  const m = pkt.getUTCMinutes();\n  const ap = h < 12 ? 'AM' : 'PM';\n  if (h === 0) h = 12;\n  else if (h > 12) h -= 12;\n  return h + (m > 0 ? ':' + String(m).padStart(2,'0') : '') + ' ' + ap;\n}\n\nconst filtered = allSlots.filter(s => {\n  const h = toPKT(s).getUTCHours();\n  return h >= 9 && h < 17;\n});\n\nconst times = filtered.slice(0, 3).map(s => fmt(s));\nlet message;\nif (times.length === 0) {\n  message = 'That day looks fully booked. Would another day work for you?';\n} else if (times.length === 1) {\n  message = 'The only opening that day is ' + times[0] + '. Would that work?';\n} else {\n  message = 'I have a few openings that day \u2014 ' + times.slice(0,-1).join(', ') + ' or ' + times[times.length-1] + '. Which suits you best?';\n}\n\nreturn [{ json: { results: [{ toolCallId: callId, result: message }] } }];"
      },
      "id": "9063036a-ee5f-4665-b9ec-4e10c7214ef7",
      "name": "Format Availability Response",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        672,
        224
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ { \"results\": $json.results } }}",
        "options": {}
      },
      "id": "922ce56a-c04e-46fc-8df0-9227638e56b5",
      "name": "Respond Availability",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.5,
      "position": [
        896,
        224
      ]
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "ghl-get-booking",
        "responseMode": "responseNode",
        "options": {}
      },
      "id": "57cd5e05-2d6a-4897-9f16-f052e39c6809",
      "name": "Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2.1,
      "position": [
        0,
        448
      ]
    },
    {
      "parameters": {
        "jsCode": "const body = $input.first().json.body;\nconst toolCall = body.message.toolCalls[0];\nconst args = toolCall.function[\"arguments\"];\nconst parsed = typeof args === \"string\" ? JSON.parse(args) : args;\nreturn [{ json: { callId: toolCall.id, name: parsed.name || '', phone: parsed.phone || '' } }];"
      },
      "id": "1ae9ef7a-5a62-4020-ba72-71d6efdf0758",
      "name": "Extract Get Params",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        224,
        448
      ]
    },
    {
      "parameters": {
        "url": "=https://services.leadconnectorhq.com/contacts/?locationId=YOUR_LOCATION_ID&query={{ $json.name || $json.phone }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Version",
              "value": "2021-07-28"
            }
          ]
        },
        "options": {}
      },
      "id": "a74dc3c9-ea75-4c18-a9d6-ea0c36efb1ba",
      "name": "Search GHL Contact",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [
        448,
        448
      ],
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const data = $input.first().json;\nconst callId = $('Extract Get Params').first().json.callId;\nconst contacts = data.contacts || [];\nif (contacts.length === 0) { return [{ json: { results: [{ toolCallId: callId, result: \"I couldn't find a booking under that name or number. Could you please provide both your full name and phone number?\" }] } }]; }\nconst contact = contacts[0];\nreturn [{ json: { results: [{ toolCallId: callId, result: 'I found your record \u2014 ' + (contact.firstName || '') + ' ' + (contact.lastName || '') + '. Would you like to reschedule or cancel your appointment?' }], contactId: contact.id } }];"
      },
      "id": "4118036c-02e5-4ed6-9645-31c2db4527b5",
      "name": "Format Get Response",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        672,
        448
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ { \"results\": $json.results } }}",
        "options": {}
      },
      "id": "0fb31032-30a9-4521-89cc-e8a9b6ffa916",
      "name": "Respond Get",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.5,
      "position": [
        896,
        448
      ]
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "ghl-reschedule",
        "responseMode": "responseNode",
        "options": {}
      },
      "id": "bc969ab6-c6a3-44bb-aa14-e22f4ffc7bd4",
      "name": "Webhook1",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2.1,
      "position": [
        0,
        672
      ]
    },
    {
      "parameters": {
        "jsCode": "const body = $input.first().json.body;\nconst toolCall = body.message.toolCalls[0];\nconst args = toolCall.function[\"arguments\"];\nconst parsed = typeof args === \"string\" ? JSON.parse(args) : args;\n\nfunction normalizeTime(t) {\n  if (!t) return '';\n  t = String(t).trim();\n  const upper = t.toUpperCase();\n  const isPM = /PM/i.test(upper);\n  const isAM = /AM/i.test(upper);\n  const cleaned = t.replace(/\\s*(AM|PM)\\s*/gi, '').trim();\n  const parts = cleaned.split(':');\n  let h = parseInt(parts[0], 10);\n  let m = parts[1] ? parseInt(parts[1], 10) : 0;\n  if (isNaN(h)) return '';\n  if (isNaN(m)) m = 0;\n  // If already in 24hr format (has leading zero like 09 or h>=12) leave as-is\n  const hasLeadingZero = parts[0].length === 2 && parts[0][0] === '0';\n  if (isAM) {\n    if (h === 12) h = 0;\n  } else if (isPM) {\n    if (h !== 12) h += 12;\n  } else if (!hasLeadingZero && h >= 1 && h <= 9) {\n    // Only ambiguous single-digit hours get PM treatment\n    h += 12;\n  }\n  // If h >= 17 and no explicit PM was given, likely wrong - cap at business hours\n  return String(h).padStart(2, '0') + ':' + String(m).padStart(2, '0');\n}\n\nreturn [{ json: { callId: toolCall.id, name: parsed.name || '', phone: parsed.phone || '', newDate: parsed.newDate || parsed.date || '', newTime: normalizeTime(parsed.newTime || parsed.time || '') } }];"
      },
      "id": "cf239a85-0776-427b-8d6b-ab8a7f240f4c",
      "name": "Extract Reschedule Params",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        224,
        672
      ]
    },
    {
      "parameters": {
        "url": "=https://services.leadconnectorhq.com/contacts/?locationId=YOUR_LOCATION_ID&query={{ $json.name || $json.phone }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Version",
              "value": "2021-07-28"
            }
          ]
        },
        "options": {}
      },
      "id": "f1dadd8d-39cd-4752-8368-0af3468e0fa5",
      "name": "Find Contact Reschedule",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [
        448,
        672
      ],
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "url": "=https://services.leadconnectorhq.com/contacts/{{ $json.contacts[0].id }}/appointments",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Version",
              "value": "2021-07-28"
            }
          ]
        },
        "options": {}
      },
      "id": "888ffb2b-ef09-48a3-923f-0d019621314c",
      "name": "Get Contact Appointments",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [
        672,
        672
      ],
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "method": "DELETE",
        "url": "=https://services.leadconnectorhq.com/calendars/events/appointments/{{ $json.events && $json.events[0] ? $json.events[0].id : '' }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Version",
              "value": "2021-07-28"
            }
          ]
        },
        "options": {}
      },
      "id": "ccb16be0-0b3d-4cce-a365-4ba9e9883227",
      "name": "Delete Old Appointment",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [
        896,
        672
      ],
      "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"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ \"calendarId\": \"YOUR_CALENDAR_ID\", \"locationId\": \"YOUR_LOCATION_ID\", \"contactId\": $('Find Contact Reschedule').item.json.contacts[0].id, \"title\": \"Rescheduled - \" + $('Extract Reschedule Params').item.json.name, \"appointmentStatus\": \"confirmed\", \"startTime\": $('Extract Reschedule Params').item.json.newDate + 'T' + $('Extract Reschedule Params').item.json.newTime + ':00+05:00', \"endTime\": DateTime.fromISO($('Extract Reschedule Params').item.json.newDate + 'T' + $('Extract Reschedule Params').item.json.newTime + ':00', { zone: 'Asia/Karachi' }).plus({ minutes: 30 }).toISO() }) }}",
        "options": {}
      },
      "id": "7899677f-b788-490f-97fa-cebfc781ec80",
      "name": "Create New Appointment",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [
        1120,
        672
      ],
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const callId = $('Extract Reschedule Params').first().json.callId;\nconst newDate = $('Extract Reschedule Params').first().json.newDate;\nconst newTime = $('Extract Reschedule Params').first().json.newTime;\nreturn [{ json: { results: [{ toolCallId: callId, result: 'Done! Your appointment has been rescheduled to ' + newDate + ' at ' + newTime + '. You will receive an SMS confirmation shortly.' }] } }];"
      },
      "id": "d605bc3a-9c2c-445a-b5b3-53d4d38118f6",
      "name": "Respond Reschedule",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1344,
        672
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ { \"results\": $json.results } }}",
        "options": {}
      },
      "id": "03a77a64-7838-4d26-9ccc-c717de4f30bb",
      "name": "Respond Reschedule Webhook",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.5,
      "position": [
        1568,
        672
      ]
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "ghl-cancel",
        "responseMode": "responseNode",
        "options": {}
      },
      "id": "3fcc2de9-d803-42e6-a027-5e2202458fc4",
      "name": "Webhook2",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2.1,
      "position": [
        0,
        896
      ]
    },
    {
      "parameters": {
        "jsCode": "const body = $input.first().json.body;\nconst toolCall = body.message.toolCalls[0];\nconst args = toolCall.function[\"arguments\"];\nconst parsed = typeof args === \"string\" ? JSON.parse(args) : args;\nreturn [{ json: { callId: toolCall.id, name: parsed.name || '', phone: parsed.phone || '' } }];"
      },
      "id": "675d4b0b-8ed5-4a23-aa69-a6815059aa66",
      "name": "Extract Cancel Params",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        224,
        896
      ]
    },
    {
      "parameters": {
        "url": "=https://services.leadconnectorhq.com/contacts/?locationId=YOUR_LOCATION_ID&query={{ $json.name || $json.phone }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Version",
              "value": "2021-07-28"
            }
          ]
        },
        "options": {}
      },
      "id": "02ace81a-d8a6-4dd4-a26d-b7dd6e8d6f36",
      "name": "Find Contact Cancel",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [
        448,
        896
      ],
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "url": "=https://services.leadconnectorhq.com/contacts/{{ $json.contacts[0].id }}/appointments",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Version",
              "value": "2021-07-28"
            }
          ]
        },
        "options": {}
      },
      "id": "08ed8040-c36a-49bc-bba8-52c21b96937c",
      "name": "Get Appointment To Cancel",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [
        672,
        896
      ],
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "method": "DELETE",
        "url": "=https://services.leadconnectorhq.com/calendars/events/appointments/{{ $json.events && $json.events[0] ? $json.events[0].id : '' }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Version",
              "value": "2021-07-28"
            }
          ]
        },
        "options": {}
      },
      "id": "6278c4b9-83c8-4da3-9c39-ebebefde8363",
      "name": "Delete Appointment",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [
        896,
        896
      ],
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "url": "=https://services.leadconnectorhq.com/opportunities/search?location_id=YOUR_LOCATION_ID&contact_id={{ $('Find Contact Cancel').item.json.contacts[0].id }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Version",
              "value": "2021-07-28"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "options": {}
      },
      "id": "8a32bfb0-b334-4506-a232-9cd1c3d19ab0",
      "name": "Move Pipeline Cancelled",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [
        1120,
        896
      ],
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const callId = $('Extract Cancel Params').first().json.callId;\nreturn [{ json: { results: [{ toolCallId: callId, result: 'Done. Your appointment has been cancelled and updated in our system. We hope to see you again soon. Take care!' }] } }];"
      },
      "id": "05650e63-b8c0-4814-b636-c0a93c99e9d1",
      "name": "Respond Cancel",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1616,
        896
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ { \"results\": $json.results } }}",
        "options": {}
      },
      "id": "8b32e3f0-8f7e-4ded-b98b-f8e891641ab6",
      "name": "Respond Cancel Webhook",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.5,
      "position": [
        1888,
        896
      ]
    },
    {
      "parameters": {
        "method": "PUT",
        "url": "=https://services.leadconnectorhq.com/opportunities/{{ $json.opportunities[0].id }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Version",
              "value": "2021-07-28"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "{\"status\": \"lost\", \"pipelineStageId\": \"YOUR_CANCELLED_STAGE_ID\"}",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [
        1312,
        896
      ],
      "id": "4092bcf4-f3a7-463c-9791-143557d69107",
      "name": "Update Opportunity Cancelled",
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const callId = $('Extract Book Params').first().json.callId;\nconst date = $('Extract Book Params').first().json.date;\nreturn [{ json: { results: [{ toolCallId: callId, result: \"I'm sorry, that time slot is no longer available. Let me offer you different options \u2014 would another time on \" + date + \" work, or would you prefer a different day altogether?\" }] } }];"
      },
      "id": "29517e9a-5c82-4705-a9ee-8ff6f37ec410",
      "name": "Handle Slot Unavailable",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1280,
        160
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ { \"results\": $json.results } }}",
        "options": {}
      },
      "id": "cd4f9be6-2111-4f59-8689-e13b6412b01e",
      "name": "Respond Slot Error",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.5,
      "position": [
        1712,
        160
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://services.leadconnectorhq.com/conversations/messages",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Version",
              "value": "2021-07-28"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ \"type\": \"SMS\", \"contactId\": $('Create GHL Contact1').item.json.contact.id, \"locationId\": \"YOUR_LOCATION_ID\", \"message\": \"Hi \" + $('Extract Book Params').item.json.firstName + \", your \" + $('Extract Book Params').item.json.service + \" appointment is confirmed for \" + $('Extract Book Params').item.json.date + \" at \" + $('Extract Book Params').item.json.time + \". See you soon! - BrightSmile Dental\" }) }}",
        "options": {}
      },
      "id": "6c9e5417-01b2-441e-93ad-148aac2bb9e2",
      "name": "Send Booking SMS",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [
        1136,
        -64
      ],
      "alwaysOutputData": true,
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "onError": "continueRegularOutput"
    }
  ],
  "connections": {
    "VAPI Webhook": {
      "main": [
        [
          {
            "node": "Extract Book Params",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Book Params": {
      "main": [
        [
          {
            "node": "Create GHL Contact1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create GHL Contact1": {
      "main": [
        [
          {
            "node": "Book GHL Calendar",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Book GHL Calendar": {
      "main": [
        [
          {
            "node": "Move Pipeline Booked",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Handle Slot Unavailable",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Move Pipeline Booked": {
      "main": [
        [
          {
            "node": "Send Booking SMS",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Respond Book": {
      "main": [
        [
          {
            "node": "Respond Book Webhook",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GHL Voice Agent Webhook": {
      "main": [
        [
          {
            "node": "Extract Availability Params",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Availability Params": {
      "main": [
        [
          {
            "node": "Get GHL Available Slots",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get GHL Available Slots": {
      "main": [
        [
          {
            "node": "Format Availability Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Availability Response": {
      "main": [
        [
          {
            "node": "Respond Availability",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Webhook": {
      "main": [
        [
          {
            "node": "Extract Get Params",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Get Params": {
      "main": [
        [
          {
            "node": "Search GHL Contact",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search GHL Contact": {
      "main": [
        [
          {
            "node": "Format Get Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Get Response": {
      "main": [
        [
          {
            "node": "Respond Get",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Webhook1": {
      "main": [
        [
          {
            "node": "Extract Reschedule Params",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Reschedule Params": {
      "main": [
        [
          {
            "node": "Find Contact Reschedule",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Find Contact Reschedule": {
      "main": [
        [
          {
            "node": "Get Contact Appointments",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Contact Appointments": {
      "main": [
        [
          {
            "node": "Delete Old Appointment",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Delete Old Appointment": {
      "main": [
        [
          {
            "node": "Create New Appointment",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create New Appointment": {
      "main": [
        [
          {
            "node": "Respond Reschedule",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Respond Reschedule": {
      "main": [
        [
          {
            "node": "Respond Reschedule Webhook",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Webhook2": {
      "main": [
        [
          {
            "node": "Extract Cancel Params",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Cancel Params": {
      "main": [
        [
          {
            "node": "Find Contact Cancel",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Find Contact Cancel": {
      "main": [
        [
          {
            "node": "Get Appointment To Cancel",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Appointment To Cancel": {
      "main": [
        [
          {
            "node": "Delete Appointment",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Delete Appointment": {
      "main": [
        [
          {
            "node": "Move Pipeline Cancelled",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Move Pipeline Cancelled": {
      "main": [
        [
          {
            "node": "Update Opportunity Cancelled",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Respond Cancel": {
      "main": [
        [
          {
            "node": "Respond Cancel Webhook",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update Opportunity Cancelled": {
      "main": [
        [
          {
            "node": "Respond Cancel",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Handle Slot Unavailable": {
      "main": [
        [
          {
            "node": "Respond Slot Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Booking SMS": {
      "main": [
        [
          {
            "node": "Respond Book",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": true,
  "settings": {
    "executionOrder": "v1",
    "binaryMode": "separate",
    "availableInMCP": true
  },
  "versionId": "a9c2835a-51c5-4744-a277-82947591586a",
  "meta": {
    "aiBuilderAssisted": true,
    "builderVariant": "mcp",
    "templateCredsSetupCompleted": true
  },
  "nodeGroups": [],
  "id": "HwIggz54RsnZA03G",
  "tags": []
}

Credentials you'll need

Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

Voice Agent — Complete GHL Integration. Uses httpRequest. Webhook trigger; 37 nodes.

Source: https://github.com/Usman-rai/ghl-ai-voice-receptionist/blob/main/workflow/ghl-voice-receptionist.n8n.json — original creator credit. Request a take-down →

More AI & RAG workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

AI & RAG

Jigsaw API key for image processing, I use this as a gatekeeper/second pair of eyes. LINK to their website https://jigsawstack.com/ SECOND A postgress DATABASE (I use Supabase) LlamaCloud for the pars

HTTP Request, Postgres, Stop And Error +2
AI & RAG

Onsite Photos to Jobs (SMS Agent). Uses dataTable, twilio, httpRequest, airtable. Webhook trigger; 62 nodes.

Data Table, Twilio, HTTP Request +1
AI & RAG

W1 - IN WhatsApp Adapter (Secure + Fast ACK). Uses postgres, redis, httpRequest. Webhook trigger; 50 nodes.

Postgres, Redis, HTTP Request
AI & RAG

W1 - IN WhatsApp Adapter (Secure + Fast ACK). Uses postgres, redis, httpRequest. Webhook trigger; 48 nodes.

Postgres, Redis, HTTP Request
AI & RAG

WF-OB-2: Post-Call Handler. Uses httpRequest, slack. Webhook trigger; 48 nodes.

HTTP Request, Slack