AutomationFlowsAI & RAG › ElevenLabs Voice Agent Books Google Calendar Appointments

ElevenLabs Voice Agent Books Google Calendar Appointments

Original n8n title: Automate Medical Appointments with Elevenlabs Voice Agent & Google Calendar

ByAnatoly @savantageai on n8n.io

Transform your appointment scheduling with this production-ready workflow that connects ElevenLabs voice AI to your Google Calendar. Patients can call and book appointments naturally through conversation, while the system handles real-time availability checking, calendar…

Webhook trigger★★★★☆ complexity21 nodesGoogle CalendarGmail
AI & RAG Trigger: Webhook Nodes: 21 Complexity: ★★★★☆ Added:

This workflow corresponds to n8n.io template #9366 — we link there as the canonical source.

This workflow follows the Gmail → Google Calendar recipe pattern — see all workflows that pair these two integrations.

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
{
  "id": "KYehtlopDpAO49xz",
  "name": "ElevenLabs Voice Agent: Medical Clinic Appointment Booking",
  "tags": [],
  "nodes": [
    {
      "id": "3e4fd8c7-20a2-4e12-b628-e320a3d18fd8",
      "name": "Sticky Note - Webhook",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -3936,
        -272
      ],
      "parameters": {
        "color": 5,
        "width": 510,
        "height": 481,
        "content": "## \ud83d\udce5 WEBHOOK ENTRY POINT\n\n**Receives data from ElevenLabs:**\n\n**Body parameters:**\n- `fullName` - Patient name (only for booking)\n- `email` - Contact email (only for booking)\n- `phone` - Phone number (only for booking)\n- `date` - Format: YYYY-MM-DD\n- `time` - Format: HH:MM\n- `appointmentType` - Service type\n- `location` - Clinic location\n\n**Query parameter:**\n- `request` - \"check availability\" or blank for booking\n\n**\u2699\ufe0f Setup Required:**\nChange the webhook path to your own unique ID for security."
      },
      "typeVersion": 1
    },
    {
      "id": "09316f64-01a4-41a6-a5d7-fe188596d08c",
      "name": "Sticky Note - Edit Fields",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -3424,
        -272
      ],
      "parameters": {
        "color": 5,
        "width": 310,
        "height": 481,
        "content": "## \u270f\ufe0f DATA PREPARATION\n\n**Extracts and formats:**\n- Combines `date` + `time` into single datetime field\n- Prepares all fields for calendar and email nodes\n\n**Output format:**\nYYYY-MM-DD HH:MM\n(e.g., \"2025-10-08 15:00\")"
      },
      "typeVersion": 1
    },
    {
      "id": "e50d4da0-11c4-482b-bf94-3cbde2d83b7e",
      "name": "Sticky Note - Routing",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -3120,
        -272
      ],
      "parameters": {
        "color": 6,
        "width": 406,
        "height": 485,
        "content": "## \ud83d\udd00 ROUTING LOGIC\n\n**Determines action type:**\n\n**Path 1 (True):** Availability Check\n- Triggered when `fullName` OR `email` is missing\n- Only date/time/type provided\n- Returns available/unavailable status\n\n**Path 2 (False):** Book Appointment\n- All fields present (name, email, phone, etc.)\n- Creates calendar event\n- Sends confirmation email"
      },
      "typeVersion": 1
    },
    {
      "id": "3d3dc615-146c-4602-af78-ed7484856b90",
      "name": "Sticky Note - Booking",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2704,
        304
      ],
      "parameters": {
        "color": 3,
        "width": 255,
        "height": 274,
        "content": "## \u2705 BOOKING PATH\n\n**Creates Google Calendar Event**\n"
      },
      "typeVersion": 1
    },
    {
      "id": "c16c3b25-795a-4ec0-89f5-b11545ea16b5",
      "name": "Sticky Note - Email",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2448,
        304
      ],
      "parameters": {
        "color": 3,
        "width": 357,
        "height": 274,
        "content": "## \ud83d\udce7 EMAIL CONFIRMATION\n\n**Sends an ppointment confirmation via Gmail**\n\n\n"
      },
      "typeVersion": 1
    },
    {
      "id": "06216198-2728-4f2f-bf04-c981c25185a9",
      "name": "Sticky Note - Availability",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2720,
        -272
      ],
      "parameters": {
        "color": 7,
        "width": 453,
        "height": 482,
        "content": "## \ud83d\udd0d AVAILABILITY CHECK PATH\n\n**Check if specific time slot is free**\n\u2192 If available: Return success\n\u2192 If unavailable: Find alternative slots\n\n**\u2699\ufe0f Setup Required:**\nConnect Google Calendar and select your calendar"
      },
      "typeVersion": 1
    },
    {
      "id": "56ffb539-4752-4a58-ac64-245a9ff11138",
      "name": "Sticky Note - Quick Start",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -4336,
        -272
      ],
      "parameters": {
        "color": 4,
        "width": 405,
        "height": 900,
        "content": "## \ud83d\ude80 QUICK START GUIDE\n\n**1. Configure Credentials:**\n   - [ ] Google Calendar OAuth2\n   - [ ] Gmail OAuth2\n\n**2. Select Calendar:**\n   - Open all Google Calendar nodes\n   - Choose your booking calendar from dropdown\n\n**3. Customize Settings:**\n   - [ ] Update webhook path (security)\n   - [ ] Adjust business hours in \"Sort Available Slots\" code\n   - [ ] Modify timezone \n   - [ ] Update clinic details in email template\n\n**4. Test Workflow:**\n   - Use pinned test data\n   - Test both availability check and booking\n\n**5. Connect to ElevenLabs:**\n   - Copy production webhook URL\n   - Configure agent functions\n   - Map parameters correctly\n\n**6. Go Live:**\n   - Activate workflow\n   - Test with real voice calls"
      },
      "typeVersion": 1
    },
    {
      "id": "600186bf-6b72-4daf-9e0a-00a8cb247b36",
      "name": "Sticky Note - Troubleshooting",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -4336,
        352
      ],
      "parameters": {
        "color": 4,
        "width": 409,
        "height": 524,
        "content": "## \ud83d\udc1b TROUBLESHOOTING\n\n**Workflow not triggering:**\n- Check webhook URL is correct\n- Verify workflow is active\n- Check ElevenLabs function configuration\n\n**Calendar not syncing:**\n- Reconnect Google Calendar credential\n- Verify calendar ID is correct\n- Check OAuth permissions\n\n**Email not sending:**\n- Verify Gmail credential\n- Check spam folder\n- Confirm email address format\n\n**Timezone issues:**\n- Adjust timezone in your Calendar\n\n**Slots showing as unavailable:**\n- Check calendar has no conflicts"
      },
      "typeVersion": 1
    },
    {
      "id": "c5f730f6-8930-4efe-8773-5b93c6fd8e4e",
      "name": "Webhook1",
      "type": "n8n-nodes-base.webhook",
      "position": [
        -3696,
        240
      ],
      "parameters": {
        "path": "2be0d61e-a2a0-48de-867e-4892849296b4",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 2.1
    },
    {
      "id": "aba4a40b-94f7-4f54-b1e1-87459cf96dca",
      "name": "Available?1",
      "type": "n8n-nodes-base.if",
      "position": [
        -2432,
        48
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "1d0c317e-477a-437e-918a-a761e9069115",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $json.available }}",
              "rightValue": "true"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "3cbee3f2-dd7a-4a09-84d7-0957aaac68b7",
      "name": "Check Availability Again",
      "type": "n8n-nodes-base.googleCalendar",
      "position": [
        -2176,
        128
      ],
      "parameters": {
        "options": {},
        "timeMax": "={{ DateTime.fromISO($('Webhook1').item.json.body.date).plus({ days: 30 }).toISODate() }}",
        "timeMin": "={{ $('Webhook1').item.json.body.date }} ",
        "calendar": {
          "__rl": true,
          "mode": "id",
          "value": "="
        },
        "operation": "getAll",
        "returnAll": true
      },
      "credentials": {
        "googleCalendarOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "7cafaa98-68fb-4408-b37b-ba1e7edb436f",
      "name": "Set Up Variables",
      "type": "n8n-nodes-base.set",
      "position": [
        -3328,
        240
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "4478c006-d158-4854-bf88-5e0fa1c8b936",
              "name": "fullName",
              "type": "string",
              "value": "={{ $json.body.fullName }}"
            },
            {
              "id": "b7bb33fc-426d-40ce-91b9-93844a269bfd",
              "name": "email",
              "type": "string",
              "value": "={{ $json.body.email }}"
            },
            {
              "id": "66f90820-45ae-4a98-ad17-f5ee72042680",
              "name": "phone",
              "type": "string",
              "value": "={{ $json.body.phone }}"
            },
            {
              "id": "0992fb3c-06e9-49f2-aba1-49fcee27172a",
              "name": "location",
              "type": "string",
              "value": "={{ $json.body.location }}"
            },
            {
              "id": "a969fb63-fee2-4e07-96d4-1f1468d8ed43",
              "name": "appointmentType",
              "type": "string",
              "value": "={{ $json.body.appointmentType }}"
            },
            {
              "id": "4444d238-b4ae-4bba-823a-aec64b4ea9de",
              "name": "date",
              "type": "string",
              "value": "={{ $json.body.date }} {{ $json.body.time }}"
            },
            {
              "id": "a4ef9aa5-11ca-4a35-b1f6-f0a20c64026f",
              "name": "appointmentType",
              "type": "string",
              "value": "={{ $json.body.appointmentType }}"
            },
            {
              "id": "6fff6dc5-0389-41c9-abfe-eaa4ddf7a2d8",
              "name": "location",
              "type": "string",
              "value": "={{ $json.body.location }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "7e5cd44f-3b1e-4f6e-ab9f-59d9efd3ce84",
      "name": "Book Appointment",
      "type": "n8n-nodes-base.googleCalendar",
      "position": [
        -2640,
        416
      ],
      "parameters": {
        "end": "={{ DateTime.fromFormat($('Set Up Variables').item.json.date, 'yyyy-MM-dd HH:mm').plus({ minutes: 30 }).toISO({ suppressMilliseconds: true }) }}",
        "start": "={{ DateTime.fromFormat($('Set Up Variables').item.json.date, 'yyyy-MM-dd HH:mm').toISO({ suppressMilliseconds: true }) }}",
        "calendar": {
          "__rl": true,
          "mode": "id",
          "value": "="
        },
        "additionalFields": {
          "summary": "={{ $json.fullName }}, {{ $('Webhook1').item.json.body.appointmentType }}",
          "location": "={{ $('Webhook1').item.json.body.location }} ",
          "attendees": [
            "={{ $('Webhook1').item.json.body.email }}"
          ],
          "description": "={{ $json.fullName }}\n\n{{ $('Webhook1').item.json.query.request }}"
        },
        "useDefaultReminders": false
      },
      "credentials": {
        "googleCalendarOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "0bbc9ee0-6813-4688-9638-dc4e79162b1a",
      "name": "Get availability in a calendar",
      "type": "n8n-nodes-base.googleCalendar",
      "position": [
        -2640,
        48
      ],
      "parameters": {
        "options": {},
        "timeMax": "={{ DateTime.fromFormat($('Set Up Variables').item.json.date, 'yyyy-MM-dd HH:mm').plus({ minutes: 30 }).toISO({ suppressMilliseconds: true }) }}",
        "timeMin": "={{ DateTime.fromFormat($('Set Up Variables').item.json.date, 'yyyy-MM-dd HH:mm').toISO({ suppressMilliseconds: true }) }}",
        "calendar": {
          "__rl": true,
          "mode": "id",
          "value": "="
        },
        "resource": "calendar"
      },
      "credentials": {
        "googleCalendarOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "5c6da70a-f54f-4088-8ed1-4bf32c91cd86",
      "name": "Send a message",
      "type": "n8n-nodes-base.gmail",
      "position": [
        -2432,
        416
      ],
      "parameters": {
        "message": "=Hello {{ $('Set Up Variables').item.json.fullName.split(' ')[0] }}! Your appointment for {{ $('Set Up Variables').item.json.appointmentType }} is confirmed \u2013 Evergreen Clinic on {{\n  (() => {\n    const dateStr = $('Set Up Variables').item.json.date; // e.g., \"2025-10-07 15:00\"\n    const [datePart, timePart] = dateStr.split(' ');\n    const [year, month, day] = datePart.split('-').map(Number);\n\n    // Month names\n    const months = [\n      'January', 'February', 'March', 'April', 'May', 'June',\n      'July', 'August', 'September', 'October', 'November', 'December'\n    ];\n\n    // Ordinal function\n    function ordinal(n) {\n      if (n > 3 && n < 21) return 'th';\n      switch (n % 10) {\n        case 1: return 'st';\n        case 2: return 'nd';\n        case 3: return 'rd';\n        default: return 'th';\n      }\n    }\n\n    return `${months[month - 1]} the ${day}${ordinal(day)} at ${timePart}`;\n  })()\n}}<br><br> We're looking forward to seeing you. Here are the key details:<br><br> <b>Provider:</b> Dr. Sava (Aesthetic Medicine)<br> <b>Location:</b> Evergreen Clinic, Hasenb\u00fchlstrasse 36<br> <b>Duration:</b> ~20\u201330 minutes<br> <b>Contact:</b> 027 545 15 82 \u00b7 evergreen@clinic.com<br><br> <b>Before you come (quick prep):</b> <ul>   <li>Arrive 5\u201310 minutes early for check-in and a brief consent review.</li>   <li>If possible, avoid alcohol, ibuprofen/aspirin, and intense workouts for 24 hours before (helps reduce bruising).</li>   <li>Come with a clean face (no heavy makeup on treatment areas).</li>   <li>Tell us in advance if you're pregnant/breastfeeding, have a skin infection, or take blood thinners\u2014your safety first.</li> </ul> See you on {{    DateTime.fromISO($('Set Up Variables').item.json.date)     .toFormat(\"MMMM d 'at' h:mm a\")     .replace(/(\\d{1,2})(?= at)/, (d) => d + (['th','st','nd','rd'][((d%100-20)%10)||d%10]||'th')) }}!<br><br> Warm regards,<br> Evergreen Clinic<br> Rosenweg 24 3931, Z\u00fcrich \u00b7 027 545 15 82",
        "options": {
          "appendAttribution": false
        },
        "subject": "={{ $('Set Up Variables').item.json.appointmentType }} at Evergreen Clinic on {{\n  (() => {\n    const dateStr = $('Set Up Variables').item.json.date; // e.g., \"2025-10-07 15:00\"\n    const [datePart, timePart] = dateStr.split(' ');\n    const [year, month, day] = datePart.split('-').map(Number);\n\n    // Month names\n    const months = [\n      'January', 'February', 'March', 'April', 'May', 'June',\n      'July', 'August', 'September', 'October', 'November', 'December'\n    ];\n\n    // Ordinal function\n    function ordinal(n) {\n      if (n > 3 && n < 21) return 'th';\n      switch (n % 10) {\n        case 1: return 'st';\n        case 2: return 'nd';\n        case 3: return 'rd';\n        default: return 'th';\n      }\n    }\n\n    return `${months[month - 1]} the ${day}${ordinal(day)} at ${timePart}`;\n  })()\n}}"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "cda91dc7-8c9a-4ef5-ae69-38e894dd11ad",
      "name": "Confirm Booking ",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        -2224,
        416
      ],
      "parameters": {
        "options": {},
        "respondWith": "text",
        "responseBody": "=Thank you. The appointment is scheduled on {{\n  (() => {\n    const dateStr = $('Set Up Variables').item.json.date; // e.g., \"2025-10-07 15:00\"\n    const [datePart, timePart] = dateStr.split(' ');\n    const [year, month, day] = datePart.split('-').map(Number);\n\n    // Month names\n    const months = [\n      'January', 'February', 'March', 'April', 'May', 'June',\n      'July', 'August', 'September', 'October', 'November', 'December'\n    ];\n\n    // Ordinal function\n    function ordinal(n) {\n      if (n > 3 && n < 21) return 'th';\n      switch (n % 10) {\n        case 1: return 'st';\n        case 2: return 'nd';\n        case 3: return 'rd';\n        default: return 'th';\n      }\n    }\n\n    return `${months[month - 1]} the ${day}${ordinal(day)} at ${timePart}`;\n  })()\n}}"
      },
      "typeVersion": 1.4
    },
    {
      "id": "ce9d6170-3b3f-486b-8667-0f0ec8e879dc",
      "name": "Confirm Available Times",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        -2176,
        -32
      ],
      "parameters": {
        "options": {},
        "respondWith": "text",
        "responseBody": "=Thank you for waiting! This time is available"
      },
      "typeVersion": 1.4
    },
    {
      "id": "ade07557-b674-4bb3-9415-cf0c98d6d095",
      "name": "Sort Available Slots",
      "type": "n8n-nodes-base.code",
      "position": [
        -1968,
        128
      ],
      "parameters": {
        "jsCode": "// Collect all booked intervals from input items (previous node)\nconst bookedIntervals = [];\nfor (const item of $input.all()) {\n  bookedIntervals.push({\n    start: new Date(item.json.start.dateTime),\n    end: new Date(item.json.end.dateTime)\n  });\n}\n\n// Settings\nconst days = 30;\nconst workStartHour = 7;\nconst workEndHour = 17; // exclusive, so last slot starts at 16:30\nconst slotDurationMinutes = 30;\n\n// Helper to format date to ISO8601 with +02:00\nfunction toISOWithFixedOffset(date) {\n  const pad = n => String(n).padStart(2, '0');\n  return date.getFullYear() + '-' +\n    pad(date.getMonth() + 1) + '-' +\n    pad(date.getDate()) + 'T' +\n    pad(date.getHours()) + ':' +\n    pad(date.getMinutes()) + ':00+02:00';\n}\n\n// Helper to check if two intervals overlap\nfunction isOverlapping(slotStart, slotEnd, bookedStart, bookedEnd) {\n  return slotStart < bookedEnd && slotEnd > bookedStart;\n}\n\n// Get current time in +02:00 (Europe/Zurich) timezone\nconst now = new Date();\nconst nowUtc = now.getTime() + (now.getTimezoneOffset() * 60000);\nconst nowPlus2 = new Date(nowUtc + 2 * 60 * 60000);\n\nnowPlus2.setSeconds(0, 0); // ignore seconds/milliseconds\n\nlet available = [];\n\nfor (let d = 0; d < days; d++) {\n  for (let h = workStartHour; h < workEndHour; h++) {\n    for (let m = 0; m < 60; m += slotDurationMinutes) {\n      let slotStart = new Date(nowPlus2);\n      slotStart.setDate(slotStart.getDate() + d);\n      slotStart.setHours(h, m, 0, 0);\n\n      let slotEnd = new Date(slotStart);\n      slotEnd.setMinutes(slotEnd.getMinutes() + slotDurationMinutes);\n\n      // Exclude slots in the past\n      if (slotStart < nowPlus2) continue;\n\n      // Check for overlap with any booked interval\n      let overlaps = bookedIntervals.some(b =>\n        isOverlapping(slotStart, slotEnd, b.start, b.end)\n      );\n\n      if (!overlaps) {\n        available.push({ available: toISOWithFixedOffset(slotStart) });\n      }\n    }\n  }\n}\n\nreturn available.map(a => ({ json: a }));"
      },
      "typeVersion": 2
    },
    {
      "id": "7527abf9-5e6d-44d2-8250-02db3eee78fa",
      "name": "Aggregate",
      "type": "n8n-nodes-base.aggregate",
      "position": [
        -1760,
        128
      ],
      "parameters": {
        "options": {},
        "fieldsToAggregate": {
          "fieldToAggregate": [
            {
              "fieldToAggregate": "available"
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "729f6a0f-ad3e-4f4d-b5f4-d9695ecd1e03",
      "name": "Confirm The Time's Unavailable",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        -1552,
        128
      ],
      "parameters": {
        "options": {},
        "respondWith": "text",
        "responseBody": "=Sorry, this time slot is unavailable right now. You may have another time in your mind?"
      },
      "typeVersion": 1.4
    },
    {
      "id": "990395b5-8563-445a-bf87-9159427498ee",
      "name": "If",
      "type": "n8n-nodes-base.if",
      "position": [
        -2976,
        240
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "or",
          "conditions": [
            {
              "id": "f586362a-6775-4692-b290-bc43bfdeb361",
              "operator": {
                "type": "string",
                "operation": "notExists",
                "singleValue": true
              },
              "leftValue": "={{ $json.fullName }}",
              "rightValue": "check availability"
            },
            {
              "id": "1b48aa09-72f2-4d20-8cac-e5e0adb80f28",
              "operator": {
                "type": "string",
                "operation": "notExists",
                "singleValue": true
              },
              "leftValue": "={{ $json.email }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "eb4efa41-4863-44b4-a691-9f1553d2c4a9",
  "connections": {
    "If": {
      "main": [
        [
          {
            "node": "Get availability in a calendar",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Book Appointment",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Webhook1": {
      "main": [
        [
          {
            "node": "Set Up Variables",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Aggregate": {
      "main": [
        [
          {
            "node": "Confirm The Time's Unavailable",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Available?1": {
      "main": [
        [
          {
            "node": "Confirm Available Times",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Check Availability Again",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send a message": {
      "main": [
        [
          {
            "node": "Confirm Booking ",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Book Appointment": {
      "main": [
        [
          {
            "node": "Send a message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Up Variables": {
      "main": [
        [
          {
            "node": "If",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sort Available Slots": {
      "main": [
        [
          {
            "node": "Aggregate",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Availability Again": {
      "main": [
        [
          {
            "node": "Sort Available Slots",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get availability in a calendar": {
      "main": [
        [
          {
            "node": "Available?1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}

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

Transform your appointment scheduling with this production-ready workflow that connects ElevenLabs voice AI to your Google Calendar. Patients can call and book appointments naturally through conversation, while the system handles real-time availability checking, calendar…

Source: https://n8n.io/workflows/9366/ — 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

Zoho CRM - Smart Meeting Scheduler. Uses zohoCrm, googleCalendar, httpRequest, agent. Webhook trigger; 23 nodes.

Zoho Crm, Google Calendar, HTTP Request +3
AI & RAG

🧾 An intelligent automation system that turns Google Meet recordings into structured meeting notes — integrating Fireflies.ai, OpenAI GPT-4.1-mini, Notion, Slack, Google Drive, and Gmail via n8n.

Google Drive, OpenAI Chat, Output Parser Structured +8
AI & RAG

This workflow is an AI-powered Salon Booking Assistant that automates hair, beauty, and spa appointment scheduling through a Webhook trigger. It interacts with the user in natural conversation, collec

OpenAI Chat, Output Parser Autofixing, Output Parser Structured +4
AI & RAG

This workflow was created by Varritech Technologies, a cutting-edge software agency that helps founders and operators go from idea to production 5× faster using AI. Based in New York City, we speciali

Postgres, Twilio, Gmail +5
AI & RAG

This workflow is for anyone who wants to offer a fun, automated palm reading experience through LINE — Japan's dominant messaging app with over 90 million active users. It suits indie developers, well

HTTP Request, Chain Llm, Google Gemini Chat +2