{
  "id": "ZEt8fHPKy3Ye5CYX",
  "name": "Voice AI Appointment Booking System with Vapi & Google Calendar - Template",
  "tags": [],
  "nodes": [
    {
      "id": "e7503d57-8408-4b88-bcc7-52b8b9a0751f",
      "name": "Book Appointment Webhook",
      "type": "n8n-nodes-base.webhook",
      "position": [
        2608,
        2432
      ],
      "parameters": {
        "path": "book-appointment",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 2.1
    },
    {
      "id": "ffaa0c7e-ebe5-4794-9b08-7772630ab556",
      "name": "Extract Booking Data",
      "type": "n8n-nodes-base.set",
      "position": [
        2832,
        2432
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "888090a8-b557-4d1e-8ead-c247b701a37a",
              "name": "name",
              "type": "string",
              "value": "={{ $json.body.message.toolCalls[0].function.arguments.name }}"
            },
            {
              "id": "25750d43-8579-4eb5-bf84-bdd3753fd69d",
              "name": "email",
              "type": "string",
              "value": "={{ $json.body.message.toolCalls[0].function.arguments.email }}"
            },
            {
              "id": "57ae0306-b762-4e4e-8af3-45ed50c9dd59",
              "name": "phone",
              "type": "string",
              "value": "={{ $json.body.message.toolCalls[0].function.arguments.phoneNumber }}"
            },
            {
              "id": "fc18bf95-65f2-4d5c-81c3-59cbd8b33d06",
              "name": "appointmentDate",
              "type": "string",
              "value": "={{ $json.body.message.toolCalls[0].function.arguments.appointmentDate }}"
            },
            {
              "id": "1db91e90-015a-4dd5-829b-84588352e1eb",
              "name": "appointmentTime",
              "type": "string",
              "value": "={{ $json.body.message.toolCalls[0].function.arguments.appointmentTime }}"
            },
            {
              "id": "4260e29b-4098-46e5-9c17-0a0e823953da",
              "name": "duration",
              "type": "string",
              "value": "={{ $json.body.message.toolCalls[0].function.arguments.duration }}"
            },
            {
              "id": "54728c90-79d8-4a7e-90dc-b7d2fa023258",
              "name": "service",
              "type": "string",
              "value": "={{ $json.body.message.toolCalls[0].function.arguments.service }}"
            },
            {
              "id": "f26383cd-3d5c-45ea-b042-57b85261c3a4",
              "name": "notes",
              "type": "string",
              "value": "={{ $json.body.message.toolCalls[0].function.arguments.notes }}"
            },
            {
              "id": "ae3e11d8-bb91-4717-9a3b-4b4cad1f68bd",
              "name": "toolCallId",
              "type": "string",
              "value": "={{ $json.body.message.toolCalls[0].id }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "93cabadc-fc25-4779-9c59-f63662518615",
      "name": "Validate Required Fields",
      "type": "n8n-nodes-base.if",
      "position": [
        3056,
        2432
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "67d148c4-4e97-41cd-a98b-5e3cd9d18898",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ $json.name }}",
              "rightValue": ""
            },
            {
              "id": "d78fff76-793d-40d1-b9e0-f25be4e69ce7",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ $json.phone }}",
              "rightValue": ""
            },
            {
              "id": "343af58d-4768-4e74-870f-3f36d7911ae5",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ $json.appointmentDate }}",
              "rightValue": ""
            },
            {
              "id": "f0ecd392-52d3-4b58-92e0-63d9e8e2e16d",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ $json.appointmentTime }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "9b26b185-7285-4ed1-a47b-21046b0052c4",
      "name": "Respond - Validation Error",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        3664,
        2528
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 1.5
    },
    {
      "id": "b5dc67a8-f6d6-4ff5-82d1-9345c98cb5c8",
      "name": "Create Calendar Event",
      "type": "n8n-nodes-base.googleCalendar",
      "position": [
        3248,
        2272
      ],
      "parameters": {
        "end": "={{ DateTime.fromISO($json.appointmentDate + 'T' + $json.appointmentTime + ':00', {zone: 'America/Vancouver'}).plus({minutes: parseInt($json.duration) || 30}).toISO() }}",
        "start": "={{ DateTime.fromISO($json.appointmentDate + 'T' + $json.appointmentTime + ':00', {zone: 'America/Vancouver'}).toISO() }}",
        "calendar": {
          "__rl": true,
          "mode": "list",
          "value": "user@example.com",
          "cachedResultName": "Bookings"
        },
        "additionalFields": {
          "summary": "={{ $json.service + ' - ' + $json.name }}",
          "attendees": [],
          "description": "=Customer: {{ $json.name }} Phone: {{ $json.phone }} Service: {{ $json.service }} Notes: {{ $json.notes }}"
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "948e3ab6-bddc-432f-a918-c03d41c4836f",
      "name": "Respond - Booking Success",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        3664,
        2272
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 1.5
    },
    {
      "id": "f4f3b539-ac7b-471d-bbb9-8d35dc9fc1df",
      "name": "Format Booking Success Response",
      "type": "n8n-nodes-base.code",
      "position": [
        3456,
        2272
      ],
      "parameters": {
        "jsCode": "// Get data from Extract Booking Data node\nconst editFieldsData = $('Extract Booking Data').first().json;\nconst webhookData = $('Book Appointment Webhook').first().json;\n\n// Get toolCallId from webhook body\nconst toolCallId = webhookData.body?.message?.toolCallList?.[0]?.id;\n\n// Create Vapi response format\nreturn [{\n  json: {\n    results: [{\n      toolCallId: toolCallId,\n      result: `The appointment for ${editFieldsData.name} at ${editFieldsData.appointmentDate} at ${editFieldsData.appointmentTime} was booked successfully.`\n    }]\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "d06933a5-aa49-462d-bc71-31e14cd39170",
      "name": "Format Validation Error Response",
      "type": "n8n-nodes-base.code",
      "position": [
        3440,
        2528
      ],
      "parameters": {
        "jsCode": "// Get data from Extract Booking Data node\nconst editFieldsData = $('Extract Booking Data').first().json;\nconst webhookData = $('Book Appointment Webhook').first().json;\n\n// Get toolCallId from webhook body\nconst toolCallId = webhookData.body?.message?.toolCallList?.[0]?.id;\n\n// Create Vapi response format\nreturn [{\n  json: {\n    results: [{\n      toolCallId: toolCallId,\n      result: `The input data from webhook is missing required fields.`\n    }]\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "9b9c9b54-8564-42b2-bedc-c08faceb61b9",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2128,
        2416
      ],
      "parameters": {
        "color": 6,
        "width": 284,
        "height": 244,
        "content": "## Tool 2: Book Appointment\n\nReceives booking data from Vapi, validates required fields, creates Google Calendar event, and returns formatted response."
      },
      "typeVersion": 1
    },
    {
      "id": "8fa0a809-d354-4a68-b33f-baff4d2196e6",
      "name": "Get Calendar Events for Date",
      "type": "n8n-nodes-base.googleCalendar",
      "position": [
        3056,
        1456
      ],
      "parameters": {
        "options": {
          "timeZone": {
            "__rl": true,
            "mode": "list",
            "value": "America/Vancouver",
            "cachedResultName": "America/Vancouver"
          }
        },
        "timeMax": "={{ $json.appointmentDate }}T23:59:59-08:00",
        "timeMin": "={{ $json.appointmentDate }}T00:00:00-08:00",
        "calendar": {
          "__rl": true,
          "mode": "id",
          "value": "user@example.com",
          "__regex": "(^[a-zA-Z0-9.!#$%&\u2019*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]+)*)"
        },
        "operation": "getAll"
      },
      "typeVersion": 1.3,
      "alwaysOutputData": true
    },
    {
      "id": "76b97cbd-354b-4793-a106-a1f706d1cd68",
      "name": "Check Availability Webhook",
      "type": "n8n-nodes-base.webhook",
      "position": [
        2608,
        1456
      ],
      "parameters": {
        "path": "availability",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 2.1
    },
    {
      "id": "3dd18a71-b6fc-4ae9-a3c6-842a8b618b23",
      "name": "Extract Availability Request",
      "type": "n8n-nodes-base.set",
      "position": [
        2832,
        1456
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "74d32727-53c1-4097-be98-6890c7d42a45",
              "name": "appointmentDate",
              "type": "string",
              "value": "={{ $json.body.message.toolCalls[0].function.arguments.appointmentDate }}"
            },
            {
              "id": "58b24bc7-3cea-4596-9401-d028bca697a4",
              "name": "appointmentTime",
              "type": "string",
              "value": "={{ $json.body.message.toolCalls[0].function.arguments.appointmentTime }}"
            },
            {
              "id": "2d2d38d9-f680-4e4b-9bb1-270bc810dbcd",
              "name": "requestedDatetime",
              "type": "string",
              "value": "={{ DateTime.fromISO($json.body.message.toolCalls[0].function.arguments.appointmentDate + 'T' + $json.body.message.toolCalls[0].function.arguments.appointmentTime + ':00', { zone: 'America/Vancouver' }).toFormat(\"yyyy-MM-dd'T'HH:mm:ssZZ\") }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "324e6bdb-55bb-44d7-9efc-8a727ca8bc86",
      "name": "Check Time Slot Availability",
      "type": "n8n-nodes-base.if",
      "position": [
        3280,
        1456
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "20c2de12-9104-4211-99c1-623bcc344a1e",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.start.dateTime }}",
              "rightValue": "={{ $items('Edit Fields1')[0].json.requestedDatetime }}"
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "7e25e200-ae98-412b-92cd-29d9267a5be3",
      "name": "Respond - Time Available",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        3728,
        1552
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 1.5
    },
    {
      "id": "3aab7fb2-6089-49a8-9ceb-7a93febfed13",
      "name": "Respond - Time Unavailable",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        3728,
        1360
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 1.5
    },
    {
      "id": "b168952e-55aa-4584-8558-bda1f5e28c63",
      "name": "Format Unavailable Time Response",
      "type": "n8n-nodes-base.code",
      "position": [
        3504,
        1360
      ],
      "parameters": {
        "jsCode": "// Get data from previous nodes\nconst calendarEvents = $('Get Calendar Events for Date').all().map(item => item.json);\nconst requestedDate = $('Extract Availability Request').first().json.appointmentDate;\nconst requestedTime = $('Extract Availability Request').first().json.appointmentTime;\nconst duration = $('Extract Availability Request').first().json.duration;\nconst timeZone = $('Extract Availability Request').first().json.timeZone;\nconst toolCallId = $('Check Availability Webhook').first().json.body.message.toolCallList[0].id;\n\n// Business hours (customize as needed)\nconst WORKDAY_START = \"09:00\";\nconst WORKDAY_END = \"18:00\";\nconst SLOT_DURATION_MINUTES = 30;\n\n// Helper function to format time as HH:MM\nfunction formatTime(date) {\n  return date.toLocaleTimeString('en-US', { \n    hour: '2-digit', \n    minute: '2-digit',\n    hour12: true,\n    timeZone: timeZone \n  });\n}\n\n// Generate all possible 30-minute slots for the day\nfunction generateDaySlots(date) {\n  const slots = [];\n  const [startHour, startMin] = WORKDAY_START.split(':').map(Number);\n  const [endHour, endMin] = WORKDAY_END.split(':').map(Number);\n  \n  let current = new Date(`${date}T${WORKDAY_START}:00`);\n  const end = new Date(`${date}T${WORKDAY_END}:00`);\n  \n  while (current < end) {\n    const slotEnd = new Date(current.getTime() + SLOT_DURATION_MINUTES * 60 * 1000);\n    slots.push({\n      start: current.toISOString(),\n      end: slotEnd.toISOString(),\n      startTime: formatTime(current),\n      endTime: formatTime(slotEnd)\n    });\n    current = slotEnd;\n  }\n  \n  return slots;\n}\n\n// Check if a slot overlaps with any calendar events\nfunction isSlotAvailable(slot, events) {\n  const slotStart = new Date(slot.start);\n  const slotEnd = new Date(slot.end);\n  \n  for (const event of events) {\n    const eventStart = new Date(event.start.dateTime);\n    const eventEnd = new Date(event.end.dateTime);\n    \n    // Check for overlap\n    if (slotStart < eventEnd && slotEnd > eventStart) {\n      return false;\n    }\n  }\n  \n  return true;\n}\n\n// Generate all slots for the requested day\nconst allSlots = generateDaySlots(requestedDate);\n\n// Filter to only available slots\nconst availableSlots = allSlots.filter(slot => \n  isSlotAvailable(slot, calendarEvents)\n);\n\n// Take first 3 available slots as suggestions\nconst suggestedSlots = availableSlots.slice(0, 3);\n\nreturn {\n    results: [{\n      toolCallId: toolCallId,\nresult: `UNAVAILABLE - That requested time slot is already booked. However, I have these available times that day: ${suggestedSlots.map(s => `${s.startTime} to ${s.endTime}`).join(', ')}`  }]};"
      },
      "typeVersion": 2
    },
    {
      "id": "ed8ce086-844b-4207-888e-7f5def845232",
      "name": "Format Available Time Response",
      "type": "n8n-nodes-base.code",
      "position": [
        3504,
        1552
      ],
      "parameters": {
        "jsCode": "// Get data from Extract Availability Request node\nconst editFieldsData = $('Extract Availability Request').first().json;\nconst webhookData = $('Check Availability Webhook').first().json;\n\n// Get toolCallId from webhook body\nconst toolCallId = webhookData.body?.message?.toolCallList?.[0]?.id || 'available-check';\n\n// Create Vapi response format\nreturn [{\n  json: {\n    results: [{\n      toolCallId: toolCallId,\n      result: `The time slot on ${editFieldsData.appointmentDate} at ${editFieldsData.appointmentTime} is available for booking.`\n    }]\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "92eec412-0717-40cc-bc2e-456e798ab190",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2080,
        1472
      ],
      "parameters": {
        "color": 4,
        "width": 300,
        "height": 212,
        "content": "## Tool 1: Check Availability\n\nQueries Google Calendar for requested date, checks if time slot is available, and suggests alternatives if booked."
      },
      "typeVersion": 1
    },
    {
      "id": "c57866e7-e8b8-4817-b664-54bea38622e8",
      "name": "Route by Action Type",
      "type": "n8n-nodes-base.switch",
      "position": [
        2992,
        3488
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "outputKey": "Update",
              "conditions": {
                "options": {
                  "version": 3,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "55619954-92f2-44ed-9c74-4f2bf3aae47b",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.action }}",
                    "rightValue": "update"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "Cancel",
              "conditions": {
                "options": {
                  "version": 3,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "66c8b926-7005-415d-9d4a-385f71d9aa2a",
                    "operator": {
                      "name": "filter.operator.equals",
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.action }}",
                    "rightValue": "cancel"
                  }
                ]
              },
              "renameOutput": true
            }
          ]
        },
        "options": {}
      },
      "typeVersion": 3.4
    },
    {
      "id": "43ad4f84-5636-4ce7-833f-db74fb6cad01",
      "name": "Update Calendar Event",
      "type": "n8n-nodes-base.googleCalendar",
      "position": [
        3440,
        3312
      ],
      "parameters": {
        "eventId": "={{ $json.id }}",
        "calendar": {
          "__rl": true,
          "mode": "list",
          "value": "user@example.com",
          "cachedResultName": "Bookings"
        },
        "operation": "update",
        "updateFields": {
          "end": "={{ DateTime.fromISO($('Extract Appointment Management Data').item.json.newDate + 'T' + $('Extract Appointment Management Data').item.json.newTime + ':00', { zone: 'America/Vancouver' }).plus({minutes: 30}).toISO() }}",
          "start": "={{ DateTime.fromISO($('Extract Appointment Management Data').item.json.newDate + 'T' + $('Extract Appointment Management Data').item.json.newTime + ':00', { zone: 'America/Vancouver' }).toISO() }}"
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "be76caef-429e-47da-8a3a-172608ef38a9",
      "name": "Find Appointment to Cancel",
      "type": "n8n-nodes-base.googleCalendar",
      "position": [
        3216,
        3584
      ],
      "parameters": {
        "options": {},
        "timeMax": "={{ DateTime.fromISO($json.originalDate + 'T' + $json.originalTime + ':00', { zone: 'America/Vancouver' }).plus({ minutes: 1 }).toISO() }}",
        "timeMin": "={{ DateTime.fromISO($json.originalDate + 'T' + $json.originalTime + ':00', { zone: 'America/Vancouver' }).toISO() }}",
        "calendar": {
          "__rl": true,
          "mode": "list",
          "value": "user@example.com",
          "cachedResultName": "Bookings"
        },
        "operation": "getAll"
      },
      "typeVersion": 1.3
    },
    {
      "id": "6815d0a0-b874-4f19-a46f-db8a40738685",
      "name": "Delete Calendar Event",
      "type": "n8n-nodes-base.googleCalendar",
      "position": [
        3440,
        3584
      ],
      "parameters": {
        "eventId": "={{ $json.id }}",
        "options": {},
        "calendar": {
          "__rl": true,
          "mode": "list",
          "value": "user@example.com",
          "cachedResultName": "Bookings"
        },
        "operation": "delete"
      },
      "typeVersion": 1.3
    },
    {
      "id": "7510b91c-2635-47e4-b8bc-1191c5f47e01",
      "name": "Manage Appointment Webhook",
      "type": "n8n-nodes-base.webhook",
      "position": [
        2544,
        3488
      ],
      "parameters": {
        "path": "manage-appointment",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 2.1
    },
    {
      "id": "98b1dd20-fe60-476c-bbc3-518dd66aaf5a",
      "name": "Extract Appointment Management Data",
      "type": "n8n-nodes-base.set",
      "position": [
        2768,
        3488
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "88789c10-5026-48cf-8272-2779a9a9b99b",
              "name": "action",
              "type": "string",
              "value": "={{ $json.body.message.toolCallList[0].function.arguments.action }}"
            },
            {
              "id": "08604b29-ed0b-415a-9d65-1ade89e5d9c6",
              "name": "email",
              "type": "string",
              "value": "={{ $json.body.message.toolCallList[0].function.arguments.email }}"
            },
            {
              "id": "811ec028-3f20-442a-a228-938ef8b441ee",
              "name": "originalTime",
              "type": "string",
              "value": "={{ $json.body.message.toolCallList[0].function.arguments.appointmentTime }}"
            },
            {
              "id": "460cd369-65d2-4190-aeec-6b4a0fdd8693",
              "name": "originalDate",
              "type": "string",
              "value": "={{ $json.body.message.toolCallList[0].function.arguments.appointmentDate }}"
            },
            {
              "id": "d38bda2b-3d1c-407e-9277-2c4cd2613ee1",
              "name": "newDate",
              "type": "string",
              "value": "={{ $json.body.message.toolCallList[0].function.arguments.newDate }}"
            },
            {
              "id": "8fe3ce2d-3f5b-4ce3-9623-7afd0a7d9ca7",
              "name": "newTime",
              "type": "string",
              "value": "={{ $json.body.message.toolCallList[0].function.arguments.newTime }}"
            },
            {
              "id": "dda2ab30-6425-4296-8e48-0989828fd623",
              "name": "phoneNumber",
              "type": "string",
              "value": "={{ $json.body.message.toolCallList[0].function.arguments.phoneNumber }}"
            },
            {
              "id": "86b4031c-6b5d-4a59-84e3-f814d24123eb",
              "name": "name",
              "type": "string",
              "value": "={{ $json.body.message.toolCalls[0].function.arguments.name }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "44b36764-836d-4997-8a09-27667a51c482",
      "name": "Find Appointment to Update",
      "type": "n8n-nodes-base.googleCalendar",
      "position": [
        3216,
        3312
      ],
      "parameters": {
        "options": {
          "timeZone": {
            "__rl": true,
            "mode": "list",
            "value": "America/Vancouver",
            "cachedResultName": "America/Vancouver"
          }
        },
        "timeMax": "={{ DateTime.fromISO($json.originalDate + 'T' + $json.originalTime + ':00', { zone: 'America/Vancouver' }).plus({ minutes: 1 }).toISO() }}",
        "timeMin": "={{ DateTime.fromISO($json.originalDate + 'T' + $json.originalTime + ':00', { zone: 'America/Vancouver' }).toISO() }}",
        "calendar": {
          "__rl": true,
          "mode": "list",
          "value": "user@example.com",
          "cachedResultName": "Bookings"
        },
        "operation": "getAll"
      },
      "typeVersion": 1.3
    },
    {
      "id": "1d4de4a8-3e05-48fb-a598-be2b55f1010e",
      "name": "Respond - Reschedule Success",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        3888,
        3312
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 1.5
    },
    {
      "id": "c3cea1dd-973e-47c9-887f-6f9580e8522b",
      "name": "Respond - Cancellation Success",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        3888,
        3584
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 1.5
    },
    {
      "id": "df6347f3-3fb2-44d0-9f21-79a67d233d3a",
      "name": "Format Cancellation Success Response",
      "type": "n8n-nodes-base.code",
      "position": [
        3664,
        3584
      ],
      "parameters": {
        "jsCode": "const toolCallId = $('Manage Appointment Webhook').first().json.body.message.toolCallList[0].id;\n\nreturn {\n  results: [\n    {\n      toolCallId: toolCallId,\n      result: \"Appointment cancelled successfully\"\n    }\n  ]\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "02444c73-db46-4bf3-a1d7-13952e602a8c",
      "name": "Format Reschedule Success Response",
      "type": "n8n-nodes-base.code",
      "position": [
        3664,
        3312
      ],
      "parameters": {
        "jsCode": "const toolCallId = $('Manage Appointment Webhook').first().json.body.message.toolCallList[0].id;\n\nreturn {\n  results: [\n    {\n      toolCallId: toolCallId,\n      result: \"Appointment rescheduled successfully\"\n    }\n  ]\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "f49778a4-e30d-4b02-8ac8-dfc6497d7fe9",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2016,
        3376
      ],
      "parameters": {
        "color": 3,
        "width": 300,
        "height": 260,
        "content": "## Tool 3: Manage Appointments\n\nHandles cancellations and rescheduling. Routes by action type, finds existing appointment, then updates or deletes calendar event."
      },
      "typeVersion": 1
    },
    {
      "id": "9501e517-7cb6-47b6-a219-21778d2d3f88",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        5024,
        1520
      ],
      "parameters": {
        "color": 5,
        "width": 1232,
        "height": 1904,
        "content": "# VAPI VOICE ASSISTANT PROMPT\n\n### Booking Appointments\nBook the appointment using the **bookAppointment** tool.\nOnce the customer has confirmed their preferred date and time slot, collect **all required information** before booking.\n\n**Required information:**\n- Name (use the name you already collected)\n- Phone number (use the number you already collected)\n- Service type (identified during service identification)\n- Date: YYYY-MM-DD format (e.g., 2026-01-15)\n- Time: HH:MM 24-hour format (e.g., 14:30 for 2:30 PM)\n- Duration: Default to 30 minutes unless otherwise specified\n- Notes: Any special requests or additional information\n\n**CRITICAL RULES for booking:**\n- Only book after the customer explicitly confirms the date and time.\n- Always use **checkAvailability** first to verify the slot is available.\n- Never book a time slot that shows as **UNAVAILABLE**.\n- If **checkAvailability** returns **UNAVAILABLE**, offer the suggested alternative times.\n- After booking, confirm all details with the customer: date, time, service type, and address.\n\n\n### Managing Existing Appointments\nWhen a customer wants to cancel or reschedule an existing appointment, use the following process.\n\n#### Locating the Appointment\nFirst, collect the information needed to find their appointment:\n\n#### Determining the Action\nAsk if they want to cancel or reschedule their appointment:  \n\n#### For Cancellations\n1. Confirm the action\n2. Use the **manageAppointment** tool with `action=\"cancel\"` to cancel the existing appointment.\n\n#### For Rescheduling\n1. Confirm the change:from [date] at [time] to [newDate] at [newTime].\"\n2. Use the **manageAppointment** tool with `action=\"update\"` to update the existing appointment.\n\n### Using the manageAppointment Tool\nWhen calling the **manageAppointment** tool, you need:\n- `name`: Customer's name\n- `phoneNumber`: Customer's phone number\n- `appointmentDate`: Current appointment date in YYYY-MM-DD format\n- `appointmentTime`: Current appointment time in HH:MM format (24-hour)\n- `action`: \"cancel\" or \"update\"\n- `newDate`: New appointment date in YYYY-MM-DD format (rescheduling only)\n- `newTime`: New appointment time in HH:MM format (24-hour) (rescheduling only)\n\n**CRITICAL:** Always verify the appointment details with the customer before using the tool. Never cancel or reschedule without explicit confirmation.\n\n### Confirmation and Wrap-up\n1. Summarize details:\n\n## Response Guidelines\n- Use explicit confirmation for addresses, dates, and times.\n- Ask only one question at a time.\n- For addresses, confirm:\n  - \"That's [house number] [street name], [city], [zip code]. Is that correct?\"\n\n### For Rescheduling Requests\n- Locate the existing appointment:\n- Verify details:\n- Offer alternatives:\n- Confirm change:\n\n**When you receive a response from the checkAvailability tool:**\n- Always read and use the exact information provided in the tool response.\n- If the response says the time slot is available, inform the customer it is available.\n- If the response says the time slot is not available, tell the customer it is not available and offer the alternative time slots provided.\n- Do not make assumptions or provide different information than what the tool returned.\n- The tool response contains real-time availability data\u2014trust it completely."
      },
      "typeVersion": 1
    },
    {
      "id": "389acfc3-2633-47c1-92d2-b715bce1d48b",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        240,
        1840
      ],
      "parameters": {
        "width": 976,
        "height": 864,
        "content": "# Book Appointments using Vapi and Google Calendar\nUse this workflow to book, cancel, or reschedule appointments using Vapi and Google Calendar\n\n## How it works\nThe Check Availability workflow checks if appointments exist on Google Calendar and returns a response to Vapi.\nThe Book Appointment workflow schedules an appointment on Google Calendar.\nThe Manage Appointments workflow updates or cancels appointments based on the user\u2019s request.\n\n## Key Features:\n1. Ability to book, update, or cancel an appointment\n2. Validates required fields before booking\n3. Handles errors with helpful user messages\n4. Returns a Vapi-compatible response format\n\n\n## Set up steps\n### Prerequisites\n1. Vapi.ai account\n2. Google Calendar\n3. n8n instance - Self-hosted or cloud\n\n### Steps\n- Step 1: Configure Google Calendar Access\n- Step 2: Import and Configure Workflows\n- Step 3: Create Vapi Custom Tools\n- Step 4: Configure Vapi Assistant\n\n### Use Cases\nAppointment booking automation for service businesses\n\n\n\n\n"
      },
      "typeVersion": 1
    },
    {
      "id": "c8da4a29-3b68-46c1-b8b8-067c53412556",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3440,
        1168
      ],
      "parameters": {
        "color": 7,
        "width": 528,
        "height": 560,
        "content": "The top code node pulls all events, checks that time and data requested does not overlap with any events, returns time periods as unavailable and then suggests 3 alternate times closest to the requested time and returns a result in the required vapi response format. The bottom code node returns the result as available in the required vapi response format.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "c7ec83f6-17a4-4a70-baf9-1a774c16aeb5",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3376,
        2144
      ],
      "parameters": {
        "color": 7,
        "width": 496,
        "height": 592,
        "content": "The top and bottom code node format the results in the required vapi response format"
      },
      "typeVersion": 1
    },
    {
      "id": "4b9ddc95-13f0-4b08-b577-dcf84f46ead8",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3600,
        3168
      ],
      "parameters": {
        "color": 7,
        "width": 512,
        "height": 592,
        "content": "The code nodes format results in the required vapi response format for easy debugging on vapi terminal"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "availableInMCP": false,
    "executionOrder": "v1"
  },
  "versionId": "11f51e95-34ee-4c62-8ea3-8fd0b3f33b67",
  "connections": {
    "Extract Booking Data": {
      "main": [
        [
          {
            "node": "Validate Required Fields",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Route by Action Type": {
      "main": [
        [
          {
            "node": "Find Appointment to Update",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Find Appointment to Cancel",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Calendar Event": {
      "main": [
        [
          {
            "node": "Format Booking Success Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Delete Calendar Event": {
      "main": [
        [
          {
            "node": "Format Cancellation Success Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update Calendar Event": {
      "main": [
        [
          {
            "node": "Format Reschedule Success Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Book Appointment Webhook": {
      "main": [
        [
          {
            "node": "Extract Booking Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate Required Fields": {
      "main": [
        [
          {
            "node": "Create Calendar Event",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Format Validation Error Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Availability Webhook": {
      "main": [
        [
          {
            "node": "Extract Availability Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Find Appointment to Cancel": {
      "main": [
        [
          {
            "node": "Delete Calendar Event",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Find Appointment to Update": {
      "main": [
        [
          {
            "node": "Update Calendar Event",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Manage Appointment Webhook": {
      "main": [
        [
          {
            "node": "Extract Appointment Management Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Time Slot Availability": {
      "main": [
        [
          {
            "node": "Format Unavailable Time Response",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Format Available Time Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Availability Request": {
      "main": [
        [
          {
            "node": "Get Calendar Events for Date",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Calendar Events for Date": {
      "main": [
        [
          {
            "node": "Check Time Slot Availability",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Available Time Response": {
      "main": [
        [
          {
            "node": "Respond - Time Available",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Booking Success Response": {
      "main": [
        [
          {
            "node": "Respond - Booking Success",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Unavailable Time Response": {
      "main": [
        [
          {
            "node": "Respond - Time Unavailable",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Validation Error Response": {
      "main": [
        [
          {
            "node": "Respond - Validation Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Reschedule Success Response": {
      "main": [
        [
          {
            "node": "Respond - Reschedule Success",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Appointment Management Data": {
      "main": [
        [
          {
            "node": "Route by Action Type",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Cancellation Success Response": {
      "main": [
        [
          {
            "node": "Respond - Cancellation Success",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}