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 →
{
"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
}
]
]
}
}
}
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 →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
github code Try yourself
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
Rodopi Dent - Public Available Slots (Calendar). Uses googleCalendar. Webhook trigger; 9 nodes.
Rodopi Dent - Calendar Update Event. Uses googleCalendar. Webhook trigger; 5 nodes.