AutomationFlowsAI & RAG › AI Voice Agent for Booking with Gemini

AI Voice Agent for Booking with Gemini

Original n8n title: Voice Agent Pro: Agentic Booking with Tts and Robust Error Handling

Voice Agent Pro: Agentic Booking with TTS and Robust Error Handling. Uses httpRequest, chainLlm, lmChatGoogleGemini. Webhook trigger; 18 nodes.

Webhook trigger★★★★☆ complexityAI-powered18 nodesHTTP RequestChain LlmGoogle Gemini Chat
AI & RAG Trigger: Webhook Nodes: 18 Complexity: ★★★★☆ AI nodes: yes Added:

This workflow follows the Chainllm → HTTP Request 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
{
  "name": "Voice Agent Pro: Agentic Booking with TTS and Robust Error Handling",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "voice-agent-pro",
        "responseMode": "responseNode",
        "options": {}
      },
      "id": "2c484d2e-8645-44de-98d5-0fbd4ccee388",
      "name": "Webhook Trigger1",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 1.1,
      "position": [
        -688,
        -2304
      ]
    },
    {
      "parameters": {
        "jsCode": "// Context Builder Function Node\n// Builds comprehensive context for LLM based on session state and conversation history\n\nconst webhookData = $('Webhook Trigger1').item.json;\nconst sttData = $input.item.json;\n\n// Get form fields from webhook\nconst sessionState = JSON.parse(webhookData.sessionState || '{}');\nconst sessionId = webhookData.sessionId || '';\nconst authToken = webhookData.authToken || '';\n\n// Get transcript from STT output\nconst transcript = sttData.text || sttData.transcript || sttData.data?.text || '';\n\n// Determine current step and build context accordingly\nconst currentStep = sessionState.currentStep || 'org_search';\nconst selectedOrgId = sessionState.selectedOrganizationId;\nconst selectedDeptId = sessionState.selectedDepartmentId;\nconst selectedProviderId = sessionState.selectedProviderId;\nconst userId = sessionState.userId;\nconst conversationHistory = sessionState.conversationHistory || [];\n\n// Add current user message to history for the LLM prompt\nconst messages = [...conversationHistory, { role: 'user', content: transcript }];\n\n// Base system prompt\nlet systemPrompt = `You are a helpful, professional voice assistant for a booking system. Your role is to guide users through booking appointments with healthcare providers.\n\nAvailable steps in the booking flow:\n1. org_search - User is searching for an organization\n2. org_selected - User has selected an organization\n3. auth_required - User needs to authenticate\n4. dept_selection - User is selecting a department\n5. provider_selection - User is selecting a provider\n6. availability_query - User is querying provider availability\n7. booking - User is creating a booking\n\nCurrent session state:\n- Step: ${currentStep}\n- Selected Organization ID: ${selectedOrgId || 'None'}\n- Selected Department ID: ${selectedDeptId || 'None'}\n- Selected Provider ID: ${selectedProviderId || 'None'}\n- User ID: ${userId || 'Not authenticated'}\n\nYou must respond with a JSON object in this exact format:\n{\n  \"responseText\": \"Natural language response for the user\",\n  \"action\": {\n    \"type\": \"search_org\" | \"select_org\" | \"list_departments\" | \"list_providers\" | \"query_availability\" | \"create_booking\" | \"auth_required\" | \"continue_conversation\" | \"help\",\n    \"parameters\": {\n      \"orgId\": \"string (optional)\",\n      \"deptId\": \"string (optional)\",\n      \"providerId\": \"string (optional)\",\n      \"eventId\": \"string (optional)\",\n      \"query\": \"string (optional)\"\n    }\n  },\n  \"sessionUpdate\": {\n    \"currentStep\": \"string\",\n    \"selectedOrganizationId\": \"string (optional)\",\n    \"selectedDepartmentId\": \"string (optional)\",\n    \"selectedProviderId\": \"string (optional)\",\n    \"conversationHistory\": [ /* Array of {role: string, content: string} objects for the last 5 turns */ ]\n  }\n}`;\n\n// Add step-specific context (omitted for brevity, assume similar to original)\n\n// Build a single prompt string for the LLM Chain node\nconst conversationText = messages.map(m => m.role + ': ' + m.content).join('\\n');\nconst fullPrompt = systemPrompt + '\\n\\nConversation so far:\\n' + conversationText + '\\n\\nRespond with the JSON object as specified above.';\n\nreturn {\n  json: {\n    systemPrompt,\n    messages,\n    fullPrompt,\n    sessionId,\n    authToken,\n    sessionState: JSON.stringify(sessionState),\n    originalTranscript: transcript\n  }\n};"
      },
      "id": "a37d02b5-720e-4316-957a-cd6f34a0bd3f",
      "name": "Context Builder (History)1",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -208,
        -2272
      ]
    },
    {
      "parameters": {
        "jsCode": "// Parse LLM response and extract action, and update history\n// Basic LLM Chain (langchain) outputs { response: \"...\" } instead of OpenAI format\nconst llmResponse = $input.item.json.response || $input.item.json.text || '';\nlet parsedResponse;\n\ntry {\n  parsedResponse = JSON.parse(llmResponse);\n} catch (error) {\n  // Fallback if JSON parsing fails\n  parsedResponse = {\n    responseText: 'I apologize, I had a momentary lapse. Could you please repeat that?',\n    action: { type: 'continue_conversation' },\n    sessionUpdate: {}\n  };\n}\n\n// Extract data\nconst actionType = parsedResponse.action?.type || 'continue_conversation';\nconst actionParams = parsedResponse.action?.parameters || {};\nconst responseText = parsedResponse.responseText || 'I understand.';\nlet sessionUpdate = parsedResponse.sessionUpdate || {};\n\n// Get context from previous node\nconst context = $('Context Builder (History)1').item.json;\nconst transcript = context.originalTranscript;\nconst conversationHistory = JSON.parse(context.sessionState).conversationHistory || [];\n\n// Update conversation history for next turn\nconst newHistory = [...conversationHistory];\nnewHistory.push({ role: 'user', content: transcript });\nnewHistory.push({ role: 'assistant', content: responseText });\n\n// Keep only the last 5 turns (10 messages)\nsessionUpdate.conversationHistory = newHistory.slice(-10);\n\nreturn {\n  json: {\n    actionType,\n    actionParams,\n    responseText,\n    sessionUpdate,\n    sessionId: context.sessionId,\n    authToken: context.authToken,\n    sessionState: JSON.parse(context.sessionState),\n    transcript,\n    requiresApiCall: ['search_org', 'list_departments', 'list_providers', 'query_availability', 'create_booking'].includes(actionType)\n  }\n};"
      },
      "id": "e8043379-9c79-4ebe-93cd-c961155dcd46",
      "name": "Parse Action & Update History1",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        192,
        -2304
      ]
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict"
                },
                "conditions": [
                  {
                    "leftValue": "={{ $json.actionType }}",
                    "rightValue": "search_org",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict"
                },
                "conditions": [
                  {
                    "leftValue": "={{ $json.actionType }}",
                    "rightValue": "list_departments",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict"
                },
                "conditions": [
                  {
                    "leftValue": "={{ $json.actionType }}",
                    "rightValue": "list_providers",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict"
                },
                "conditions": [
                  {
                    "leftValue": "={{ $json.actionType }}",
                    "rightValue": "query_availability",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict"
                },
                "conditions": [
                  {
                    "leftValue": "={{ $json.actionType }}",
                    "rightValue": "create_booking",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict"
                },
                "conditions": [
                  {
                    "leftValue": "={{ $json.actionType }}",
                    "rightValue": "auth_required",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              }
            }
          ]
        },
        "options": {
          "fallbackOutput": "extra"
        }
      },
      "id": "a3792b97-eed8-4761-971c-f85e7b4fd410",
      "name": "Switch Action Type1",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3,
      "position": [
        416,
        -2304
      ]
    },
    {
      "parameters": {
        "url": "={{ $env.FASTIFY_API_URL }}/api/organizations/search",
        "options": {}
      },
      "id": "6e2f8896-6bd6-4d72-9e2e-a300e9d15b3b",
      "name": "API: Search Organizations1",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        640,
        -2464
      ]
    },
    {
      "parameters": {
        "url": "={{ $env.FASTIFY_API_URL }}/api/client/organizations/{{ $json.actionParams.orgId }}/departments",
        "authentication": "header",
        "options": {}
      },
      "id": "00a24cf2-a66f-41aa-b6f4-e4adb184f9cd",
      "name": "API: List Departments1",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        640,
        -2336
      ]
    },
    {
      "parameters": {
        "url": "={{ $env.FASTIFY_API_URL }}/api/client/departments/{{ $json.actionParams.deptId }}/providers",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {}
          ]
        },
        "options": {}
      },
      "id": "9e325ae4-190f-4cc9-8175-14324461c4ed",
      "name": "API: List Providers1",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        640,
        -2224
      ]
    },
    {
      "parameters": {
        "url": "={{ $env.FASTIFY_API_URL }}/api/client/providers/{{ $json.actionParams.providerId }}/available-events",
        "authentication": "header",
        "options": {}
      },
      "id": "61775aa2-a353-44f0-bf8b-e0217b7f3fab",
      "name": "API: Query Availability1",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        640,
        -2096
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $env.FASTIFY_API_URL }}/api/client/bookings",
        "authentication": "header",
        "options": {}
      },
      "id": "db9eac41-8f8c-4946-b496-cd1ea74c1b7d",
      "name": "API: Create Booking1",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        640,
        -1984
      ]
    },
    {
      "parameters": {
        "jsCode": "// API Response Handler\n// Checks for 401 Unauthorized and generates a new response for the LLM to process\n\nconst apiResponse = $input.item.json;\nconst statusCode = apiResponse.statusCode;\nconst originalData = $('Parse Action & Update History1').item.json;\n\nif (statusCode === 401) {\n  // Authentication required\n  return {\n    json: {\n      responseText: 'Authentication is required for this action. Please log in.',\n      actionType: 'auth_required',\n      apiResponse: apiResponse\n    }\n  };\n} else if (statusCode >= 400) {\n  // General API error\n  return {\n    json: {\n      responseText: 'I encountered an error while communicating with the booking system. The error was: ' + (apiResponse.statusText || 'Unknown Error'),\n      actionType: 'continue_conversation',\n      apiResponse: apiResponse\n    }\n  };\n} else {\n  // Successful API call\n  return {\n    json: {\n      responseText: 'API_SUCCESS_PROCEED_TO_SUMMARY',\n      actionType: originalData.actionType,\n      apiResponse: apiResponse\n    }\n  };\n}"
      },
      "id": "1e682ed4-a260-4946-9372-cbe8810a74cf",
      "name": "API Response Handler1",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        864,
        -2224
      ]
    },
    {
      "parameters": {
        "prompt": "=Summarize the following API response in a natural, conversational way for a voice assistant user. Be concise and clear.\n\nAction type: {{ $json.actionType }}\nAPI Response: {{ JSON.stringify($json.apiResponse) }}\n\nProvide a brief, friendly summary that the user can understand.",
        "batching": {}
      },
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.9,
      "position": [
        1088,
        -2224
      ],
      "id": "b8cc04ff-4d95-41f0-a2d0-3dd5bd99ef73",
      "name": "LLM: Summarize API Result1"
    },
    {
      "parameters": {
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "typeVersion": 1,
      "position": [
        1088,
        -2096
      ],
      "id": "a9f1c2d3-e4b5-6789-abcd-ef0123456789",
      "name": "Google Gemini Chat Model (Summarize)",
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $env.LOGGING_API_URL }}/log/voice-agent",
        "options": {}
      },
      "id": "46962006-3e6c-44dd-9169-020fdfaa1dec",
      "name": "Transaction Logger1",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1312,
        -2224
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "options": {}
      },
      "id": "ac2359f0-ed70-4f6a-80fd-f5de9973383a",
      "name": "Webhook Response (Final)1",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        1744,
        -2224
      ]
    },
    {
      "parameters": {
        "jsCode": "// Error Handler\n// Catches errors from STT, LLM, or API and generates a friendly response.\n\nconst errorData = $input.item.error;\nconst originalData = $('Webhook Trigger1').item.json;\n\nconst errorType = errorData.context.node.name;\nconst errorMessage = errorData.message;\n\n// Log the error internally (optional, can be replaced by a dedicated logging node)\nconsole.error(`Error in ${errorType}: ${errorMessage}`);\n\nreturn {\n  json: {\n    responseText: 'I am sorry, I seem to have run into a technical issue while processing your request. Please try again or rephrase your question.',\n    sessionUpdate: JSON.parse(originalData.sessionState || '{}'),\n    errorDetails: { type: errorType, message: errorMessage }\n  }\n};"
      },
      "id": "7656473e-c6d9-45a1-bf80-1657d4b170ae",
      "name": "Error Handler1",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        640,
        -1792
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "options": {}
      },
      "id": "809347a6-1069-43e3-b106-fdb7d6abfdc0",
      "name": "Webhook Response (Error)1",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        1088,
        -1792
      ]
    },
    {
      "parameters": {
        "prompt": "={{ $json.fullPrompt }}",
        "batching": {}
      },
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.9,
      "position": [
        -16,
        -2304
      ],
      "id": "11049307-2d64-4ed2-91e1-bd2c24834eb1",
      "name": "Basic LLM Chain"
    },
    {
      "parameters": {
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "typeVersion": 1,
      "position": [
        -16,
        -2176
      ],
      "id": "5f3de4cf-3d1f-4cd4-b89b-eeb30fc8ed07",
      "name": "Google Gemini Chat Model",
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      }
    }
  ],
  "connections": {
    "Webhook Trigger1": {
      "main": [
        [
          {
            "node": "Context Builder (History)1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Context Builder (History)1": {
      "main": [
        [
          {
            "node": "Basic LLM Chain",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Basic LLM Chain": {
      "main": [
        [
          {
            "node": "Parse Action & Update History1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Action & Update History1": {
      "main": [
        [
          {
            "node": "Switch Action Type1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Switch Action Type1": {
      "main": [
        [
          {
            "node": "API: Search Organizations1",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "API: List Departments1",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "API: List Providers1",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "API: Query Availability1",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "API: Create Booking1",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Webhook Response (Final)1",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Webhook Response (Final)1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "API: Search Organizations1": {
      "main": [
        [
          {
            "node": "API Response Handler1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "API: List Departments1": {
      "main": [
        [
          {
            "node": "API Response Handler1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "API: List Providers1": {
      "main": [
        [
          {
            "node": "API Response Handler1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "API: Query Availability1": {
      "main": [
        [
          {
            "node": "API Response Handler1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "API: Create Booking1": {
      "main": [
        [
          {
            "node": "API Response Handler1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "API Response Handler1": {
      "main": [
        [
          {
            "node": "LLM: Summarize API Result1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "LLM: Summarize API Result1": {
      "main": [
        [
          {
            "node": "Transaction Logger1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Transaction Logger1": {
      "main": [
        [
          {
            "node": "Webhook Response (Final)1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Error Handler1": {
      "main": [
        [
          {
            "node": "Webhook Response (Error)1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Gemini Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "Basic LLM Chain",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Google Gemini Chat Model (Summarize)": {
      "ai_languageModel": [
        [
          {
            "node": "LLM: Summarize API Result1",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "0ff04401-4582-4e04-9afe-d0a77ee9464b",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "id": "XS1G1gyAzzS1uDAN",
  "tags": []
}

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

Voice Agent Pro: Agentic Booking with TTS and Robust Error Handling. Uses httpRequest, chainLlm, lmChatGoogleGemini. Webhook trigger; 18 nodes.

Source: https://github.com/hbela/booking-n8n-workflows/blob/8c9a15486d7ffe0de8d4a4a4d1ae4befb615f5b3/workflows/voice-event.json — 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

ANIS_HUB 1. Uses gmail, googleDrive, googleSheets, httpRequest. Webhook trigger; 89 nodes.

Gmail, Google Drive, Google Sheets +3
AI & RAG

CLINICAINTEGRAL_secretary. Uses postgres, mcpClientTool, googleDriveTool, toolWorkflow. Webhook trigger; 89 nodes.

Postgres, Mcp Client Tool, Google Drive Tool +14
AI & RAG

secretaria. Uses postgres, n8n-nodes-evolution-api, openAi, httpRequest. Webhook trigger; 71 nodes.

Postgres, N8N Nodes Evolution Api, OpenAI +12
AI & RAG

Resume Screening & Behavioral Interviews with Gemini, Elevenlabs, & Notion ATS copy. Uses outputParserStructured, chainLlm, googleDrive, stickyNote. Webhook trigger; 67 nodes.

Output Parser Structured, Chain Llm, Google Drive +9
AI & RAG

Candidate Engagement | Resume Screening | AI Voice Interviews | Applicant Insights

Output Parser Structured, Chain Llm, Google Drive +9