This workflow corresponds to n8n.io template #15117 — 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 →
{
"id": "BkhJJTM9qs10v0BL",
"name": "Voice-Based Appointment & Booking Agent",
"tags": [],
"nodes": [
{
"id": "ff5ee207-8ee5-4893-a476-4f7b0e33cab4",
"name": "Sticky Note - Overview",
"type": "n8n-nodes-base.stickyNote",
"position": [
0,
0
],
"parameters": {
"width": 1000,
"height": 1020,
"content": "## Voice-Based Appointment & Booking Agent\n\nThis workflow handles incoming voice calls or audio messages, transcribes them using Whisper (OpenAI) or ElevenLabs, extracts booking intent and preferred time slots using AI, checks availability on Cal.com or Google Calendar, books the appointment automatically, and sends a confirmation via WhatsApp, SMS, or Email.\n\n### Who's it for\n\u2022 Service businesses (clinics, salons, consultants, coaches)\n\u2022 Solopreneurs who want a 24/7 voice booking assistant\n\u2022 Teams that receive appointment requests via phone/WhatsApp voice notes\n\n### How it works / What it does\n1. Receives incoming voice call or audio file via Webhook\n2. Transcribes audio using OpenAI Whisper or ElevenLabs STT\n3. AI extracts booking intent: name, date, time, service type\n4. Validates and normalizes extracted date/time\n5. Checks real-time availability on Cal.com or Google Calendar\n6. If slot is free \u2192 books the appointment automatically\n7. If slot is taken \u2192 AI suggests next 3 available slots\n8. Generates a voice or text confirmation response\n9. Sends confirmation via WhatsApp, SMS, or Email\n10. Logs all bookings to Google Sheets\n\n### How to set up\n1. Import this workflow into n8n\n2. Configure credentials: OpenAI (Whisper), Cal.com API, Google Calendar, Twilio, SendGrid\n3. Update your business name, services list, and timezone\n4. Point your phone/WhatsApp webhook to the n8n webhook URL\n5. Activate workflow\n\n### Requirements\n\u2022 OpenAI API key (Whisper STT + GPT for intent extraction)\n\u2022 Cal.com API key OR Google Calendar OAuth\n\u2022 Twilio account (WhatsApp / SMS confirmation)\n\u2022 SendGrid (email confirmation, optional)\n\u2022 Google Sheets (booking log)\n\n### How to customize\n\u2022 Swap Whisper with ElevenLabs STT in the transcription node\n\u2022 Add service-specific booking rules in the AI Extraction node\n\u2022 Change confirmation channel in the Set Preferences node\n\u2022 Extend slot suggestion logic in the JS Availability node"
},
"typeVersion": 1
},
{
"id": "f6d3aedb-3ef7-406c-82fd-8dab41a01feb",
"name": "Sticky Note - Section 1",
"type": "n8n-nodes-base.stickyNote",
"position": [
1152,
32
],
"parameters": {
"color": 4,
"width": 812,
"height": 752,
"content": "## 1. Voice Intake & Transcription"
},
"typeVersion": 1
},
{
"id": "6539d432-43d7-451b-bc4a-169d434d8afc",
"name": "Sticky Note - Section 2",
"type": "n8n-nodes-base.stickyNote",
"position": [
2000,
16
],
"parameters": {
"color": 3,
"width": 964,
"height": 768,
"content": "## 2. Intent Extraction & Slot Parsing"
},
"typeVersion": 1
},
{
"id": "fc2007e3-7b28-4b65-80af-daade118cfee",
"name": "Sticky Note - Section 3",
"type": "n8n-nodes-base.stickyNote",
"position": [
2992,
0
],
"parameters": {
"color": 4,
"width": 1004,
"height": 872,
"content": "## 3. Availability Check & Booking"
},
"typeVersion": 1
},
{
"id": "5b3b0d4b-a913-48c8-bbf0-9ef73a54a693",
"name": "Sticky Note - Section 4",
"type": "n8n-nodes-base.stickyNote",
"position": [
4064,
-80
],
"parameters": {
"color": 3,
"width": 1604,
"height": 992,
"content": "## 4. Confirmation & Logging"
},
"typeVersion": 1
},
{
"id": "36140728-a30b-43f2-9cc2-7f4ac49176d1",
"name": "Webhook - Incoming Voice or Audio",
"type": "n8n-nodes-base.webhook",
"position": [
1312,
400
],
"parameters": {
"path": "voice-booking-inbound",
"options": {},
"httpMethod": "POST",
"responseMode": "responseNode"
},
"typeVersion": 1.1
},
{
"id": "63a96f18-5dcf-4a06-b043-204824b088b4",
"name": "Set Business Config",
"type": "n8n-nodes-base.set",
"position": [
1760,
400
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"name": "businessName",
"type": "string",
"value": "Bright Smile Dental Clinic"
},
{
"name": "businessTimezone",
"type": "string",
"value": "America/New_York"
},
{
"name": "workingHoursStart",
"type": "string",
"value": "09:00"
},
{
"name": "workingHoursEnd",
"type": "string",
"value": "18:00"
},
{
"name": "slotDurationMinutes",
"type": "number",
"value": 30
},
{
"name": "availableServices",
"type": "string",
"value": "General Checkup, Teeth Cleaning, Whitening, Root Canal, Consultation"
},
{
"name": "confirmationChannel",
"type": "string",
"value": "whatsapp"
},
{
"name": "calComApiKey",
"type": "string",
"value": "YOUR_CALCOM_API_KEY"
},
{
"name": "calComEventTypeId",
"type": "number",
"value": 12345
},
{
"name": "openAiApiKey",
"type": "string",
"value": "YOUR_OPENAI_API_KEY"
},
{
"name": "twilioFrom",
"type": "string",
"value": "whatsapp:+1234567890"
},
{
"name": "senderEmail",
"type": "string",
"value": "user@example.com"
},
{
"name": "audioUrl",
"type": "string",
"value": "={{ $json.body?.audioUrl || $json.audioUrl || $json.MediaUrl0 || '' }}"
},
{
"name": "callerPhone",
"type": "string",
"value": "={{ $json.body?.from || $json.From || $json.callerPhone || '' }}"
},
{
"name": "todayDate",
"type": "string",
"value": "={{ new Date().toISOString().split('T')[0] }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "2524e197-ee9e-4815-9967-756c8dfdbfbf",
"name": "Whisper STT - Transcribe Audio",
"type": "n8n-nodes-base.httpRequest",
"position": [
2048,
288
],
"parameters": {
"url": "https://api.openai.com/v1/audio/transcriptions",
"method": "POST",
"options": {},
"sendBody": true,
"contentType": "multipart-form-data",
"sendHeaders": true,
"bodyParameters": {
"parameters": [
{
"name": "model",
"value": "whisper-1"
},
{
"name": "url",
"value": "={{ $json.audioUrl }}"
},
{
"name": "language",
"value": "en"
},
{
"name": "response_format",
"value": "json"
}
]
},
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "=Bearer {{ $json.openAiApiKey }}"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "302ed6ac-ec70-463b-9525-6b84a9c9268c",
"name": "ElevenLabs STT - Alt Transcription",
"type": "n8n-nodes-base.httpRequest",
"position": [
2048,
512
],
"parameters": {
"url": "https://api.elevenlabs.io/v1/speech-to-text",
"method": "POST",
"options": {},
"sendBody": true,
"contentType": "multipart-form-data",
"sendHeaders": true,
"bodyParameters": {
"parameters": [
{
"name": "audio",
"value": "={{ $('Set Business Config').item.json.audioUrl }}"
},
{
"name": "model_id",
"value": "scribe_v1"
}
]
},
"headerParameters": {
"parameters": [
{
"name": "xi-api-key",
"value": "YOUR_ELEVENLABS_API_KEY"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "5727d6cb-e6cd-4bd9-bb80-bfb4d153f218",
"name": "JS - Merge Transcription Result",
"type": "n8n-nodes-base.code",
"position": [
2240,
384
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "// Merge transcription from either Whisper or ElevenLabs\nconst whisperResult = $('Whisper STT - Transcribe Audio').first()?.json;\nconst elevenLabsResult = $('ElevenLabs STT - Alt Transcription').first()?.json;\nconst config = $('Set Business Config').first().json;\n\nconst transcribedText =\n whisperResult?.text ||\n elevenLabsResult?.text ||\n elevenLabsResult?.transcription ||\n 'Could not transcribe audio.';\n\nreturn {\n json: {\n ...config,\n transcribedText,\n transcriptionSource: whisperResult?.text ? 'whisper' : 'elevenlabs',\n transcriptionTimestamp: new Date().toISOString()\n }\n};"
},
"typeVersion": 2
},
{
"id": "95732158-8e3d-4296-bdbd-1426b10f21a0",
"name": "AI - Extract Booking Intent",
"type": "n8n-nodes-base.httpRequest",
"position": [
2512,
384
],
"parameters": {
"url": "https://api.openai.com/v1/chat/completions",
"method": "POST",
"options": {},
"jsonBody": "={\n \"model\": \"gpt-4.1-mini\",\n \"temperature\": 0.2,\n \"response_format\": {\"type\": \"json_object\"},\n \"messages\": [\n {\n \"role\": \"system\",\n \"content\": \"You are a booking assistant for {{ $json.businessName }}. Extract appointment details from the transcribed voice message. Available services: {{ $json.availableServices }}. Today's date is {{ $json.todayDate }}. Timezone: {{ $json.businessTimezone }}.\\n\\nReturn ONLY a JSON object with these fields:\\n- callerName (string, or 'Unknown' if not mentioned)\\n- callerPhone (string, use '{{ $json.callerPhone }}' if not mentioned in text)\\n- requestedService (string, match closest from available services or 'General Consultation')\\n- requestedDate (string, ISO format YYYY-MM-DD, infer from relative terms like 'tomorrow', 'next Monday')\\n- requestedTime (string, 24h format HH:MM, e.g. '14:30')\\n- requestedTimeRaw (string, as spoken, e.g. '2:30 PM')\\n- notes (string, any extra info or special requests)\\n- intentConfidence (string: 'high', 'medium', or 'low')\\n- isBookingRequest (boolean)\"\n },\n {\n \"role\": \"user\",\n \"content\": \"Transcribed voice message: {{ $json.transcribedText }}\"\n }\n ]\n}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "=Bearer {{ $json.openAiApiKey }}"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "fd0b4a0e-8e15-4979-9964-d9d9c8eb5f44",
"name": "JS - Parse Extracted Intent",
"type": "n8n-nodes-base.code",
"position": [
2752,
384
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const item = $input.item.json;\nconst config = $('JS - Merge Transcription Result').first().json;\n\n// Parse AI response\nlet extracted = {};\ntry {\n const rawContent = item.choices?.[0]?.message?.content || '{}';\n extracted = JSON.parse(rawContent);\n} catch (e) {\n extracted = {\n callerName: 'Unknown',\n callerPhone: config.callerPhone,\n requestedService: 'General Consultation',\n requestedDate: config.todayDate,\n requestedTime: '10:00',\n requestedTimeRaw: '10:00 AM',\n notes: '',\n intentConfidence: 'low',\n isBookingRequest: true\n };\n}\n\n// Build ISO datetime for the requested slot\nconst slotDateTime = `${extracted.requestedDate}T${extracted.requestedTime}:00`;\nconst slotEndTime = new Date(new Date(slotDateTime).getTime() + config.slotDurationMinutes * 60000).toISOString();\n\nreturn {\n json: {\n ...config,\n ...extracted,\n callerPhone: extracted.callerPhone || config.callerPhone,\n slotStart: slotDateTime,\n slotEnd: slotEndTime,\n transcribedText: config.transcribedText\n }\n};"
},
"typeVersion": 2
},
{
"id": "7535d986-1f48-4c25-8f71-7f65a10d6b72",
"name": "Filter - Is Booking Request?",
"type": "n8n-nodes-base.filter",
"position": [
3040,
384
],
"parameters": {
"options": {},
"conditions": {
"conditions": [
{
"operator": {
"type": "boolean",
"operation": "true"
},
"leftValue": "={{ $json.isBookingRequest }}"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "b4745ac1-e4dd-4fba-bbc1-341fc4c8a531",
"name": "Cal.com - Check Available Slots",
"type": "n8n-nodes-base.httpRequest",
"position": [
3296,
304
],
"parameters": {
"url": "=https://api.cal.com/v1/slots?apiKey={{ $json.calComApiKey }}&eventTypeId={{ $json.calComEventTypeId }}&startTime={{ $json.requestedDate }}T00:00:00Z&endTime={{ $json.requestedDate }}T23:59:59Z&timeZone={{ $json.businessTimezone }}",
"options": {}
},
"typeVersion": 4.2
},
{
"id": "f8e9ac3f-809b-4da8-84e9-81972fed6ca9",
"name": "Google Calendar - Check Busy Times",
"type": "n8n-nodes-base.httpRequest",
"position": [
3296,
496
],
"parameters": {
"url": "=https://www.googleapis.com/calendar/v3/calendars/primary/events?timeMin={{ $json.requestedDate }}T00:00:00Z&timeMax={{ $json.requestedDate }}T23:59:59Z&singleEvents=true&orderBy=startTime",
"options": {},
"authentication": "oAuth2"
},
"typeVersion": 4.2
},
{
"id": "babc7646-3f81-4f96-959e-fd3c45c34da5",
"name": "JS - Evaluate Slot Availability",
"type": "n8n-nodes-base.code",
"position": [
3536,
368
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const item = $input.item.json;\nconst config = $('JS - Parse Extracted Intent').first().json;\n\n// Get Cal.com slots\nconst calSlots = $('Cal.com - Check Available Slots').first()?.json;\nconst googleEvents = $('Google Calendar - Check Busy Times').first()?.json;\n\n// Extract available times from Cal.com response\nconst calAvailableSlots = [];\nif (calSlots?.slots) {\n const dateKey = Object.keys(calSlots.slots)[0];\n const slots = calSlots.slots[dateKey] || [];\n slots.forEach(s => calAvailableSlots.push(s.time || s));\n}\n\n// Extract busy times from Google Calendar\nconst busyTimes = (googleEvents?.items || []).map(e => ({\n start: e.start?.dateTime || e.start?.date,\n end: e.end?.dateTime || e.end?.date\n}));\n\n// Check if requested slot is available\nconst requestedStart = new Date(config.slotStart);\nconst requestedEnd = new Date(config.slotEnd);\n\nconst isConflict = busyTimes.some(b => {\n const bStart = new Date(b.start);\n const bEnd = new Date(b.end);\n return requestedStart < bEnd && requestedEnd > bStart;\n});\n\n// Find next 3 available Cal.com slots if conflict\nconst suggestedSlots = calAvailableSlots\n .filter(s => new Date(s) > new Date())\n .slice(0, 3);\n\nconst isAvailable = !isConflict && (\n calAvailableSlots.length === 0 ||\n calAvailableSlots.some(s => s.startsWith(config.requestedTime.substring(0, 5)) || Math.abs(new Date(s) - requestedStart) < 900000)\n);\n\nreturn {\n json: {\n ...config,\n isAvailable,\n isConflict,\n suggestedSlots,\n availableSlotCount: calAvailableSlots.length,\n busySlotCount: busyTimes.length\n }\n};"
},
"typeVersion": 2
},
{
"id": "c57ffe79-2988-447e-a3b5-23688b23bb26",
"name": "Route - Slot Available or Conflict?",
"type": "n8n-nodes-base.if",
"position": [
4096,
368
],
"parameters": {
"options": {},
"conditions": {
"conditions": [
{
"operator": {
"type": "boolean",
"operation": "true"
},
"leftValue": "={{ $json.isAvailable }}"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "cd7c9eb6-7a5e-4646-94ce-dbc623ec7030",
"name": "Cal.com - Book the Appointment",
"type": "n8n-nodes-base.httpRequest",
"position": [
4400,
240
],
"parameters": {
"url": "=https://api.cal.com/v1/bookings?apiKey={{ $json.calComApiKey }}",
"method": "POST",
"options": {},
"jsonBody": "={\n \"eventTypeId\": {{ $json.calComEventTypeId }},\n \"start\": \"{{ $json.slotStart }}\",\n \"end\": \"{{ $json.slotEnd }}\",\n \"timeZone\": \"{{ $json.businessTimezone }}\",\n \"responses\": {\n \"name\": \"{{ $json.callerName }}\",\n \"email\": \"{{ $json.callerEmail || 'noreply@placeholder.com' }}\",\n \"phone\": \"{{ $json.callerPhone }}\",\n \"notes\": \"{{ $json.notes }} | Service: {{ $json.requestedService }} | Booked via Voice Agent\"\n },\n \"metadata\": {\n \"source\": \"voice-booking-agent\",\n \"transcription\": \"{{ $json.transcribedText }}\"\n }\n}",
"sendBody": true,
"specifyBody": "json"
},
"typeVersion": 4.2
},
{
"id": "07925b5f-bf73-468b-b698-40dd8b07f23a",
"name": "AI - Generate Conflict Response",
"type": "n8n-nodes-base.httpRequest",
"position": [
4400,
512
],
"parameters": {
"url": "https://api.openai.com/v1/chat/completions",
"method": "POST",
"options": {},
"jsonBody": "={\n \"model\": \"gpt-4.1-mini\",\n \"temperature\": 0.4,\n \"messages\": [\n {\n \"role\": \"system\",\n \"content\": \"You are a polite booking assistant for {{ $('Set Business Config').item.json.businessName }}. The requested time slot is NOT available. Suggest alternative times warmly and apologetically. Keep under 100 words.\"\n },\n {\n \"role\": \"user\",\n \"content\": \"Requested: {{ $json.requestedTimeRaw }} on {{ $json.requestedDate }} for {{ $json.requestedService }}. Available alternatives: {{ $json.suggestedSlots.join(', ') || 'Please call to reschedule.' }}\"\n }\n ]\n}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "=Bearer {{ $('Set Business Config').item.json.openAiApiKey }}"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "c863ce7c-3ca0-4026-bd32-e13828b2c50c",
"name": "JS - Build Confirmation Message",
"type": "n8n-nodes-base.code",
"position": [
4704,
368
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const item = $input.item.json;\nconst config = $('JS - Evaluate Slot Availability').first().json;\n\n// Determine if we came from booking success or conflict path\nconst bookingData = $('Cal.com - Book the Appointment').first()?.json;\nconst conflictAI = $('AI - Generate Conflict Response').first()?.json;\n\nlet confirmationText = '';\nlet bookingStatus = '';\nlet bookingRef = '';\n\nif (bookingData?.uid || bookingData?.id) {\n // Successful booking\n bookingRef = bookingData.uid || bookingData.id || 'N/A';\n bookingStatus = 'CONFIRMED';\n confirmationText = `\u2705 Appointment Confirmed!\n\nHi ${config.callerName},\n\nYour appointment has been booked at ${config.businessName}.\n\n\ud83d\udccb Details:\n\u2022 Service: ${config.requestedService}\n\u2022 Date: ${config.requestedDate}\n\u2022 Time: ${config.requestedTimeRaw}\n\u2022 Ref: ${bookingRef}\n\nPlease arrive 5 minutes early. Reply CANCEL to cancel.`;\n} else {\n // Conflict - use AI suggested response\n bookingStatus = 'CONFLICT';\n const aiMsg = conflictAI?.choices?.[0]?.message?.content || `Sorry, ${config.requestedTimeRaw} on ${config.requestedDate} is not available. Please call us to reschedule.`;\n confirmationText = `\u26a0\ufe0f Slot Unavailable\\n\\n${aiMsg}`;\n}\n\nreturn {\n json: {\n ...config,\n confirmationText,\n bookingStatus,\n bookingRef,\n bookedAt: new Date().toISOString()\n }\n};"
},
"typeVersion": 2
},
{
"id": "b56aa7dd-f659-44f5-81fd-d5e257af0186",
"name": "Route - WhatsApp or Email?",
"type": "n8n-nodes-base.if",
"position": [
4944,
368
],
"parameters": {
"options": {},
"conditions": {
"conditions": [
{
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.confirmationChannel }}",
"rightValue": "whatsapp"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "8a46e937-74e2-4dfc-bb11-483cfc5c1684",
"name": "Send WhatsApp Confirmation",
"type": "n8n-nodes-base.httpRequest",
"position": [
5184,
240
],
"parameters": {
"url": "=https://api.twilio.com/2010-04-01/Accounts/YOUR_TWILIO_ACCOUNT_SID/Messages.json",
"method": "POST",
"options": {},
"sendBody": true,
"specifyBody": "=formUrlEncoded",
"authentication": "genericCredentialType",
"bodyParameters": {
"parameters": [
{}
]
},
"genericAuthType": "httpBasicAuth"
},
"credentials": {
"httpBasicAuth": {
"name": "<your credential>"
}
},
"typeVersion": 4.2
},
{
"id": "3584d253-8699-4bf9-bab5-5b46bde738c8",
"name": "Send Email Confirmation",
"type": "n8n-nodes-base.httpRequest",
"position": [
5184,
512
],
"parameters": {
"url": "https://api.sendgrid.com/v3/mail/send",
"method": "POST",
"options": {},
"jsonBody": "={\n \"personalizations\": [{\n \"to\": [{\"email\": \"{{ $json.callerEmail || $json.userEmail }}\", \"name\": \"{{ $json.callerName }}\"}]\n }],\n \"from\": {\"email\": \"{{ $json.senderEmail }}\", \"name\": \"{{ $json.businessName }}\"},\n \"subject\": \"{{ $json.bookingStatus === 'CONFIRMED' ? '\u2705 Appointment Confirmed' : '\u26a0\ufe0f Booking Update' }} \u2014 {{ $json.businessName }}\",\n \"content\": [{\n \"type\": \"text/plain\",\n \"value\": \"{{ $json.confirmationText }}\"\n }]\n}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "Bearer YOUR_TOKEN_HERE"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "94cfdf4a-20df-4a41-8988-97af19e1d88b",
"name": "Webhook Response",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
5424,
368
],
"parameters": {
"options": {},
"respondWith": "json",
"responseBody": "={\n \"status\": \"{{ $json.bookingStatus }}\",\n \"message\": \"{{ $json.bookingStatus === 'CONFIRMED' ? 'Appointment booked successfully.' : 'Slot unavailable, alternatives sent.' }}\",\n \"bookingRef\": \"{{ $json.bookingRef }}\",\n \"callerName\": \"{{ $json.callerName }}\",\n \"requestedService\": \"{{ $json.requestedService }}\",\n \"slotStart\": \"{{ $json.slotStart }}\"\n}"
},
"typeVersion": 1.1
},
{
"id": "10f9a723-8a94-43af-84a7-4430640d78d5",
"name": "Log Booking to Google Sheets",
"type": "n8n-nodes-base.httpRequest",
"position": [
5424,
560
],
"parameters": {
"url": "https://sheets.googleapis.com/v4/spreadsheets/YOUR_GOOGLE_SHEET_ID/values/Bookings!A1:append?valueInputOption=USER_ENTERED",
"method": "POST",
"options": {},
"jsonBody": "={\n \"values\": [[\n \"{{ $json.bookedAt }}\",\n \"{{ $json.callerName }}\",\n \"{{ $json.callerPhone }}\",\n \"{{ $json.requestedService }}\",\n \"{{ $json.requestedDate }}\",\n \"{{ $json.requestedTimeRaw }}\",\n \"{{ $json.bookingStatus }}\",\n \"{{ $json.bookingRef }}\",\n \"{{ $json.transcribedText }}\",\n \"{{ $json.intentConfidence }}\",\n \"{{ $json.transcriptionSource }}\"\n ]]\n}",
"sendBody": true,
"specifyBody": "json",
"authentication": "oAuth2"
},
"typeVersion": 4.2
},
{
"id": "3628ada2-fe98-4f85-bcc9-cd0a10ea4a1c",
"name": "Wait For Result",
"type": "n8n-nodes-base.wait",
"position": [
3744,
368
],
"parameters": {},
"typeVersion": 1.1
},
{
"id": "fae08b8f-e6b0-4b9c-887a-883d572483f9",
"name": "Wait For Data",
"type": "n8n-nodes-base.wait",
"position": [
1520,
400
],
"parameters": {
"amount": 15
},
"typeVersion": 1.1
}
],
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "77a20fc5-b948-4faf-945c-2e9e8c55a5b3",
"connections": {
"Wait For Data": {
"main": [
[
{
"node": "Set Business Config",
"type": "main",
"index": 0
}
]
]
},
"Wait For Result": {
"main": [
[
{
"node": "Route - Slot Available or Conflict?",
"type": "main",
"index": 0
}
]
]
},
"Set Business Config": {
"main": [
[
{
"node": "Whisper STT - Transcribe Audio",
"type": "main",
"index": 0
},
{
"node": "ElevenLabs STT - Alt Transcription",
"type": "main",
"index": 0
}
]
]
},
"Send Email Confirmation": {
"main": [
[
{
"node": "Webhook Response",
"type": "main",
"index": 0
},
{
"node": "Log Booking to Google Sheets",
"type": "main",
"index": 0
}
]
]
},
"Route - WhatsApp or Email?": {
"main": [
[
{
"node": "Send WhatsApp Confirmation",
"type": "main",
"index": 0
}
],
[
{
"node": "Send Email Confirmation",
"type": "main",
"index": 0
}
]
]
},
"Send WhatsApp Confirmation": {
"main": [
[
{
"node": "Webhook Response",
"type": "main",
"index": 0
},
{
"node": "Log Booking to Google Sheets",
"type": "main",
"index": 0
}
]
]
},
"AI - Extract Booking Intent": {
"main": [
[
{
"node": "JS - Parse Extracted Intent",
"type": "main",
"index": 0
}
]
]
},
"JS - Parse Extracted Intent": {
"main": [
[
{
"node": "Filter - Is Booking Request?",
"type": "main",
"index": 0
}
]
]
},
"Filter - Is Booking Request?": {
"main": [
[
{
"node": "Cal.com - Check Available Slots",
"type": "main",
"index": 0
},
{
"node": "Google Calendar - Check Busy Times",
"type": "main",
"index": 0
}
]
]
},
"Cal.com - Book the Appointment": {
"main": [
[
{
"node": "JS - Build Confirmation Message",
"type": "main",
"index": 0
}
]
]
},
"Whisper STT - Transcribe Audio": {
"main": [
[
{
"node": "JS - Merge Transcription Result",
"type": "main",
"index": 0
}
]
]
},
"AI - Generate Conflict Response": {
"main": [
[
{
"node": "JS - Build Confirmation Message",
"type": "main",
"index": 0
}
]
]
},
"Cal.com - Check Available Slots": {
"main": [
[
{
"node": "JS - Evaluate Slot Availability",
"type": "main",
"index": 0
}
]
]
},
"JS - Build Confirmation Message": {
"main": [
[
{
"node": "Route - WhatsApp or Email?",
"type": "main",
"index": 0
}
]
]
},
"JS - Evaluate Slot Availability": {
"main": [
[
{
"node": "Wait For Result",
"type": "main",
"index": 0
}
]
]
},
"JS - Merge Transcription Result": {
"main": [
[
{
"node": "AI - Extract Booking Intent",
"type": "main",
"index": 0
}
]
]
},
"Webhook - Incoming Voice or Audio": {
"main": [
[
{
"node": "Wait For Data",
"type": "main",
"index": 0
}
]
]
},
"ElevenLabs STT - Alt Transcription": {
"main": [
[
{
"node": "JS - Merge Transcription Result",
"type": "main",
"index": 0
}
]
]
},
"Google Calendar - Check Busy Times": {
"main": [
[
{
"node": "JS - Evaluate Slot Availability",
"type": "main",
"index": 0
}
]
]
},
"Route - Slot Available or Conflict?": {
"main": [
[
{
"node": "Cal.com - Book the Appointment",
"type": "main",
"index": 0
}
],
[
{
"node": "AI - Generate Conflict Response",
"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.
httpBasicAuth
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow handles incoming voice calls or audio messages, transcribes them using Whisper (OpenAI) or ElevenLabs, extracts booking intent and preferred time slots using AI, checks availability on Cal.com or Google Calendar, books the appointment automatically, and sends a…
Source: https://n8n.io/workflows/15117/ — 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.
Jigsaw API key for image processing, I use this as a gatekeeper/second pair of eyes. LINK to their website https://jigsawstack.com/ SECOND A postgress DATABASE (I use Supabase) LlamaCloud for the pars
Whatsapp Multi Agent System optimized copy 2.0. Uses airtable, httpRequest, errorTrigger. Webhook trigger; 44 nodes.
Invoice Agent. Uses httpRequest, emailSend. Webhook trigger; 29 nodes.
Reputation Engine — SEO QA Agent. Uses httpRequest. Webhook trigger; 28 nodes.
Local AI Agent (HTTP-based). Uses telegram, httpRequest. Webhook trigger; 24 nodes.