This workflow corresponds to n8n.io template #17630 — we link there as the canonical source.
This workflow follows the Agent → Execute Workflow Trigger 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 →
{
"id": "SDr4EMja5tiqXq1j",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "AI Appointment Assistant - N8N creator Version",
"tags": [],
"nodes": [
{
"id": "7e471930-c667-4d36-b884-9da21710206d",
"name": "AI Agent",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
48,
160
],
"parameters": {
"text": "={{ $('WhatsApp Trigger').item.json.messages[0].text.body }}",
"options": {
"maxIterations": 15,
"systemMessage": "=# AI Appointment Booking Assistant \n\nYou are a WhatsApp Appointment Booking Assistant.\n\nYou are a rule-driven scheduling agent whose primary responsibility is to help customers manage appointments.\n\nAccuracy is more important than speed.\n\nNever guess, assume, or invent information.\n\n---\n\n## Current Context\n\nCurrent Date & Time:\n\n```\n{{ $now.format('yyyy-MM-dd HH:mm') }}\n```\n\n---\n\n## Primary Responsibilities\n\nYou can help customers:\n\n- Book appointments\n- Check appointment availability\n- Reschedule appointments\n- Cancel appointments\n- Answer business-related appointment questions\n- Escalate to a human when appropriate\n\n---\n\n## Customer Information\n\nOnly collect information when it is actually required.\n\n### Name Rules\n\n- Accept names in any language or script.\n- Third-party bookings are allowed.\n- If booking for someone else, always use the patient's name, not the conversation owner's.\n- If the provided name is clearly invalid (random characters, numbers only, emojis, etc.), politely ask for a valid name.\n- Never guess a customer's name.\n\n---\n\n## Availability Rules\n\nAvailability is provided only by the scheduling system.\n\nYou must never:\n\n- Invent available slots\n- Estimate availability\n- Suggest likely times\n- Assume appointments exist\n\nIf availability has not been checked, retrieve it from the scheduling system.\n\n---\n\n## Booking Workflow\n\n### Booking\n\n1. Determine the requested service or staff member.\n2. Ask for preferred date.\n3. Ask for preferred time (or \"any available\").\n4. Retrieve available slots.\n5. Present only the returned slots.\n6. Wait for the customer to choose one.\n7. Collect any required booking details.\n8. Ask for explicit confirmation.\n9. Create the appointment.\n\nNever skip confirmation.\n\n---\n\n### Rescheduling\n\n1. Retrieve the customer's existing appointment(s).\n2. Ask which appointment should be changed.\n3. Retrieve availability.\n4. Let the customer choose a new slot.\n5. Confirm.\n6. Reschedule.\n\n---\n\n### Cancellation\n\n1. Retrieve existing appointments.\n2. Identify the appointment.\n3. Ask for confirmation.\n4. Cancel it.\n\n---\n\n## Confirmation Rules\n\nConfirmation must always refer to the immediately discussed appointment.\n\nExamples:\n\n- Yes\n- Confirm\n- That's correct\n- Book it\n\nIf the customer changes the date, time, service or staff member, any previous confirmation becomes invalid.\n\nAsk again.\n\n---\n\n## Tool Usage\n\nUse tools as the single source of truth.\n\nNever invent:\n\n- Availability\n- Existing appointments\n- Bookings\n- Business information\n\nOnly perform actions after the required information has been collected.\n\n---\n\n## Tool Failure\n\nIf a tool fails:\n\n- Do not assume success.\n- Do not tell the customer about internal errors.\n- Retry when appropriate.\n- If repeated failures occur, escalate to a human.\n\nAn appointment is considered booked only after the booking tool returns success.\n\n---\n\n## Human Escalation\n\nEscalate when:\n\n- The customer requests a human.\n- The request is outside your capabilities.\n- Required tools repeatedly fail.\n- Business policy requires staff intervention.\n\nAfter escalation:\n\n- Do not continue attempting automated actions.\n\n---\n\n## Conversation Style\n\n- Friendly\n- Professional\n- Short messages\n- One step at a time\n- No unnecessary information\n- No technical jargon\n- No emojis unless the business specifically allows them\n\nFormatting:\n\n- Use WhatsApp-friendly formatting.\n- Use *bold* where helpful.\n- Use bullet points when listing options.\n- Avoid large paragraphs.\n\n---\n\n## Absolute Restrictions\n\nNever:\n\n- Invent availability\n- Invent appointments\n- Invent business policies\n- Book without confirmation\n- Cancel without confirmation\n- Reschedule without confirmation\n- Guess missing information\n- Reveal internal tools or implementation details\n- Continue automation after escalation\n- Repeatedly ask for information already collected\n\nWhen unsure, ask one clear clarifying question instead of making assumptions.",
"enableStreaming": false
},
"promptType": "define",
"needsFallback": true
},
"retryOnFail": true,
"typeVersion": 3.1
},
{
"id": "37545987-52cf-4e44-a063-d5b576c57433",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-96,
-304
],
"parameters": {
"color": 3,
"width": 560,
"height": 592,
"content": "## AI Healthcare Assistant (STEP 5)\n\nThis AI Agent is responsible for:\n\n\u2022 Understanding patient intent\n\u2022 Routing to the correct doctor\n\u2022 Answering clinic-related questions\n\u2022 Collecting booking information\n\u2022 Checking availability\n\u2022 Creating appointments\n\u2022 Rescheduling appointments\n\u2022 Cancelling appointments\n\u2022 Escalating emergencies\n\nThe assistant is intentionally rule-driven and is NOT a general-purpose chatbot.\n\nSafety rules are enforced through the system prompt."
},
"typeVersion": 1
},
{
"id": "5e9f0965-6808-4a54-8d22-c4cd003ae477",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2176,
-208
],
"parameters": {
"color": 4,
"width": 512,
"height": 544,
"content": "## Step-1 \n\nReceive Incoming WhatsApp Messages\n\nThis workflow starts whenever a patient sends a WhatsApp message.\n\nSupported message types\n\n\u2022 Text\n\u2022 Appointment Requests\n\u2022 Reschedule Requests\n\u2022 Cancellation Requests\n\nThe workflow validates the message before sending it to the AI assistant."
},
"typeVersion": 1
},
{
"id": "b0556dd9-02b8-468e-bdef-8877e2d13a93",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-512,
608
],
"parameters": {
"color": 6,
"width": 752,
"height": 432,
"content": "Appointment Management Tools\n\nThe AI has access to controlled scheduling tools.\n\nAvailable operations\n\n\u2022 Check availability\n\u2022 Create appointment\n\u2022 Update appointment\n\u2022 Cancel appointment\n\nThe AI never invents appointment slots.\n\nEvery booking is validated against Google Calendar before confirmation."
},
"typeVersion": 1
},
{
"id": "a7611e22-ac0c-4ac1-8aa8-151c0e0a6210",
"name": "Get if the escalation Exists",
"type": "n8n-nodes-base.redis",
"onError": "continueRegularOutput",
"position": [
-944,
176
],
"parameters": {
"key": "=escalation:{{ $json.data }}",
"options": {},
"operation": "get",
"propertyName": "=response"
},
"credentials": {
"redis": {
"name": "<your credential>"
}
},
"retryOnFail": true,
"typeVersion": 1,
"alwaysOutputData": true
},
{
"id": "39e8f237-c7cf-42f8-b92e-4e80ffc35290",
"name": "Handle Message Updates",
"type": "n8n-nodes-base.if",
"position": [
-1616,
192
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "43f8c9ee-b6a2-45be-9dc3-fecfb3c9a91a",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.messages[0].type }}",
"rightValue": "text"
}
]
}
},
"typeVersion": 2.3
},
{
"id": "2da7e814-b93e-4b5e-8ec3-be856dca5b00",
"name": "Route according to escalation status",
"type": "n8n-nodes-base.if",
"position": [
-736,
176
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "fc84ca2f-840e-43e8-896b-525ba8812155",
"operator": {
"type": "object",
"operation": "exists",
"singleValue": true
},
"leftValue": "={{ JSON.stringify($json.response) }}",
"rightValue": ""
}
]
},
"looseTypeValidation": true
},
"typeVersion": 2.3
},
{
"id": "a5a51b74-0a4d-4b60-a93b-79c02d83a433",
"name": "Postgres Chat Memory",
"type": "@n8n/n8n-nodes-langchain.memoryPostgresChat",
"position": [
640,
384
],
"parameters": {
"sessionKey": "={{ $('WhatsApp Trigger').item.json.contacts[0].wa_id }}",
"sessionIdType": "customKey",
"contextWindowLength": 20
},
"credentials": {
"postgres": {
"name": "<your credential>"
}
},
"retryOnFail": true,
"typeVersion": 1.3
},
{
"id": "a4b52b26-ab0e-4211-9f30-c143ccaea271",
"name": "Switch",
"type": "n8n-nodes-base.switch",
"position": [
-368,
176
],
"parameters": {
"rules": {
"values": [
{
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "ad37bd0c-5292-4d66-ae8f-cea71f51045a",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $('Webhook').item.json.messages[0].type }}",
"rightValue": "text"
}
]
}
},
{
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "b8efb132-1d49-419b-aebc-4cb23b9cd75e",
"operator": {
"type": "string",
"operation": "startsWith"
},
"leftValue": "={{ $('Webhook').item.json.messages[0].type }}",
"rightValue": "reaction"
}
]
}
}
]
},
"options": {
"fallbackOutput": "extra"
},
"looseTypeValidation": true
},
"typeVersion": 3.4
},
{
"id": "dec6d377-707c-439b-9d68-039e6a8ecaa4",
"name": "Secondary LLM",
"type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
"position": [
16,
432
],
"parameters": {
"options": {},
"modelName": "models/gemini-3.1-flash-lite"
},
"credentials": {
"googlePalmApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "362ff4a3-e522-4554-8dbf-859e96351152",
"name": "Primary LLM",
"type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
"position": [
-96,
384
],
"parameters": {
"options": {}
},
"credentials": {
"googlePalmApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "b7f6a884-5781-4de5-b725-50fd0eed6ff8",
"name": "Crypto",
"type": "n8n-nodes-base.crypto",
"position": [
-1216,
176
],
"parameters": {
"value": "={{ $('WhatsApp Trigger').item.json.contacts[0].wa_id }}"
},
"typeVersion": 2
},
{
"id": "122263b4-e9f0-4f56-b90b-ab90780c36a5",
"name": "Verify the name",
"type": "n8n-nodes-base.code",
"position": [
-1392,
176
],
"parameters": {
"jsCode": "function validate_name(name){\n const patient_name = name.trim();\n if(typeof(patient_name) === \"string\" && patient_name.length !== 0)\n {\n if(patient_name.length > 2 && patient_name.length <= 50)\n {\n return { \n name:patient_name,\n valid: true\n };\n }\n return {\n valid:false,\n reason:\"Empty Name Received.Ask the user for name.\"\n };\n }\n\n return {\n valid: false,\n reason:\"Invalid Name received.Ask the user for name.\"\n };\n}\n\nconst name = $input.first().json.contacts[0].profile.name || \"\";\n\nreturn {\n ...validate_name(name)\n};"
},
"typeVersion": 2
},
{
"id": "fa9a192b-fcbe-46ff-8c7b-79f401a679c7",
"name": "Send message",
"type": "n8n-nodes-base.whatsApp",
"position": [
1056,
160
],
"parameters": {
"textBody": "={{ $json.output }}",
"operation": "send",
"phoneNumberId": "908689399005700",
"additionalFields": {},
"recipientPhoneNumber": "={{ $('WhatsApp Trigger').item.json.contacts[0].wa_id }}"
},
"credentials": {
"whatsAppApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.1
},
{
"id": "0f46dbae-9478-4259-9df2-f604e4c99d64",
"name": "WhatsApp Trigger",
"type": "n8n-nodes-base.whatsAppTrigger",
"position": [
-1968,
192
],
"parameters": {
"options": {},
"updates": [
"messages"
]
},
"credentials": {
"whatsAppTriggerApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "7d7072b1-3fd5-415c-ad00-fcbd9426a76a",
"name": "Get availability in a calendar in Google Calendar",
"type": "n8n-nodes-base.googleCalendarTool",
"position": [
-224,
736
],
"parameters": {
"options": {},
"timeMax": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('End_Time', ``, 'string') }}",
"timeMin": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Start_Time', ``, 'string') }}",
"calendar": {
"__rl": true,
"mode": "list",
"value": ""
},
"resource": "calendar"
},
"typeVersion": 1.3
},
{
"id": "c5798e57-2ec5-45e3-8051-c7a428d6743f",
"name": "Create an event in Google Calendar",
"type": "n8n-nodes-base.googleCalendarTool",
"position": [
-96,
640
],
"parameters": {
"calendar": {
"__rl": true,
"mode": "list",
"value": ""
},
"additionalFields": {}
},
"typeVersion": 1.3
},
{
"id": "aa0e6d76-e2ab-4cef-9e60-13f76b12a616",
"name": "Delete an event in Google Calendar",
"type": "n8n-nodes-base.googleCalendarTool",
"position": [
-16,
768
],
"parameters": {
"eventId": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Event_ID', ``, 'string') }}",
"options": {},
"calendar": {
"__rl": true,
"mode": "list",
"value": ""
},
"operation": "delete"
},
"typeVersion": 1.3
},
{
"id": "ba41a057-b731-4927-87db-f4578b119930",
"name": "Update an event in Google Calendar",
"type": "n8n-nodes-base.googleCalendarTool",
"position": [
80,
640
],
"parameters": {
"eventId": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Event_ID', ``, 'string') }}",
"calendar": {
"__rl": true,
"mode": "list",
"value": ""
},
"operation": "update",
"updateFields": {
"end": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('End', ``, 'string') }}",
"start": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Start', ``, 'string') }}"
},
"useDefaultReminders": false
},
"typeVersion": 1.3
},
{
"id": "35703707-355f-4534-bd9a-51ccb9e4b7e5",
"name": "Get row(s) in sheet in Google Sheets",
"type": "n8n-nodes-base.googleSheetsTool",
"position": [
832,
736
],
"parameters": {
"options": {},
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1y8Dn0lQrcmq30NGaS_Ojwdtdzh4H2_uuzO6VelUGh5s/edit#gid=0",
"cachedResultName": "Sheet1"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1y8Dn0lQrcmq30NGaS_Ojwdtdzh4H2_uuzO6VelUGh5s",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1y8Dn0lQrcmq30NGaS_Ojwdtdzh4H2_uuzO6VelUGh5s/edit?usp=drivesdk",
"cachedResultName": "Test Automation Sheet"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.7
},
{
"id": "c82706cc-8c4a-4e60-904c-edb2ea1e8fd9",
"name": "Get Day From Date",
"type": "@n8n/n8n-nodes-langchain.toolCode",
"position": [
112,
864
],
"parameters": {
"jsCode": "const data = query;\nconst daysOfWeek = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];\nconst today = new Date(query.date);\nconst currentDayName = daysOfWeek[today.getDay()];\n\nreturn ` Day on the date ${today.toISOString().slice(0,10)} is ${currentDayName}`;\n",
"schemaType": "manual",
"description": "Call this tool to get which day falls on the date.\n\nInput\n\nDate in ISO8601 format:\n\nOutput:\n\nDay",
"inputSchema": "{\n\"type\": \"object\",\n\"properties\": {\n\t\"date\": {\n\t\t\"type\": \"string\",\n\t\t\"description\": \"Date in ISO8601 Format.\"\n\t\t}\n\t}\n}",
"specifyInputSchema": true
},
"typeVersion": 1.3
},
{
"id": "10b06121-eb1a-4634-b770-86bdc8a6be8f",
"name": "Escalate to Human",
"type": "@n8n/n8n-nodes-langchain.toolWorkflow",
"position": [
368,
816
],
"parameters": {
"workflowId": {
"__rl": true,
"mode": "list",
"value": "ye7g9dBXCCXFoUdv",
"cachedResultUrl": "/workflow/ye7g9dBXCCXFoUdv",
"cachedResultName": "escalate_to_human"
},
"description": "=### Purpose\n\n**Description:**\nEscalates the current conversation to a human staff member and immediately halts all automated responses. This tool should be invoked when the user explicitly requests human assistance (e.g., \u201ctalk to a human\u201d, \u201cconnect me to staff\u201d) or when the situation requires manual intervention (e.g., emergency, complex medical query, repeated failures, or unclear intent). Once triggered, the system will notify clinic staff and lock the conversation so no further AI automation occurs until manually resumed.\n\n**Input:**\n\n```json\n{\n \"patient_phone\": \"string (Automatically injected by system; AI must NOT generate or modify this)\",\n \"patient_name\": \"string (Name of the patient as provided in conversation or from the System Prompt)\",\n \"reason\": \"string (Short explanation for escalation, e.g., 'User requested human support' or 'Emergency concern')\"\n}\n```\n\n**Input Field Rules:**\n\n* `patient_phone`\n\n * Provided automatically by the backend (from WhatsApp metadata).\n * The AI must never fabricate, guess, or alter this value.\n* `patient_name`\n\n * Extract from conversation context if available.\n * If not available ask the user first and then pass it.Name cannot be empty else the tool will crash.\n\n* `reason`\n\n * Concise, human-readable justification for escalation.\n * Should reflect the user\u2019s intent or risk scenario.\n\n**When to Use:**\n\n* User explicitly asks for a human agent\n* Mentions emergency or urgent medical situation\n* Requests help outside supported automation scope\n* Repeated booking failures or ambiguous requirements\n* Any scenario requiring manual review or intervention\n\n\n**Expected Outcome:**\nThe conversation is safely handed over to human operators with full context, ensuring no further automated replies are sent.\n",
"workflowInputs": {
"value": {
"reason": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('reason', ``, 'string') }}",
"patient_name": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('patient_name', ``, 'string') }}",
"patient_phone": "={{ $('WhatsApp Trigger').item.json.contacts[0].wa_id }}"
},
"schema": [
{
"id": "patient_phone",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "patient_phone",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "patient_name",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "patient_name",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "reason",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "reason",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": false
}
},
"typeVersion": 2.2
},
{
"id": "06ac19a7-2070-4ef2-b760-da2dd57cda01",
"name": "When Executed by Another Workflow",
"type": "n8n-nodes-base.executeWorkflowTrigger",
"position": [
-1888,
752
],
"parameters": {
"workflowInputs": {
"values": [
{
"name": "patient_phone"
},
{
"name": "patient_name"
},
{
"name": "reason"
}
]
}
},
"typeVersion": 1.1
},
{
"id": "2677ddb0-221c-44d6-831f-0efcf4130bf5",
"name": "Redis",
"type": "n8n-nodes-base.redis",
"position": [
-1232,
752
],
"parameters": {
"key": "=escalation:{{ $json.token}}",
"ttl": 1800,
"value": "={{ $json }}",
"expire": true,
"operation": "set"
},
"credentials": {
"redis": {
"name": "<your credential>"
}
},
"retryOnFail": true,
"typeVersion": 1
},
{
"id": "32d62e5f-1e7d-4dd7-82db-329d321bc192",
"name": "Create A Link",
"type": "n8n-nodes-base.code",
"position": [
-1456,
752
],
"parameters": {
"jsCode": "const base_url = \"\" // Update your instance URL here\n\nconst payload = $input.first().json;\n\nconst unique_id = payload.unique_id;\n\nconst msg = `Patient with name: ${payload.patient_name} raised an escalation.\nReason for Escalation: ${payload.reason}\nMessage the User: wa.me/${payload.patient_phone}\n\n*IMPORTANT*: Click this link after you have resolved the Escalation as this will allow the bot to continue to chat to the user.\n\nResolution URL:\n${base_url}?id=${unique_id}\n`\n\nreturn {\n ...payload,\n token: unique_id,\n resolve_url: `${base_url}?id=${unique_id}`,\n msg: encodeURIComponent(msg)\n}\n\n\n\n\n"
},
"typeVersion": 2
},
{
"id": "9532b947-375d-490f-aaec-5127fa55f602",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1952,
400
],
"parameters": {
"color": 2,
"width": 1120,
"height": 576,
"content": "## Human in the Loop System\n\nEmergency & Human Escalation\n\nPatients are immediately transferred to clinic staff when:\n\n\u2022 Emergency symptoms are detected\n\u2022 Human assistance is requested\n\u2022 Booking cannot be completed\n\u2022 The AI cannot safely continue\n\nOnce escalated, automated replies stop until the conversation is resumed by staff."
},
"typeVersion": 1
},
{
"id": "6f921416-75a6-4ef1-b85d-f93f756eb381",
"name": "Send template",
"type": "n8n-nodes-base.whatsApp",
"position": [
-1040,
752
],
"parameters": {
"template": "hello_world|en_US",
"phoneNumberId": "908689399005700",
"recipientPhoneNumber": "={{ $('When Executed by Another Workflow').item.json.patient_phone }}"
},
"credentials": {
"whatsAppApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.1
},
{
"id": "79bdf287-895e-4984-97fc-332b0df3abfa",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
-3024,
-368
],
"parameters": {
"width": 720,
"height": 1584,
"content": "## AI Healthcare Appointment Assistant\n\nThis workflow provides an AI-powered WhatsApp assistant for\nhealthcare clinics.\n\n### Features\n- WhatsApp appointment booking\n- AI-powered patient conversation\n- Doctor routing based on symptoms\n- Google Calendar integration\n- Human escalation\n- Persistent conversation memory\n- Appointment rescheduling\n- Appointment cancellation\n\n### Suitable for\n- Clinics\n- Hospitals\n- Medical Practices\n- Diagnostic Centres\n\n### Supported Integrations\n- WhatsApp Cloud API\n- Google Gemini\n- Google Calendar\n- PostgreSQL\n- Redis\n\nEstimated Setup Time\n30 Minutes\n\n\n\n### How It Works (High Level)\n\n1. Receive Customer Message\nThe workflow starts whenever a customer sends a WhatsApp message requesting to book, reschedule, cancel, or enquire about an appointment.\n\n2. Validate the Request\nIncoming messages are validated, customer information is extracted, and the workflow checks whether the conversation has already been escalated to a human agent.\n\n3. Continue the AI Conversation\nThe AI Assistant uses persistent PostgreSQL chat memory to understand previous interactions and continue the conversation naturally without asking for the same information again.\n\n4. Understand Customer Intent\nThe AI identifies what the customer wants, such as booking a new appointment, checking availability, rescheduling, cancelling an appointment, or requesting general appointment information.\n\n5. Interact with Scheduling Tools\nWhen required, the AI securely interacts with connected tools to:\nCheck appointment availability\nCreate appointments\nReschedule appointments\nCancel appointments\nRetrieve business information\n\n\n6. Follow Safe Booking Rules\nThe assistant never guesses availability or confirms appointments without explicit customer confirmation. All scheduling decisions are validated using connected systems.\n\n7. Escalate When Needed\nIf the customer requests human assistance or the conversation cannot be safely completed, the workflow automatically escalates the conversation and pauses further AI responses.\n\n8. Send the Final Response\nOnce the requested action is completed, the AI sends a clear, concise response back to the customer through WhatsApp while maintaining the conversation history for future interactions.\n"
},
"typeVersion": 1
},
{
"id": "df2f5db5-06a4-4e13-a45a-9211e2bba721",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1648,
-208
],
"parameters": {
"color": 4,
"width": 560,
"height": 544,
"content": "## STEP 2\n\nConversation Validation\n\nBefore AI processing the workflow:\n\n\u2022 Validates message type\n\u2022 Extracts patient profile\n\u2022 Verifies patient name\n\u2022 Generates secure conversation identifier\n\u2022 Checks escalation status\n\nInvalid or unsupported messages are ignored."
},
"typeVersion": 1
},
{
"id": "83cf2771-de81-4ea2-ad50-0d2cc243dd27",
"name": "Sticky Note6",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1024,
-208
],
"parameters": {
"color": 4,
"width": 512,
"height": 544,
"content": "## STEP 3\n\nHuman Escalation Check\n\nBefore responding, Redis is checked to determine whether this conversation has already been escalated.\n\nIf an active escalation exists:\n\n\u2022 AI responses stop\n\u2022 Human staff owns the conversation\n\nThis prevents AI from replying after manual takeover."
},
"typeVersion": 1
},
{
"id": "7a87d0f3-0b91-46d5-ae38-bfbb4ef5ce9b",
"name": "Sticky Note7",
"type": "n8n-nodes-base.stickyNote",
"position": [
240,
320
],
"parameters": {
"color": 3,
"width": 576,
"height": 304,
"content": "## Persistent Conversation Memory\n\nConversation history is stored in PostgreSQL.\n\nThe memory allows the assistant to:\n\n\u2022 Continue previous conversations\n\u2022 Remember appointment details\n\u2022 Maintain context across multiple WhatsApp messages\n\nOnly recent conversation history is supplied to the LLM to reduce token usage."
},
"typeVersion": 1
},
{
"id": "4db6bcd6-f38d-4476-ba4a-fb435e8eb3df",
"name": "Sticky Note8",
"type": "n8n-nodes-base.stickyNote",
"position": [
-432,
352
],
"parameters": {
"color": 3,
"width": 576,
"height": 240,
"content": "Primary Model\n\n\u2022 Handles all patient conversations.\n\nSecondary Model\n\n\u2022 Used automatically if the primary model fails.\n\nThis improves workflow reliability while reducing downtime."
},
"typeVersion": 1
},
{
"id": "880d196b-4bae-497c-96bf-dd6486dd7aa4",
"name": "Sticky Note9",
"type": "n8n-nodes-base.stickyNote",
"position": [
608,
640
],
"parameters": {
"width": 432,
"height": 384,
"content": "Google Sheets is used as a lightweight structured knowledge source.\n\nTypical use cases\n\n\u2022 Clinic information\n\u2022 Doctor information\n\u2022 FAQs\n\u2022 Configuration\n\nThis can be replaced with a database or CRM in production deployments."
},
"typeVersion": 1
},
{
"id": "4c571c9f-6012-4ca2-8b23-55c193008906",
"name": "Sticky Note10",
"type": "n8n-nodes-base.stickyNote",
"position": [
912,
-128
],
"parameters": {
"color": 7,
"width": 400,
"height": 432,
"content": "### STEP 6\n\nThe AI response is delivered to the patient through WhatsApp.\n\nEvery reply follows clinic communication rules:\n\n\u2022 Short\n\u2022 Professional\n\u2022 No diagnosis\n\u2022 No hallucinated availability\n\u2022 Appointment confirmation only after successful booking"
},
"typeVersion": 1
},
{
"id": "5bdd09bb-98a4-46f4-a8bd-1609529b30e6",
"name": "Sticky Note11",
"type": "n8n-nodes-base.stickyNote",
"position": [
272,
720
],
"parameters": {
"color": 2,
"width": 272,
"height": 256,
"content": "## Human in the Loop Tool\n"
},
"typeVersion": 1
},
{
"id": "37d675f3-c6b4-4820-b531-6699ab927841",
"name": "Crypto1",
"type": "n8n-nodes-base.crypto",
"position": [
-1680,
752
],
"parameters": {
"value": "={{ $json.patient_phone }}",
"dataPropertyName": "unique_id"
},
"typeVersion": 2
},
{
"id": "7fa4f52c-df7a-4640-b45c-b7fae4a853a2",
"name": "Sticky Note12",
"type": "n8n-nodes-base.stickyNote",
"position": [
-480,
0
],
"parameters": {
"color": 2,
"width": 336,
"height": 336,
"content": "STEP 4:\n\nThe switch node prevents non-text messages reaching the node.\n"
},
"typeVersion": 1
}
],
"active": false,
"settings": {
"binaryMode": "separate",
"availableInMCP": false,
"executionOrder": "v1"
},
"versionId": "15cd2932-990c-4bf9-bb22-6c4582898ddf",
"nodeGroups": [],
"connections": {
"Redis": {
"main": [
[
{
"node": "Send template",
"type": "main",
"index": 0
}
]
]
},
"Crypto": {
"main": [
[
{
"node": "Get if the escalation Exists",
"type": "main",
"index": 0
}
]
]
},
"Switch": {
"main": [
[
{
"node": "AI Agent",
"type": "main",
"index": 0
}
],
[]
]
},
"Crypto1": {
"main": [
[
{
"node": "Create A Link",
"type": "main",
"index": 0
}
]
]
},
"AI Agent": {
"main": [
[
{
"node": "Send message",
"type": "main",
"index": 0
}
]
]
},
"Primary LLM": {
"ai_languageModel": [
[
{
"node": "AI Agent",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Send message": {
"main": [
[]
]
},
"Create A Link": {
"main": [
[
{
"node": "Redis",
"type": "main",
"index": 0
}
]
]
},
"Secondary LLM": {
"ai_languageModel": [
[
{
"node": "AI Agent",
"type": "ai_languageModel",
"index": 1
}
]
]
},
"Verify the name": {
"main": [
[
{
"node": "Crypto",
"type": "main",
"index": 0
}
]
]
},
"WhatsApp Trigger": {
"main": [
[
{
"node": "Handle Message Updates",
"type": "main",
"index": 0
}
]
]
},
"Escalate to Human": {
"ai_tool": [
[
{
"node": "AI Agent",
"type": "ai_tool",
"index": 0
}
]
]
},
"Get Day From Date": {
"ai_tool": [
[
{
"node": "AI Agent",
"type": "ai_tool",
"index": 0
}
]
]
},
"Postgres Chat Memory": {
"ai_memory": [
[
{
"node": "AI Agent",
"type": "ai_memory",
"index": 0
}
]
]
},
"Handle Message Updates": {
"main": [
[
{
"node": "Verify the name",
"type": "main",
"index": 0
}
]
]
},
"Get if the escalation Exists": {
"main": [
[
{
"node": "Route according to escalation status",
"type": "main",
"index": 0
}
]
]
},
"When Executed by Another Workflow": {
"main": [
[
{
"node": "Crypto1",
"type": "main",
"index": 0
}
]
]
},
"Create an event in Google Calendar": {
"ai_tool": [
[
{
"node": "AI Agent",
"type": "ai_tool",
"index": 0
}
]
]
},
"Delete an event in Google Calendar": {
"ai_tool": [
[
{
"node": "AI Agent",
"type": "ai_tool",
"index": 0
}
]
]
},
"Update an event in Google Calendar": {
"ai_tool": [
[
{
"node": "AI Agent",
"type": "ai_tool",
"index": 0
}
]
]
},
"Get row(s) in sheet in Google Sheets": {
"ai_tool": [
[
{
"node": "AI Agent",
"type": "ai_tool",
"index": 0
}
]
]
},
"Route according to escalation status": {
"main": [
[],
[
{
"node": "Switch",
"type": "main",
"index": 0
}
]
]
},
"Get availability in a calendar in Google Calendar": {
"ai_tool": [
[
{
"node": "AI Agent",
"type": "ai_tool",
"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.
googlePalmApigoogleSheetsOAuth2ApipostgresrediswhatsAppApiwhatsAppTriggerApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow uses the WhatsApp Cloud API, Google Gemini, Google Calendar, PostgreSQL, Redis, and Google Sheets to run an AI appointment assistant that answers scheduling questions, checks availability, books/reschedules/cancels appointments, and escalates conversations to a…
Source: https://n8n.io/workflows/17630/ — 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.
This workflow implements an AI-powered WhatsApp booking assistant for a hair salon. The system allows customers to book, reschedule, or cancel appointments automatically via text or voice messages on
fluxo_n8n_consultorio. Uses postgres, mcpClientTool, outputParserStructured, memoryPostgresChat. Event-driven trigger; 44 nodes.
Powertech Whatsapp. Uses whatsAppTrigger, whatsApp, httpRequest, googleGemini. Event-driven trigger; 36 nodes.
This template is designed for businesses that provide customer support and appointment-based services over WhatsApp. It’s ideal for service providers (e.g., clinics, salons, repair shops, consultants)
Turn your WhatsApp chats into an AI-powered meeting scheduler with Google Gemini, Google Calendar, and Google Sheets. This workflow understands natural language like “Book a meeting with Ali at 3 PM t