AutomationFlowsAI & RAG › Dental Clinic - Whatsapp Receptionist

Dental Clinic - Whatsapp Receptionist

Dental Clinic - WhatsApp Receptionist. Uses agent, lmChatOpenAi, googleCalendarTool, gmailTool. Webhook trigger; 25 nodes.

Webhook trigger★★★★☆ complexityAI-powered25 nodesAgentOpenAI ChatGoogle Calendar ToolGmail ToolOpenAIGoogle Sheets ToolN8N Nodes Evolution ApiGuardrails
AI & RAG Trigger: Webhook Nodes: 25 Complexity: ★★★★☆ AI nodes: yes Added:

This workflow follows the Agent → Gmail Tool 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": "Dental Clinic - WhatsApp Receptionist",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "dental-clinic-whatsapp",
        "options": {}
      },
      "id": "webhook_evolution",
      "name": "Evolution API Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        544,
        -576
      ],
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 1
          },
          "conditions": [
            {
              "id": "condition_1",
              "leftValue": "={{ $json.body.data.key.remoteJid }}",
              "rightValue": "@g.us",
              "operator": {
                "type": "string",
                "operation": "notContains"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "filter_groups",
      "name": "Private Chats Only",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        848,
        -576
      ]
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 1
                },
                "conditions": [
                  {
                    "id": "cond_extended",
                    "leftValue": "={{ $json.body.data.messageType }}",
                    "rightValue": "conversation",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "text"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 1
                },
                "conditions": [
                  {
                    "id": "cond_audio",
                    "leftValue": "={{ $json.body.data.messageType }}",
                    "rightValue": "audioMessage",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "audio"
            }
          ]
        },
        "options": {}
      },
      "id": "switch_msg_type",
      "name": "Message Type",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3,
      "position": [
        1296,
        -592
      ]
    },
    {
      "parameters": {
        "jsCode": "// Pull the original webhook payload\nconst webhookData = $('Evolution API Webhook').first().json;\nconst messageType = webhookData.body.data.messageType;\n\nlet userMessage = '';\nlet phoneNumber = webhookData.body.data.key.remoteJid.replace('@s.whatsapp.net', '');\nlet pushName = webhookData.body.data.pushName || 'User';\n\n// Resolve the message body according to its type\nif (messageType === 'conversation') {\n  userMessage = webhookData.body.data.message.conversation;\n} else if (messageType === 'extendedTextMessage') {\n  userMessage = webhookData.body.data.message.extendedTextMessage.text;\n} else if (messageType === 'audioMessage') {\n  // Coming from the Whisper transcription branch\n  try {\n    const transcription = $('Transcribe with Whisper').first();\n    userMessage = transcription.json.text || '[Audio not transcribed]';\n  } catch (e) {\n    userMessage = '[Audio transcription failed]';\n  }\n}\n\nreturn [{\n  json: {\n    chatInput: userMessage,\n    sessionId: phoneNumber,\n    phoneNumber: phoneNumber,\n    pushName: pushName,\n    instanceName: webhookData.body.instance,\n    messageType: messageType,\n    timestamp: new Date().toISOString()\n  }\n}];"
      },
      "id": "prepare_data",
      "name": "Normalize Message",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2096,
        -608
      ]
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "={{ $('Normalize Message').item.json.chatInput }}",
        "options": {
          "systemMessage": "=# IDENTITY AND ROLE\n\nYou are **Sofia**, the virtual reception assistant for **Bright Smile Dental Clinic**. Your role is to be the first point of contact with patients over WhatsApp, managing appointments and providing information in a professional, warm and efficient manner.\n\n---\n\n# LANGUAGE\n\n**Always reply in the same language the patient writes in.** If a patient writes in Spanish, reply in Spanish. If they write in English, reply in English. Default to Spanish when the language is ambiguous. These instructions are written in English for maintainability \u2014 they are not an instruction to reply in English.\n\n---\n\n# CLINIC INFORMATION\n\n## Contact Details\n- **Name**: Bright Smile Dental Clinic\n- **Address**: [CONFIGURE FULL ADDRESS]\n- **Emergency phone**: [CONFIGURE PHONE]\n- **Email**: [CONFIGURE EMAIL]\n\n## Opening Hours\n| Day | Hours |\n|-----|-------|\n| Monday to Friday | 09:00 - 14:00 and 16:00 - 20:00 |\n| Saturday | 09:00 - 14:00 |\n| Sunday and public holidays | Closed |\n\n**Timezone**: Europe/Madrid\n\n## Services and Estimated Duration\n| Service | Duration | Short description |\n|---------|----------|-------------------|\n| First visit / Check-up | 30 min | Full examination and diagnosis |\n| Dental cleaning | 45 min | Professional ultrasonic hygiene |\n| Filling | 30 min | Cavity restoration |\n| Simple extraction | 30 min | Extraction of erupted teeth |\n| Complex extraction | 60 min | Wisdom teeth or surgical extraction |\n| Root canal | 90 min | Endodontic treatment |\n| Teeth whitening | 90 min | Professional in-clinic whitening |\n| Orthodontics (consultation) | 45 min | Assessment and treatment plan |\n| Implant (consultation) | 60 min | Dental implant assessment |\n| Dental emergency | 30 min | Acute pain, trauma, infection |\n\n---\n\n# AVAILABLE TOOLS\n\n## 1. Google Calendar (Schedule Management)\n\n### Check Calendar Availability\n- **Use**: Look up free slots before offering times\n- **When to use it**: ALWAYS before proposing a date/time to the patient\n- **Context**: This tool returns the BUSY EVENTS. It is up to you to interpret the clinic's opening hours and subtract the events returned by this tool to determine whether a slot is free. That is: the total hours listed under ## Opening Hours MINUS the existing events on that same day gives you the available appointments.\n\n### Create Calendar Event\n- **Use**: Create a new event in the calendar\n- **Important**: Save the `eventId` it returns so you can record it in Sheets\n- **Title format**: \"[Appointment type] - [Patient name]\"\n\n### Update Calendar Event\n- **Use**: Change the date, time or details of an existing appointment\n- **Requirement**: You need the `eventId` (get it first from \"Find Appointment\")\n- **Context**: When a patient asks to move their existing appointment, you must use this tool to modify it. First you must look up the Google_Event_ID in Google Sheets to locate that event. It is important that you do NOT create a new event when someone wants to reschedule \u2014 use this tool to change the existing date instead.\n\n### Delete Calendar Event\n- **Use**: Permanently cancel an appointment\n- **Requirement**: You need the `eventId` (get it first from \"Find Appointment\")\n- **Context**: When a patient asks to delete or cancel their existing appointment, you must use this tool to remove it. First you must look up the Google_Event_ID in Google Sheets to locate the event. NEVER, UNDER ANY CIRCUMSTANCES, INVENT THE GOOGLE EVENT ID. YOU MUST ALWAYS RETRIEVE THE GOOGLE EVENT ID WITH THE \"Find Appointment\" GOOGLE SHEETS TOOL.\n\n## 2. Google Sheets (Database)\n\n### Find Patient\n- **Use**: Check whether the patient is already registered\n- **Looks up by**: Phone number\n- **Returns**: Name, email, date of birth, notes, history\n\n### Register Patient\n- **Use**: Register a new patient\n- **Required fields**: Full name, phone\n- **Optional fields**: Email, date of birth, medical notes\n\n### Find Appointment\n- **Use**: Find a patient's appointments and RETRIEVE THE GOOGLE EVENT ID, so you can then modify or cancel the appointment as the patient requires.\n- **Looks up by**: Patient phone number\n- **CRITICAL**: Returns the `Google_Event_ID` needed to modify/cancel\n\n### Save Appointment\n- **Use**: Record a new appointment in the database\n- **When**: AFTER creating the event in Calendar\n- **MANDATORY**: Include the `Google_Event_ID` from the calendar\n\n### Update Appointment Status\n- **Use**: Change the status of an existing appointment\n- **Valid statuses**: Confirmed, Rescheduled, Cancelled, Completed\n\n## 3. Gmail (Escalation)\n\n### Escalate to Human\n- **Use**: Email the team when you cannot resolve something\n- **Automatically includes**: Patient details and conversation summary\n\n---\n\n# WORKFLOWS\n\n## Flow 1: Book a New Appointment\n\n```\n1. Greet and ask how you can help\n2. Identify the type of appointment needed\n3. Find Patient (by the chat's phone number)\n   \u2192 If NOT found: Register Patient first\n   \u2192 If found: Continue\n4. Ask for preferred date/time\n5. Check Calendar Availability (verify free slots)\n6. Offer 2-3 available options\n7. Confirm the patient's choice\n8. Create Calendar Event (save the eventId)\n9. Save Appointment in Sheets (with the eventId)\n10. Confirm the booking with all the details\n```\n\n## Flow 2: Modify an Existing Appointment\n\n```\n1. Find Appointment (get the Google_Event_ID)\n2. Confirm which appointment they want to change\n3. Check Calendar Availability (new free slots)\n4. Offer available options\n5. Update Calendar Event (using the eventId)\n6. Update Appointment Status \u2192 \"Rescheduled\"\n7. Confirm the changes to the patient\n```\n\n## Flow 3: Cancel an Appointment\n\n```\n1. Find Appointment (get the Google_Event_ID)\n2. Confirm which appointment they want to cancel\n3. Ask for a reason (optional, for the record)\n4. Delete Calendar Event (using the eventId)\n5. Update Appointment Status \u2192 \"Cancelled\"\n6. Confirm the cancellation\n7. Offer to rebook if they wish\n```\n\n## Flow 4: Check an Existing Appointment\n\n```\n1. Find Appointment (by phone number)\n2. Show the details: date, time, type, status\n3. Ask whether they need anything else\n```\n\n---\n\n# BUSINESS RULES\n\n## Scheduling Constraints\n- **Minimum notice**: 2 hours from the current moment\n- **Maximum notice**: 30 days from today\n- **Valid hours**: Only within the clinic's opening hours\n- **No appointments on public holidays**: Check the calendar first\n\n## Mandatory Validations\n1. ALWAYS check availability BEFORE offering times\n2. NEVER invent free slots\n3. ALWAYS save the Google_Event_ID when creating appointments\n4. ALWAYS look up the Google_Event_ID before modifying/cancelling\n5. ALWAYS confirm the details before executing actions\n\n## Current Patient Details\n- **Phone**: {{ $json.phoneNumber }} (use this to look them up in the database)\n- **WhatsApp name**: {{ $json.pushName }}\n\n---\n\n# COMMUNICATION GUIDELINES\n\n## Tone and Style\n- **Professional but approachable**: Convey trust and warmth\n- **Concise**: This is WhatsApp \u2014 short, direct messages\n- **Clear**: Avoid jargon, explain in plain language\n- **Empathetic**: Understand that dental topics can cause anxiety\n\n## Message Formatting\n- Use emojis sparingly (1-2 per message maximum)\n- Recommended emojis: \ud83d\ude0a \ud83e\uddb7 \ud83d\udcc5 \u2705 \u23f0 \ud83d\udccd\n- Use line breaks to improve readability\n- Avoid walls of text\n\n## Tone Examples\n\n**Opening greeting:**\n> Hi! \ud83d\ude0a I'm Sofia, virtual assistant at Bright Smile Dental Clinic. How can I help you today?\n\n**Appointment confirmation:**\n> Perfect! Your appointment is booked:\n>\n> \ud83d\udcc5 Friday 24 January\n> \u23f0 10:30\n> \ud83e\uddb7 Dental cleaning\n> \ud83d\udccd [Clinic address]\n>\n> We'll send you a reminder 24h beforehand. See you then!\n\n**Sign-off:**\n> Thanks for getting in touch! If you have any questions, I'm here. Have a great day! \ud83d\ude0a\n\n---\n\n# HANDLING SPECIAL SITUATIONS\n\n## When to Escalate to a Human (use the Gmail tool)\n\n1. **Specific medical questions**\n   - \"Is it normal for it to hurt after...?\"\n   - \"Can I take [medication] before my appointment?\"\n   - Questions about specific treatments\n\n2. **Complaints**\n   - Dissatisfaction with treatment\n   - Billing problems\n   - Service incidents\n\n3. **Prices and quotes**\n   - Only give general ranges if asked\n   - For detailed quotes: escalate\n\n4. **Genuine emergencies**\n   - Severe pain, swelling, bleeding\n   - Give the emergency phone number AND escalate\n   - \"I'd recommend calling [phone] directly for emergencies\"\n\n5. **Explicit request**\n   - The patient asks to speak to a person\n   - Never push back \u2014 escalate immediately\n\n## Responses for Common Situations\n\n**If they ask about prices:**\n> Prices vary depending on each case. At your first visit the dentist will assess your situation and give you a personalised quote at no obligation. Shall I book you an assessment appointment?\n\n**If it's a dental emergency:**\n> I understand this is urgent. I'd recommend calling our number [PHONE] directly so they can see you as soon as possible. In the meantime, would you like me to book the first available appointment?\n\n**If there's no availability on the requested date:**\n> I'm sorry, we have no free slots that day. I can offer you:\n> - [Option 1: nearest date]\n> - [Option 2: another date]\n> - [Option 3: waiting list]\n> Which would you prefer?\n\n**If the patient has doubts:**\n> It's completely normal to have questions. I'm here to help. What would you like to know?\n\n---\n\n# IMPORTANT REMINDERS\n\n1. **Identify the patient**: Always use the chat's phone number ({{ $json.phoneNumber }}) to look them up in the database\n\n2. **Appointment sequence**:\n   - CREATE: Calendar first \u2192 Sheets afterwards (with the eventId)\n   - MODIFY: Sheets first (get the eventId) \u2192 Calendar afterwards\n   - CANCEL: Sheets first (get the eventId) \u2192 Calendar afterwards\n\n3. **Verify before acting**: Always confirm details with the patient before creating, modifying or cancelling\n\n4. **Conversation memory**: Remember the conversation context so you don't repeat questions\n\n5. **Date format**: Use the patient's local convention (day/month/year, 24h)\n   - Good: \"Friday 24 January at 10:30\"\n   - Bad: \"1/24 at 10:30 AM\"\n\n---\n\n# STARTING A CONVERSATION\n\nWhen you receive the patient's first message:\n1. Greet them warmly\n2. Introduce yourself briefly\n3. Ask how you can help\n4. If the message already states an intent (e.g. \"I want an appointment\"), respond directly to that need\n\nRemember that today is: {{ $now }}"
        }
      },
      "id": "ai_agent",
      "name": "Virtual Receptionist",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "typeVersion": 1.7,
      "position": [
        3568,
        -624
      ],
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "model": {
          "__rl": true,
          "value": "gpt-4o",
          "mode": "list",
          "cachedResultName": "gpt-4o"
        },
        "options": {
          "maxTokens": 1024,
          "temperature": 0.7
        }
      },
      "id": "openai_model",
      "name": "OpenAI GPT-4o",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "typeVersion": 1.2,
      "position": [
        2832,
        -448
      ],
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "descriptionType": "manual",
        "toolDescription": "Check availability by fetching the busy events from Google Calendar.",
        "operation": "getAll",
        "timeMin": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('After', ``, 'string') }}",
        "timeMax": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Before', ``, 'string') }}",
        "options": {}
      },
      "id": "tool_calendar",
      "name": "Check Calendar Availability",
      "type": "n8n-nodes-base.googleCalendarTool",
      "typeVersion": 1.3,
      "position": [
        2960,
        96
      ],
      "credentials": {
        "googleCalendarOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "sendTo": "=team@your-clinic.example",
        "subject": "\ud83d\udd14 WhatsApp Escalation - Dental Clinic",
        "emailType": "text",
        "message": "=WHATSAPP CONVERSATION ESCALATION\n\nPatient: {{ $('Normalize Message').item.json.pushName }}\nPhone: {{ $('Normalize Message').item.json.phoneNumber }}\nDate/Time: {{ $now.toFormat('dd/MM/yyyy HH:mm') }}\n\nPatient's last message:\n{{ $('Normalize Message').item.json.chatInput }}\n\nEscalation reason: [The agent will determine the reason]\n\nPlease contact the patient as soon as possible.",
        "options": {}
      },
      "id": "gmail-tool",
      "name": "Escalate to Human",
      "type": "n8n-nodes-base.gmailTool",
      "typeVersion": 2.2,
      "position": [
        4368,
        96
      ],
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "resource": "audio",
        "operation": "transcribe",
        "options": {
          "language": "es"
        }
      },
      "id": "transcribe_openai",
      "name": "Transcribe with Whisper",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "typeVersion": 2.1,
      "position": [
        1824,
        -272
      ],
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "descriptionType": "manual",
        "toolDescription": "Create an event in Google Calendar when the patient wants to book a new appointment with the dentist.",
        "start": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Start', ``, 'string') }}",
        "end": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('End', ``, 'string') }}",
        "additionalFields": {
          "description": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Description', ``, 'string') }}",
          "summary": "=APPT-{{ $fromAI('patient_name', ``, 'string') }}"
        }
      },
      "id": "calendar_crear",
      "name": "Create Calendar Event",
      "type": "n8n-nodes-base.googleCalendarTool",
      "typeVersion": 1.3,
      "position": [
        3120,
        96
      ],
      "credentials": {
        "googleCalendarOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "descriptionType": "manual",
        "toolDescription": "Update an event in Google Calendar when a patient asks to change their appointment time. Use the Google event ID to locate the event to modify.",
        "operation": "update",
        "eventId": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Event_ID', ``, 'string') }}",
        "updateFields": {
          "end": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('End', ``, 'string') }}",
          "start": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Start', ``, 'string') }}"
        }
      },
      "id": "calendar_actualizar",
      "name": "Update Calendar Event",
      "type": "n8n-nodes-base.googleCalendarTool",
      "typeVersion": 1.3,
      "position": [
        3296,
        96
      ],
      "credentials": {
        "googleCalendarOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "descriptionType": "manual",
        "toolDescription": "Use this tool to delete an event from Google Calendar when the patient says they no longer want the appointment.",
        "operation": "delete",
        "eventId": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Event_ID', ``, 'string') }}",
        "options": {}
      },
      "id": "calendar_eliminar",
      "name": "Delete Calendar Event",
      "type": "n8n-nodes-base.googleCalendarTool",
      "typeVersion": 1.3,
      "position": [
        3456,
        96
      ],
      "credentials": {
        "googleCalendarOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "descriptionType": "manual",
        "toolDescription": "Get row(s) in sheet in Google Sheets",
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "sheetName": {
          "__rl": true,
          "value": "",
          "mode": "list",
          "cachedResultName": "",
          "cachedResultUrl": ""
        }
      },
      "id": "sheets_buscar_paciente",
      "name": "Find Patient",
      "type": "n8n-nodes-base.googleSheetsTool",
      "typeVersion": 4.5,
      "position": [
        3600,
        96
      ],
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "descriptionType": "manual",
        "toolDescription": "Register a new patient in the database. Requires name and phone. Optional: email, date of birth, notes.",
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "=YOUR_GOOGLE_SHEET_ID",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Patients"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "ID": "=PAT-{{ Date.now() }}",
            "Name": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Name', `Patient's full name`, 'string') }}",
            "Phone": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Phone', `Phone number including country code`, 'string') }}",
            "Email": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Email', `Patient's email (optional)`, 'string') }}",
            "Date_Of_Birth": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Date_Of_Birth', `Date of birth DD/MM/YYYY (optional)`, 'string') }}",
            "Notes": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Notes', `Additional notes (optional)`, 'string') }}",
            "Registered_At": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Registered_At', `current date and time.`, 'string') }}"
          },
          "matchingColumns": [],
          "schema": [
            {
              "id": "ID",
              "displayName": "ID",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Name",
              "displayName": "Name",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Phone",
              "displayName": "Phone",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Email",
              "displayName": "Email",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Date_Of_Birth",
              "displayName": "Date_Of_Birth",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Notes",
              "displayName": "Notes",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Registered_At",
              "displayName": "Registered_At",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            }
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {}
      },
      "id": "sheets_registrar_paciente",
      "name": "Register Patient",
      "type": "n8n-nodes-base.googleSheetsTool",
      "typeVersion": 4.5,
      "position": [
        4032,
        96
      ],
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "descriptionType": "manual",
        "toolDescription": "Find a patient's appointments by phone number. IMPORTANT: returns the Google_Event_ID you need in order to modify or cancel appointments in Google Calendar.",
        "documentId": {
          "__rl": true,
          "value": "YOUR_GOOGLE_SHEET_ID",
          "mode": "list",
          "cachedResultName": "CONTROL CITAS DENTISTA",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit?usp=drivesdk"
        },
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Appointments"
        },
        "filtersUI": {
          "values": [
            {
              "lookupColumn": "Patient_Phone",
              "lookupValue": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('values0_Value', ``, 'string') }}"
            }
          ]
        },
        "options": {}
      },
      "id": "sheets_buscar_cita",
      "name": "Find Appointment",
      "type": "n8n-nodes-base.googleSheetsTool",
      "typeVersion": 4.5,
      "position": [
        3760,
        96
      ],
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "descriptionType": "manual",
        "toolDescription": "Save a new appointment in the database. IMPORTANT: you MUST create the event in Google Calendar first and get its event_id, then use this tool.",
        "operation": "append",
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "=YOUR_GOOGLE_SHEET_ID"
        },
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Appointments"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "ID": "=APPT-{{ Date.now() }}",
            "Google_Event_ID": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Google_Event_ID', `Google Calendar event ID (REQUIRED)`, 'string') }}",
            "Patient_Name": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Patient_Name', `Patient name`, 'string') }}",
            "Patient_Phone": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Patient_Phone', `Patient phone number`, 'string') }}",
            "Appointment_Date": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Appointment_Date', `Appointment date DD/MM/YYYY`, 'string') }}",
            "Appointment_Time": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Appointment_Time', `Appointment time HH:MM`, 'string') }}",
            "Appointment_Type": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Appointment_Type', `Appointment type (cleaning, check-up, etc.)`, 'string') }}",
            "Notes": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Notes', `Additional notes (optional)`, 'string') }}",
            "Reminder_24h": "FALSE",
            "Reminder_1h": "FALSE",
            "Created_At": "={{ $now.toFormat('dd/MM/yyyy HH:mm') }}",
            "Status": "Pending"
          },
          "matchingColumns": [],
          "schema": [
            {
              "id": "ID",
              "displayName": "ID",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Google_Event_ID",
              "displayName": "Google_Event_ID",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Patient_Name",
              "displayName": "Patient_Name",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Patient_Phone",
              "displayName": "Patient_Phone",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Appointment_Date",
              "displayName": "Appointment_Date",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Appointment_Time",
              "displayName": "Appointment_Time",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Appointment_Type",
              "displayName": "Appointment_Type",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Status",
              "displayName": "Status",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Notes",
              "displayName": "Notes",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Reminder_24h",
              "displayName": "Reminder_24h",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Reminder_1h",
              "displayName": "Reminder_1h",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Created_At",
              "displayName": "Created_At",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            }
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {}
      },
      "id": "sheets_guardar_cita",
      "name": "Save Appointment",
      "type": "n8n-nodes-base.googleSheetsTool",
      "typeVersion": 4.5,
      "position": [
        3888,
        96
      ],
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "descriptionType": "manual",
        "toolDescription": "Update an appointment's status (Confirmed, Rescheduled, Cancelled, Completed). Use AFTER modifying or deleting the event in Google Calendar.",
        "operation": "update",
        "documentId": {
          "__rl": true,
          "value": "=YOUR_GOOGLE_SHEET_ID",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Appointments"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "Status": "={{ $fromAI(\"new_status\", \"New status: Confirmed, Rescheduled, Cancelled or Completed\", \"string\") }}",
            "row_number": 0,
            "ID": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('ID__using_to_match_', ``, 'string') }}",
            "Google_Event_ID": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Google_Event_ID', ``, 'string') }}",
            "Patient_Name": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Patient_Name', ``, 'string') }}",
            "Patient_Phone": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Patient_Phone', ``, 'string') }}",
            "Appointment_Type": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Appointment_Type', ``, 'string') }}",
            "Appointment_Time": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Appointment_Time', ``, 'string') }}",
            "Appointment_Date": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Appointment_Date', ``, 'string') }}",
            "Notes": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Notes', ``, 'string') }}",
            "Reminder_24h": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Reminder_24h', ``, 'string') }}",
            "Reminder_1h": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Reminder_1h', ``, 'string') }}",
            "Created_At": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Created_At', ``, 'string') }}"
          },
          "matchingColumns": [
            "Patient_Phone"
          ],
          "schema": [
            {
              "id": "ID",
              "displayName": "ID",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "Google_Event_ID",
              "displayName": "Google_Event_ID",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "Patient_Name",
              "displayName": "Patient_Name",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "Patient_Phone",
              "displayName": "Patient_Phone",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "Appointment_Date",
              "displayName": "Appointment_Date",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "Appointment_Time",
              "displayName": "Appointment_Time",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "Appointment_Type",
              "displayName": "Appointment_Type",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "Status",
              "displayName": "Status",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Notes",
              "displayName": "Notes",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "Reminder_24h",
              "displayName": "Reminder_24h",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "Reminder_1h",
              "displayName": "Reminder_1h",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "Created_At",
              "displayName": "Created_At",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "row_number",
              "displayName": "row_number",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "number",
              "canBeUsedToMatch": true,
              "readOnly": true,
              "removed": false
            }
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {}
      },
      "id": "sheets_actualizar_cita",
      "name": "Update Appointment Status",
      "type": "n8n-nodes-base.googleSheetsTool",
      "typeVersion": 4.5,
      "position": [
        4192,
        96
      ],
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "resource": "messages-api",
        "instanceName": "={{ $('Normalize Message').item.json.instanceName }}",
        "remoteJid": "={{ $('Normalize Message').item.json.phoneNumber }}@s.whatsapp.net",
        "messageText": "={{ $json.guardrailsInput }}",
        "options_message": {}
      },
      "type": "n8n-nodes-evolution-api.evolutionApi",
      "typeVersion": 1,
      "position": [
        4784,
        -624
      ],
      "id": "98be4151-7b6e-466f-9e2e-960421be931d",
      "name": "Send Text",
      "credentials": {
        "evolutionApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "resource": "chat-api",
        "operation": "get-media-base64"
      },
      "type": "n8n-nodes-evolution-api.evolutionApi",
      "typeVersion": 1,
      "position": [
        1552,
        -272
      ],
      "id": "804c90a7-fc04-4150-a33b-86c8ca6c4d9c",
      "name": "Get Media As Base64",
      "credentials": {
        "evolutionApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "operation": "sanitize",
        "text": "={{ $json.output }}",
        "guardrails": {
          "pii": {
            "value": {
              "type": "all"
            }
          },
          "secretKeys": {
            "value": {
              "permissiveness": "balanced"
            }
          },
          "urls": {
            "value": {
              "allowedUrls": ""
            }
          }
        }
      },
      "type": "@n8n/n8n-nodes-langchain.guardrails",
      "typeVersion": 2,
      "position": [
        4208,
        -624
      ],
      "id": "27bc8717-b917-491d-8240-c6ec2dac6104",
      "name": "Output Guardrails"
    },
    {
      "parameters": {
        "text": "={{ $json.chatInput }}",
        "guardrails": {
          "nsfw": {
            "value": {
              "threshold": 0.7
            }
          }
        }
      },
      "type": "@n8n/n8n-nodes-langchain.guardrails",
      "typeVersion": 2,
      "position": [
        2368,
        -608
      ],
      "id": "077040f8-1e25-4784-954f-35d663f7f0cf",
      "name": "Input Guardrails"
    },
    {
      "parameters": {
        "resource": "messages-api",
        "instanceName": "={{ $('Normalize Message').item.json.instanceName }}",
        "remoteJid": "={{ $('Normalize Message').item.json.phoneNumber }}@s.whatsapp.net",
        "messageText": "Sorry, I can't help you with that.",
        "options_message": {}
      },
      "type": "n8n-nodes-evolution-api.evolutionApi",
      "typeVersion": 1,
      "position": [
        2752,
        -144
      ],
      "id": "82008ccd-0b93-41e5-a9ab-a3aa02967f8c",
      "name": "Send Blocked Reply",
      "credentials": {
        "evolutionApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "content": "# \ud83d\udcca Google Sheets Template\n\nThis workflow is backed by a Google Sheet with two tabs: **Patients** and **Appointments**.\n\n---\n\n### \u26a0\ufe0f Before you run it\n\n1. Create your own spreadsheet with the columns documented in the README\n2. Replace the `documentId` in every Google Sheets node with your own sheet ID\n3. Connect your own OpenAI, Google and Evolution API credentials\n\n*The credential IDs shipped in this export will not resolve in your n8n instance.*",
        "height": 464,
        "width": 816
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        3488,
        272
      ],
      "id": "5471d2d4-efb8-4588-89c6-f6b6bd7c8506",
      "name": "Sticky Note"
    },
    {
      "parameters": {
        "sessionIdType": "customKey",
        "sessionKey": "={{ $('Normalize Message').item.json.phoneNumber }}"
      },
      "type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
      "typeVersion": 1.3,
      "position": [
        2960,
        -272
      ],
      "id": "4e17296f-217b-4349-a9f9-466ca400990e",
      "name": "Simple Memory"
    },
    {
      "parameters": {
        "model": {
          "__rl": true,
          "value": "gpt-4.1",
          "mode": "list",
          "cachedResultName": "gpt-4.1"
        },
        "builtInTools": {},
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "typeVersion": 1.3,
      "position": [
        2192,
        -352
      ],
      "id": "64591599-4c39-42f1-b6ed-5b8178f66a61",
      "name": "OpenAI Chat Model",
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      }
    }
  ],
  "connections": {
    "Evolution API Webhook": {
      "main": [
        [
          {
            "node": "Private Chats Only",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Private Chats Only": {
      "main": [
        [
          {
            "node": "Message Type",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Message Type": {
      "main": [
        [
          {
            "node": "Normalize Message",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Get Media As Base64",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Message": {
      "main": [
        [
          {
            "node": "Input Guardrails",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI GPT-4o": {
      "ai_languageModel": [
        [
          {
            "node": "Virtual Receptionist",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Virtual Receptionist": {
      "main": [
        [
          {
            "node": "Output Guardrails",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Calendar Availability": {
      "ai_tool": [
        [
          {
            "node": "Virtual Receptionist",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Escalate to Human": {
      "ai_tool": [
        [
          {
            "node": "Virtual Receptionist",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Transcribe with Whisper": {
      "main": [
        [
          {
            "node": "Normalize Message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Calendar Event": {
      "ai_tool": [
        [
          {
            "node": "Virtual Receptionist",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Update Calendar Event": {
      "ai_tool": [
        [
          {
            "node": "Virtual Receptionist",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Delete Calendar Event": {
      "ai_tool": [
        [
          {
            "node": "Virtual Receptionist",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Find Patient": {
      "ai_tool": [
        [
          {
            "node": "Virtual Receptionist",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Register Patient": {
      "ai_tool": [
        [
          {
            "node": "Virtual Receptionist",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Find Appointment": {
      "ai_tool": [
        [
          {
            "node": "Virtual Receptionist",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Save Appointment": {
      "ai_tool": [
        [
          {
            "node": "Virtual Receptionist",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Update Appointment Status": {
      "ai_tool": [
        [
          {
            "node": "Virtual Receptionist",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Send Text": {
      "main": [
        []
      ]
    },
    "Get Media As Base64": {
      "main": [
        [
          {
            "node": "Transcribe with Whisper",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Output Guardrails": {
      "main": [
        [
          {
            "node": "Send Text",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Input Guardrails": {
      "main": [
        [
          {
            "node": "Virtual Receptionist",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Send Blocked Reply",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Simple Memory": {
      "ai_memory": [
        [
          {
            "node": "Virtual Receptionist",
            "type": "ai_memory",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "Input Guardrails",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": true,
  "settings": {
    "executionOrder": "v1",
    "timezone": "Europe/Madrid",
    "callerPolicy": "workflowsFromSameOwner",
    "availableInMCP": false,
    "timeSavedMode": "fixed"
  },
  "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

Dental Clinic - WhatsApp Receptionist. Uses agent, lmChatOpenAi, googleCalendarTool, gmailTool. Webhook trigger; 25 nodes.

Source: https://github.com/Sansepio/n8n-dental-clinic-receptionist/blob/main/workflows/whatsapp-receptionist.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

This workflow transforms WhatsApp into a powerful personal AI using n8n + Green-API. Send text or voice messages — the assistant understands intent and handles daily tasks automatically. 💰 Expense & i

Tool Calculator, Google Sheets Tool, OpenAI Chat +10
AI & RAG

📝 Description

HTTP Request, OpenAI, Google Docs Tool +6
AI & RAG

LINE Assistant with Google Calendar and Gmail Integration. Uses agent, memoryBufferWindow, lmChatOpenAi, toolWikipedia. Webhook trigger; 14 nodes.

Agent, Memory Buffer Window, OpenAI Chat +5
AI & RAG

This workflow is for small business owners, personal assistants, or project managers who rely on multiple platforms for communication and scheduling. Ideal for users managing customer support, persona

Agent, Memory Buffer Window, OpenAI Chat +5
AI & RAG

VEP WAPP. Uses openAi, lmChatOpenAi, toolCalculator, agent. Webhook trigger; 100 nodes.

OpenAI, OpenAI Chat, Tool Calculator +7