AutomationFlowsGeneral › Book and Check Phone Appointments with Vapi or Retell and Google Calendar

Book and Check Phone Appointments with Vapi or Retell and Google Calendar

ByManik Chandra Dhor @youthfireit on n8n.io

This workflow exposes a webhook for a Vapi or Retell voice agent to check Google Calendar availability or book an appointment, then returns a short, spoken-friendly reply for the agent to read to the caller. Receives a POST request from your Vapi/Retell voice agent via a webhook…

Webhook trigger★★★★☆ complexity17 nodesGoogle Calendar
General Trigger: Webhook Nodes: 17 Complexity: ★★★★☆ Added:

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

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": "AI voice receptionist that checks availability and books Google Calendar appointments for Vapi and Retell",
  "nodes": [
    {
      "id": "e8904aad-8f5f-482c-a12d-7c8892549f5e",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -608,
        -320
      ],
      "parameters": {
        "width": 480,
        "height": 896,
        "content": "## AI voice receptionist that checks availability and books Google Calendar appointments for Vapi and Retell\n\n### How it works\n\nThis workflow acts as an AI voice receptionist endpoint for Vapi or Retell. It receives a webhook request, normalizes business settings and caller request fields, then routes the request to either check Google Calendar availability, book an appointment, or return a fallback message. The selected branch builds a result and the workflow responds synchronously to the voice agent.\n\n### Setup steps\n\n- Configure the webhook URL in Vapi or Retell so voice-agent tool calls are sent to the n8n webhook trigger.\n- Connect Google Calendar credentials to the calendar nodes.\n- Edit the \u201cYour Settings (EDIT ME)\u201d node with the business name, target calendar ID, and appointment slot duration.\n- Ensure the voice agent sends the expected fields for action, name, phone, and requested start time.\n\n### Customization\n\nAdjust the switch actions, slot duration, calendar ID, and reply text in the Set nodes to match the business\u2019s booking rules and voice-agent prompts."
      },
      "typeVersion": 1
    },
    {
      "id": "26a25884-ff9e-4010-8770-46845db62b0d",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -48,
        -160
      ],
      "parameters": {
        "color": 7,
        "width": 720,
        "height": 320,
        "content": "## Receive and prepare request\n\nThe webhook receives requests from the voice agent, applies editable business/calendar settings, and extracts the action, caller details, and requested start time for downstream routing."
      },
      "typeVersion": 1
    },
    {
      "id": "765ac4ca-dd63-402e-9ea3-e518affd1944",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        736,
        -224
      ],
      "parameters": {
        "color": 7,
        "width": 240,
        "height": 416,
        "content": "## Route requested action\n\nThe switch node evaluates the extracted action and sends the request to the availability check, booking, or fallback response path."
      },
      "typeVersion": 1
    },
    {
      "id": "e0fe17bb-a4ce-43bc-b50a-673c03782db4",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1040,
        -320
      ],
      "parameters": {
        "color": 7,
        "width": 448,
        "height": 320,
        "content": "## Check calendar availability\n\nThis upper branch queries Google Calendar for the requested time slot and builds a voice-agent-friendly availability result."
      },
      "typeVersion": 1
    },
    {
      "id": "af66a6fd-096e-466c-b2cf-22d1221e0e53",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1040,
        32
      ],
      "parameters": {
        "color": 7,
        "width": 448,
        "height": 320,
        "content": "## Create calendar booking\n\nThis middle branch creates the appointment in Google Calendar and formats the confirmation message returned to the caller."
      },
      "typeVersion": 1
    },
    {
      "id": "56dde076-c55d-42c1-875f-112839ed3eb4",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        768,
        224
      ],
      "parameters": {
        "color": 7,
        "width": 240,
        "height": 416,
        "content": "## Handle unsupported requests\n\nThis lower branch builds a fallback result when the requested action is missing or does not match the supported check/book operations."
      },
      "typeVersion": 1
    },
    {
      "id": "0dca2f11-e02f-419d-88dd-d8e8ad74051b",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1600,
        -208
      ],
      "parameters": {
        "color": 7,
        "width": 240,
        "height": 368,
        "content": "## Return voice response\n\nThis final output node sends the prepared result from any branch back to the Vapi or Retell webhook caller."
      },
      "typeVersion": 1
    },
    {
      "id": "v1",
      "name": "When Voice Request Received",
      "type": "n8n-nodes-base.webhook",
      "position": [
        0,
        0
      ],
      "parameters": {
        "path": "voice-receptionist",
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 2.1
    },
    {
      "id": "v2",
      "name": "Set Business Variables",
      "type": "n8n-nodes-base.set",
      "position": [
        260,
        0
      ],
      "parameters": {
        "mode": "manual",
        "assignments": {
          "assignments": [
            {
              "id": "s1",
              "name": "businessName",
              "type": "string",
              "value": "Bright Smile Dental"
            },
            {
              "id": "s2",
              "name": "calendarId",
              "type": "string",
              "value": "primary"
            },
            {
              "id": "s3",
              "name": "slotMinutes",
              "type": "number",
              "value": 30
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "v3",
      "name": "Prepare Request Data",
      "type": "n8n-nodes-base.set",
      "position": [
        520,
        0
      ],
      "parameters": {
        "mode": "manual",
        "assignments": {
          "assignments": [
            {
              "id": "e1",
              "name": "action",
              "type": "string",
              "value": "={{ $json.body.action }}"
            },
            {
              "id": "e2",
              "name": "name",
              "type": "string",
              "value": "={{ $json.body.name }}"
            },
            {
              "id": "e3",
              "name": "phone",
              "type": "string",
              "value": "={{ $json.body.phone }}"
            },
            {
              "id": "e4",
              "name": "start",
              "type": "string",
              "value": "={{ $json.body.start }}"
            },
            {
              "id": "e5",
              "name": "end",
              "type": "string",
              "value": "={{ DateTime.fromISO($json.body.start).plus({ minutes: Number($('Set Business Variables').item.json.slotMinutes) }).toISO() }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "v4",
      "name": "Route by Request Type",
      "type": "n8n-nodes-base.switch",
      "position": [
        780,
        0
      ],
      "parameters": {
        "mode": "rules",
        "rules": {
          "values": [
            {
              "outputKey": "check",
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": false,
                  "typeValidation": "loose"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.action }}",
                    "rightValue": "check"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "book",
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": false,
                  "typeValidation": "loose"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.action }}",
                    "rightValue": "book"
                  }
                ]
              },
              "renameOutput": true
            }
          ]
        },
        "options": {
          "fallbackOutput": "extra",
          "renameFallbackOutput": "other"
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "v5",
      "name": "Verify Calendar Availability",
      "type": "n8n-nodes-base.googleCalendar",
      "position": [
        1080,
        -160
      ],
      "parameters": {
        "options": {
          "outputFormat": "availability"
        },
        "timeMax": "={{ $json.end }}",
        "timeMin": "={{ $json.start }}",
        "calendar": {
          "__rl": true,
          "mode": "id",
          "value": "primary"
        },
        "resource": "calendar",
        "operation": "availability"
      },
      "typeVersion": 1.3
    },
    {
      "id": "v6",
      "name": "Prepare Availability Response",
      "type": "n8n-nodes-base.set",
      "position": [
        1340,
        -160
      ],
      "parameters": {
        "mode": "manual",
        "assignments": {
          "assignments": [
            {
              "id": "a1",
              "name": "result",
              "type": "string",
              "value": "={{ $json.available ? ('Good news \u2014 ' + $('Prepare Request Data').item.json.start + ' is available. Would you like me to book it?') : 'Sorry, that time is not available. Could you try another time?' }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "v7",
      "name": "Schedule in Google Calendar",
      "type": "n8n-nodes-base.googleCalendar",
      "position": [
        1088,
        192
      ],
      "parameters": {
        "end": "={{ $json.end }}",
        "start": "={{ $json.start }}",
        "calendar": {
          "__rl": true,
          "mode": "id",
          "value": "primary"
        },
        "resource": "event",
        "operation": "create",
        "additionalFields": {
          "summary": "=Appointment with {{ $json.name }}",
          "description": "=Booked by AI voice receptionist. Caller phone: {{ $json.phone }}"
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "v8",
      "name": "Prepare Booking Confirmation",
      "type": "n8n-nodes-base.set",
      "position": [
        1344,
        192
      ],
      "parameters": {
        "mode": "manual",
        "assignments": {
          "assignments": [
            {
              "id": "b1",
              "name": "result",
              "type": "string",
              "value": "=All set, {{ $('Prepare Request Data').item.json.name }}! Your appointment is booked for {{ $('Prepare Request Data').item.json.start }}. We look forward to seeing you."
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "v9",
      "name": "Prepare Fallback Response",
      "type": "n8n-nodes-base.set",
      "position": [
        816,
        496
      ],
      "parameters": {
        "mode": "manual",
        "assignments": {
          "assignments": [
            {
              "id": "f1",
              "name": "result",
              "type": "string",
              "value": "Sorry, I didn't catch whether you wanted to check availability or book. Could you say that again?"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "v10",
      "name": "Send Response to Agent",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        1640,
        0
      ],
      "parameters": {
        "respondWith": "firstIncomingItem"
      },
      "typeVersion": 1.5
    }
  ],
  "connections": {
    "Prepare Request Data": {
      "main": [
        [
          {
            "node": "Route by Request Type",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Route by Request Type": {
      "main": [
        [
          {
            "node": "Verify Calendar Availability",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Schedule in Google Calendar",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Prepare Fallback Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Business Variables": {
      "main": [
        [
          {
            "node": "Prepare Request Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Fallback Response": {
      "main": [
        [
          {
            "node": "Send Response to Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule in Google Calendar": {
      "main": [
        [
          {
            "node": "Prepare Booking Confirmation",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When Voice Request Received": {
      "main": [
        [
          {
            "node": "Set Business Variables",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Booking Confirmation": {
      "main": [
        [
          {
            "node": "Send Response to Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Verify Calendar Availability": {
      "main": [
        [
          {
            "node": "Prepare Availability Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Availability Response": {
      "main": [
        [
          {
            "node": "Send Response to Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Pro

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

About this workflow

This workflow exposes a webhook for a Vapi or Retell voice agent to check Google Calendar availability or book an appointment, then returns a short, spoken-friendly reply for the agent to read to the caller. Receives a POST request from your Vapi/Retell voice agent via a webhook…

Source: https://n8n.io/workflows/17234/ — original creator credit. Request a take-down →

More General workflows → · Browse all categories →

Related workflows

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

General

github code Try yourself

Google Calendar
General

Connect a Vapi AI voice agent to Google Calendar to capture contact details and auto-book appointments. The agent asks for name, address, service type, and a preferred time. The workflow checks availa

Google Calendar
General

n8nCal · /events. Uses googleCalendar. Webhook trigger; 9 nodes.

Google Calendar
General

Rodopi Dent - Public Available Slots (Calendar). Uses googleCalendar. Webhook trigger; 9 nodes.

Google Calendar
General

Rodopi Dent - Calendar Update Event. Uses googleCalendar. Webhook trigger; 5 nodes.

Google Calendar