AutomationFlowsAI & RAG › Alex, AI Whatsapp Receptionist (restaurant Bookings)

Alex, AI Whatsapp Receptionist (restaurant Bookings)

Alex, AI WhatsApp Receptionist (restaurant bookings). Uses googleSheets, agent, lmChatOpenAi, memoryPostgresChat. Webhook trigger; 28 nodes.

Webhook trigger★★★★☆ complexityAI-powered28 nodesGoogle SheetsAgentOpenAI ChatMemory Postgres ChatN8N Nodes WhapiError Trigger
AI & RAG Trigger: Webhook Nodes: 28 Complexity: ★★★★☆ AI nodes: yes Added:

This workflow follows the Agent → Error 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 →

Download .json
{
  "name": "Alex, AI WhatsApp Receptionist (restaurant bookings)",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "restaurant-whatsapp-webhook",
        "options": {}
      },
      "id": "151eae6d-b5ec-414f-a9f8-afc2e05956e2",
      "name": "Webhook Trigger",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2.1,
      "position": [
        64,
        880
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "version": 2
          },
          "conditions": [
            {
              "id": "filter-incoming",
              "leftValue": "={{ $json.body.messages[0].from_me }}",
              "rightValue": false,
              "operator": {
                "type": "boolean",
                "operation": "equals"
              }
            },
            {
              "id": "has-text",
              "leftValue": "={{ $json.body.messages[0].text?.body }}",
              "rightValue": "",
              "operator": {
                "type": "string",
                "operation": "notEmpty"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "f719af0d-6c09-475c-bba9-198c5a857558",
      "name": "Filter Incoming Messages Only",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        288,
        880
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "customer-message",
              "name": "customerMessage",
              "value": "={{ $json.body.messages[0].text.body }}",
              "type": "string"
            },
            {
              "id": "customer-phone",
              "name": "customerPhone",
              "value": "={{ $json.body.messages[0].from }}",
              "type": "string"
            },
            {
              "id": "customer-name",
              "name": "customerName",
              "value": "={{ $json.body.messages[0].from_name || 'Guest' }}",
              "type": "string"
            },
            {
              "id": "chat-id",
              "name": "chatId",
              "value": "={{ $json.body.messages[0].chat_id }}",
              "type": "string"
            },
            {
              "id": "booking-id",
              "name": "bookingId",
              "value": "=RES-{{ $json.body.messages[0].from.slice(-6) }}-{{ Date.now().toString().slice(-6) }}",
              "type": "string"
            },
            {
              "id": "message-timestamp",
              "name": "messageTimestamp",
              "value": "={{ $now.toISO() }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "8a209561-6dcf-4d22-9683-f51e1b7d74f3",
      "name": "Extract Customer Data",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        512,
        784
      ]
    },
    {
      "parameters": {
        "documentId": {
          "__rl": true,
          "value": "PASTE_YOUR_GOOGLE_SHEET_URL_HERE",
          "mode": "url"
        },
        "sheetName": {
          "__rl": true,
          "value": "gid=0",
          "mode": "list",
          "cachedResultName": "Reservations"
        },
        "filtersUI": {
          "values": [
            {
              "lookupColumn": "Phone",
              "lookupValue": "={{ $('Extract Customer Data').first().json.customerPhone }}"
            }
          ]
        },
        "options": {
          "returnFirstMatch": true
        }
      },
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        736,
        784
      ],
      "id": "4f626eba-4425-4dbf-9fd0-2c707c94b370",
      "name": "Get Existing Booking",
      "alwaysOutputData": true
    },
    {
      "parameters": {
        "jsCode": "const customerData = $('Extract Customer Data').first().json;\nconst existing = $('Get Existing Booking').first().json;\n\nif (existing && existing.Booking_ID) {\n  customerData.bookingId = existing.Booking_ID;\n  customerData.existingBooking = existing;\n  customerData.rowNumber = existing['__row_number'];\n}\n\ntry {\n  $execution.customData.set('chatId', customerData.chatId);\n  $execution.customData.set('customerPhone', customerData.customerPhone);\n} catch (e) {}\n\nreturn [customerData];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        960,
        784
      ],
      "id": "65933d91-5a04-45d3-bf0d-7f40c637721a",
      "name": "Merge Data & Set Context"
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "={{ $('Merge Data & Set Context').first().json.customerMessage }}",
        "options": {
          "systemMessage": "=Current Year is 2026.\nToday's date is {{ $now.toFormat('cccc, dd MMMM yyyy') }}.\n\nIDENTITY & STYLE:\nYou are Alex, the warm and professional receptionist at \"The Bistro.\"\n- Greeting Style: Always start warm. If a customer just says \"Hi\" or introduces themselves, greet them by name and say: \"How can I help you today? I can book you a table, check our opening hours, or update an existing reservation.\"\n- IDENTITY QUESTIONS: If the customer asks who they are speaking to, simply say \"I'm Alex, the receptionist here at The Bistro! Is there anything else I can help you with?\", do NOT repeat the full greeting menu again.\n- CRITICAL: Output plain text only. NEVER use markdown like bold (**text**) or headers.\n\nRESTAURANT INFO:\n- Hours: Mon-Thu 11AM-10PM | Fri-Sat 11AM-11PM | Sun 10AM-9PM\n- Cuisine: Contemporary American\n\nBOOKING PROTOCOL & GATING:\nYou need EXACTLY 4 things before a booking is finalized: 1. Name, 2. Date, 3. Time, 4. Party Size.\n- NO NAME, NO BOOKING: If the user has not provided a name, ask naturally before outputting JSON.\n- STRICT RULE: Do not assume a name. Do not confirm until all 4 pieces are known.\n\nLARGE PARTY RULE: If the party size is more than 15 people, do NOT confirm the booking. Instead say: \"For parties of more than 15 guests, we'd love to accommodate you! Please give us a call on +1 (555) 123-4567 and our team will get everything arranged for you.\"\n\nMEMORY & LOOP-BREAKER RULES:\n- READ HISTORY: Use conversation history to remember the guest. Never ask for info already provided.\n- AM/PM ASSUMPTION: If customer says \"7:30\", assume PM (19:30) if AM is outside operating hours.\n- DATE INFERENCE: If the customer says \"tonight\", use today's date. \"Tomorrow\" means tomorrow. Never ask for a date you can infer.\n- THE GO AHEAD RULE: If you summarize and customer says \"Yes\" or \"Go ahead\", output JSON immediately.\n- NO REPEATS: If history shows a recent summary and customer says \"Thanks\", just say \"You're welcome! See you soon.\"\n\nJSON OUTPUT (NEW BOOKINGS ONLY):\nWhen all 4 details are collected, output ONLY this JSON. No extra text.\n{\n\"name\": \"Customer Name\",\n\"date\": \"DD/MM/YYYY\",\n\"time\": \"HH:MM\",\n\"party_size\": number,\n\"special_requests\": \"Requests or None\",\n\"action\": \"new_booking\"\n}\n\nCONFIRMATION MESSAGE (after JSON, on new bookings):\nImmediately after the JSON output, send this confirmation:\nWonderful, all booked for you [name]!\n\nReservation Details\nName: [name]\nDate: [date]\nTime: [time]\nGuests: [party_size]\nSpecial Requests: [special_requests]\n\nWe look forward to seeing you at The Bistro! If anything changes or you need to update your booking, just message us here anytime.\n\nMODIFICATIONS & CANCELLATIONS:\n- MODIFY DATE/TIME/GUESTS: Confirm warmly with updated details, then append [MODIFIED] at the very end.\n- SPECIAL REQUESTS: Confirm warmly, then append [UPDATED] at the very end.\n- CANCEL: Confirm cancellation warmly, then append [CANCELLED] at the very end.\n- For modifications output JSON after [MODIFIED]:\n{\n\"name\": \"Customer Name\",\n\"date\": \"DD/MM/YYYY\",\n\"time\": \"HH:MM\",\n\"party_size\": number,\n\"special_requests\": \"Requests or None\",\n\"action\": \"modify_booking\"\n}\n\nNORMAL CONVERSATION:\nFor general questions, reply with friendly concise text. No JSON."
        }
      },
      "id": "24ee80a7-2370-419b-a88d-13e8c03bf9e6",
      "name": "AI Receptionist Agent",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "typeVersion": 1.9,
      "position": [
        1184,
        784
      ]
    },
    {
      "parameters": {
        "model": {
          "__rl": true,
          "value": "gpt-5.2",
          "mode": "list",
          "cachedResultName": "gpt-5.2"
        },
        "builtInTools": {},
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "typeVersion": 1.3,
      "position": [
        1168,
        992
      ],
      "id": "24d0d090-e198-4338-869a-2e87de8d5ad9",
      "name": "OpenAI Chat Model"
    },
    {
      "parameters": {
        "sessionIdType": "customKey",
        "sessionKey": "={{ $('Merge Data & Set Context').first().json.customerPhone }}",
        "contextWindowLength": 10
      },
      "type": "@n8n/n8n-nodes-langchain.memoryPostgresChat",
      "typeVersion": 1.3,
      "position": [
        1312,
        992
      ],
      "id": "32823501-00f6-4502-b6ac-d37a51381248",
      "name": "Postgres Chat Memory"
    },
    {
      "parameters": {
        "jsCode": "const aiNode = $('AI Receptionist Agent').first().json;\nconst output = aiNode.output || '';\n\nconst jsonMatch = output.match(/\\{[\\s\\S]*\\}/);\nlet booking = null;\nlet hasJson = false;\n\nif (jsonMatch) {\n  try { booking = JSON.parse(jsonMatch[0]); hasJson = true; } \n  catch (e) { booking = null; hasJson = false; }\n}\n\nconst isModified = output.includes('[MODIFIED]');\nconst isCancelled = output.includes('[CANCELLED]');\nconst isUpdated = output.includes('[UPDATED]');\n\nlet cleanResponse = output\n  .replace(/\\{[\\s\\S]*\\}/g, '')\n  .replace(/\\[(CONFIRMED|MODIFIED|CANCELLED|UPDATED)\\]/g, '')\n  .trim();\n\nif (cleanResponse.length < 2) {\n  if (isCancelled) cleanResponse = \"I've cancelled that for you.\";\n  else if (isModified) cleanResponse = \"I've updated your reservation details.\";\n  else if (isUpdated) cleanResponse = \"I've noted your special request.\";\n  else if (hasJson && booking?.action === 'new_booking') cleanResponse = \"Perfect! Your reservation is confirmed.\";\n  else cleanResponse = \"Thanks for your message! How else can I help?\";\n}\n\nconst context = $('Merge Data & Set Context').first().json;\n\nreturn {\n  cleanResponse,\n  bookingId: booking?.booking_id || context.bookingId,\n  name: booking?.name || null,\n  date: booking?.date || null,\n  time: booking?.time || null,\n  partySize: booking?.party_size || null,\n  specialRequests: booking?.special_requests || null,\n  hasJson: hasJson,\n  isNewBooking: (booking?.action === 'new_booking'),\n  isModified: isModified,\n  isCancelled: isCancelled,\n  isSpecialRequestUpdate: (isUpdated && !isModified),\n  action: booking?.action || 'conversation',\n  chatId: context.chatId,\n  customerPhone: context.customerPhone,\n  rowNumber: context.rowNumber\n};"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1504,
        784
      ],
      "id": "751b9441-abb0-48a4-b152-a36d1badcdbf",
      "name": "Parse AI Response"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "has-json",
              "leftValue": "={{ $('Parse AI Response').first().json.hasJson }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "dd2b7b18-34ce-4ef8-a9f6-89235d0ae7d4",
      "name": "Has JSON?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        2352,
        1200
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "is-new",
              "leftValue": "={{ $('Parse AI Response').first().json.action }}",
              "rightValue": "new_booking",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "071a79f9-5925-4c65-a1d3-a5acfc8c9b2b",
      "name": "Route: Action Type",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        2544,
        848
      ]
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "PASTE_YOUR_GOOGLE_SHEET_URL_HERE",
          "mode": "url"
        },
        "sheetName": {
          "__rl": true,
          "value": "gid=0",
          "mode": "list",
          "cachedResultName": "Reservations"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "Customer Name": "={{ $('Parse AI Response').first().json.name }}",
            "Phone": "={{ $('Parse AI Response').first().json.customerPhone }}",
            "Date and Time": "={{ $('Parse AI Response').first().json.date }} at {{ $('Parse AI Response').first().json.time }}",
            "No.of guests": "={{ $('Parse AI Response').first().json.partySize }}",
            "Date and time added": "={{ $now.toFormat('dd/MM/yyyy HH:mm') }}",
            "Special Requests": "={{ $('Parse AI Response').first().json.specialRequests || 'None' }}",
            "Booking_ID": "={{ $('Parse AI Response').first().json.bookingId }}",
            "Status": "Confirmed"
          },
          "matchingColumns": [],
          "schema": [
            {
              "id": "Customer Name",
              "displayName": "Customer 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": "Date and Time",
              "displayName": "Date and Time",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "No.of guests",
              "displayName": "No.of guests",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Date and time added",
              "displayName": "Date and time added",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Special Requests",
              "displayName": "Special Requests",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Booking_ID",
              "displayName": "Booking_ID",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Status",
              "displayName": "Status",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            }
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {}
      },
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        2944,
        736
      ],
      "id": "e044f29f-0acf-48bd-95aa-a5a5c3a17bff",
      "name": "Save New Booking"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 3
          },
          "conditions": [
            {
              "id": "is-modified",
              "leftValue": "={{ $('Parse AI Response').first().json.isModified }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "looseTypeValidation": true,
        "options": {
          "ignoreCase": false
        }
      },
      "id": "41068d92-28b9-47cd-b7dc-a903102f1383",
      "name": "Is Modified?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.3,
      "position": [
        1728,
        784
      ]
    },
    {
      "parameters": {
        "operation": "update",
        "documentId": {
          "__rl": true,
          "value": "PASTE_YOUR_GOOGLE_SHEET_URL_HERE",
          "mode": "url"
        },
        "sheetName": {
          "__rl": true,
          "value": "gid=0",
          "mode": "list",
          "cachedResultName": "Reservations"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "Date and Time": "={{ $('Parse AI Response').first().json.date }} at {{ $('Parse AI Response').first().json.time }}",
            "No.of guests": "={{ $('Parse AI Response').first().json.partySize }}",
            "Special Requests": "={{ $('Parse AI Response').first().json.specialRequests }}",
            "Status": "Modified",
            "row_number": 0,
            "Phone": "={{ $('Parse AI Response').first().json.customerPhone }}"
          },
          "matchingColumns": [
            "Phone"
          ],
          "schema": [
            {
              "id": "Customer Name",
              "displayName": "Customer Name",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": true
            },
            {
              "id": "Phone",
              "displayName": "Phone",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "Date and Time",
              "displayName": "Date and Time",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "No.of guests",
              "displayName": "No.of guests",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Date and time added",
              "displayName": "Date and time added",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": true
            },
            {
              "id": "Special Requests",
              "displayName": "Special Requests",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Booking_ID",
              "displayName": "Booking_ID",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": true
            },
            {
              "id": "Status",
              "displayName": "Status",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "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": {}
      },
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        2944,
        368
      ],
      "id": "d4e9cf20-a375-4750-aba2-c2c6079786b6",
      "name": "Update Booking"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 3
          },
          "conditions": [
            {
              "id": "is-cancelled",
              "leftValue": "={{ $('Parse AI Response').first().json.isCancelled }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "looseTypeValidation": true,
        "options": {}
      },
      "id": "a909ef97-5c71-4b34-9f7a-6f0b859fd3b4",
      "name": "Is Cancelled?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.3,
      "position": [
        1920,
        928
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 3
          },
          "conditions": [
            {
              "id": "is-special",
              "leftValue": "={{ $('Parse AI Response').first().json.isSpecialRequestUpdate }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "looseTypeValidation": true,
        "options": {}
      },
      "id": "501d3453-bf53-4c81-b8fd-aebe9d8f6493",
      "name": "Is Special Request?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.3,
      "position": [
        2128,
        1088
      ]
    },
    {
      "parameters": {
        "operation": "update",
        "documentId": {
          "__rl": true,
          "value": "PASTE_YOUR_GOOGLE_SHEET_URL_HERE",
          "mode": "url"
        },
        "sheetName": {
          "__rl": true,
          "value": "gid=0",
          "mode": "list",
          "cachedResultName": "Reservations"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "Special Requests": "={{ $('Merge Data & Set Context').first().json.customerMessage }}",
            "Status": "Modified",
            "Phone": "={{ $('Parse AI Response').first().json.customerPhone }}"
          },
          "matchingColumns": [
            "Phone"
          ],
          "schema": [
            {
              "id": "Customer Name",
              "displayName": "Customer Name",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": true
            },
            {
              "id": "Phone",
              "displayName": "Phone",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "Date and Time",
              "displayName": "Date and Time",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": true
            },
            {
              "id": "No.of guests",
              "displayName": "No.of guests",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": true
            },
            {
              "id": "Date and time added",
              "displayName": "Date and time added",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": true
            },
            {
              "id": "Special Requests",
              "displayName": "Special Requests",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Booking_ID",
              "displayName": "Booking_ID",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": true
            },
            {
              "id": "Status",
              "displayName": "Status",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "row_number",
              "displayName": "row_number",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "number",
              "canBeUsedToMatch": true,
              "readOnly": true,
              "removed": true
            }
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {}
      },
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        2944,
        1072
      ],
      "id": "7c5dfd7e-4310-4f1a-b8c1-eb9771e58617",
      "name": "Update Special Requests"
    },
    {
      "parameters": {
        "jsCode": "const data = $('Parse AI Response').first().json;\nreturn {\n  chatId: data.chatId,\n  message: data.cleanResponse,\n  type: data.isNewBooking ? 'new' : data.isModified ? 'modified' : data.isCancelled ? 'cancelled' : data.isSpecialRequestUpdate ? 'special' : 'chat'\n};"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3328,
        864
      ],
      "id": "c8808180-4aa1-4f9b-a5df-52c3ac0fb5bc",
      "name": "Prepare Message"
    },
    {
      "parameters": {
        "operation": "send",
        "to": "={{ $('Prepare Message').first().json.chatId }}",
        "body": "={{ $('Prepare Message').first().json.message }}"
      },
      "type": "n8n-nodes-whapi.whapi",
      "typeVersion": 1,
      "position": [
        3520,
        864
      ],
      "id": "d39ada35-3a7c-4fec-b398-d0530a67d6f1",
      "name": "Send WhatsApp Message"
    },
    {
      "parameters": {},
      "id": "fe5701b9-40d0-48db-a052-b3352e41aa52",
      "name": "No Response",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [
        512,
        976
      ]
    },
    {
      "parameters": {},
      "id": "5626a607-1c12-4284-803b-ef0c6f8c8ba4",
      "name": "Error Trigger",
      "type": "n8n-nodes-base.errorTrigger",
      "typeVersion": 1,
      "position": [
        64,
        1312
      ]
    },
    {
      "parameters": {
        "operation": "send",
        "to": "={{ $('Extract Customer Data').first().json.chatId }}",
        "body": "Sorry, something went wrong. Please call us on +1 (555) 123-4567."
      },
      "type": "n8n-nodes-whapi.whapi",
      "typeVersion": 1,
      "position": [
        288,
        1312
      ],
      "id": "b3bd43af-3ece-4a4d-a00c-6893e53ddd4e",
      "name": "Send Error Fallback"
    },
    {
      "parameters": {
        "operation": "update",
        "documentId": {
          "__rl": true,
          "value": "PASTE_YOUR_GOOGLE_SHEET_URL_HERE",
          "mode": "url"
        },
        "sheetName": {
          "__rl": true,
          "value": "gid=0",
          "mode": "list",
          "cachedResultName": "Reservations",
          "cachedResultUrl": "PASTE_YOUR_GOOGLE_SHEET_URL_HERE"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "Status": "Cancelled",
            "Phone": "={{ $('Parse AI Response').first().json.customerPhone }}"
          },
          "matchingColumns": [
            "Phone"
          ],
          "schema": [
            {
              "id": "Customer Name",
              "displayName": "Customer Name",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": true
            },
            {
              "id": "Phone",
              "displayName": "Phone",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Date and Time",
              "displayName": "Date and Time",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": true
            },
            {
              "id": "No.of guests",
              "displayName": "No.of guests",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": true
            },
            {
              "id": "Date and time added",
              "displayName": "Date and time added",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": true
            },
            {
              "id": "Special Requests",
              "displayName": "Special Requests",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": true
            },
            {
              "id": "Booking_ID",
              "displayName": "Booking_ID",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": true
            },
            {
              "id": "Status",
              "displayName": "Status",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            }
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {}
      },
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        2944,
        544
      ],
      "id": "da027047-d16d-4ec4-98a7-4fb1e0ed4ae2",
      "name": "Cancel Booking"
    },
    {
      "parameters": {
        "content": "## Safety Net\n**If any node fails, the Error Trigger fires and sends a polite fallback message asking the customer to call the restaurant directly. Never leave a customer on read.**",
        "height": 336,
        "width": 512,
        "color": 3
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        0,
        1168
      ],
      "id": "1a3a20ca-819e-41ca-ba0d-14dca16fd963",
      "name": "Sticky Note4"
    },
    {
      "parameters": {
        "content": "## Input & Memory \n**This section captures the incoming WhatsApp message, filters out noise, and extracts customer data. A Google Sheets lookup retrieves any existing booking for this customer, which is then merged with session context before being passed to the AI.**",
        "height": 544,
        "width": 896,
        "color": 6
      },
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        0,
        592
      ],
      "typeVersion": 1,
      "id": "f0009a4e-8369-47f7-b898-6a68c44fca55",
      "name": "Sticky Note1"
    },
    {
      "parameters": {
        "content": "## The Brain (AI & Logic)\n**The engine room. The AI Agent processes the customer message with full conversation memory via Postgres/Supabase. The Parse node extracts structured data and sets boolean flags (isModified, isCancelled, isSpecialRequestUpdate) that drive all downstream routing.**",
        "height": 544,
        "width": 720
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        928,
        592
      ],
      "id": "526f4989-1eaa-47b4-a2b2-173131ee06a1",
      "name": "Sticky Note2"
    },
    {
      "parameters": {
        "content": "## Database & Fulfillment\n**The hands of the bot. Routing checks flags in order: Modified \u2192 Cancelled \u2192 Special Request \u2192 New Booking \u2192 Regular Response. Each path updates Google Sheets accordingly before funnelling into a single Prepare Message node and one Send WhatsApp Message node.**",
        "height": 1104,
        "width": 2000,
        "color": 4
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1696,
        272
      ],
      "id": "7fceaff2-4902-4375-b131-81a431812773",
      "name": "Sticky Note3"
    },
    {
      "parameters": {
        "content": "## Alex: AI Restaurant Receptionist (WhatsApp)\n**This workflow handles end-to-end restaurant bookings, modifications, and cancellations with persistent memory.**\n\n\ud83d\udd27**Initial Setup Checklist**\n\n**Google Sheets**: Customer Name, Phone, Date and Time, No.of guests, Date and time added, Special Requests, Booking_ID, Status\n\n**Whapi.cloud**: Connect your WhatsApp, copy the API Token, and paste it into the Whapi Auth credentials.\n\n**OpenAI**: Add your API Key. \n\n**Webhook**: Set the Webhook path to restaurant-whatsapp-webhook. Copy the Production URL and paste it into your Whapi Webhook settings.\n\n\ud83e\udde0 **Logic Overview**\n\n**Memory**: Uses Postgres Chat Memory (Supabase) to automatically store and retrieve the last 10 messages per customer. No manual read/write nodes needed - memory is handled natively by the AI Agent.\n\n**The Router**: Uses boolean flags from a custom Parse node to detect the customer's intent, new booking, modification, cancellation, or special request, and routes to the correct database action automatically.\n\n**Booking Lookup**: On every message, the workflow checks Google Sheets for an existing booking under that phone number. This ID is carried through the session so modifications and cancellations always update the correct row.\n\n**Error Handling**: If the AI hallucinates or a node fails, the \"Error Fallback\" path ensures the customer isn't ghosted.",
        "height": 544,
        "width": 816,
        "color": 5
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        0,
        0
      ],
      "id": "fc0c63a2-d794-4e84-9dc7-bf3c6efedfce",
      "name": "Sticky Note"
    }
  ],
  "connections": {
    "Webhook Trigger": {
      "main": [
        [
          {
            "node": "Filter Incoming Messages Only",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter Incoming Messages Only": {
      "main": [
        [
          {
            "node": "Extract Customer Data",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "No Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Customer Data": {
      "main": [
        [
          {
            "node": "Get Existing Booking",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Existing Booking": {
      "main": [
        [
          {
            "node": "Merge Data & Set Context",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge Data & Set Context": {
      "main": [
        [
          {
            "node": "AI Receptionist Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Receptionist Agent": {
      "main": [
        [
          {
            "node": "Parse AI Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "AI Receptionist Agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Postgres Chat Memory": {
      "ai_memory": [
        [
          {
            "node": "AI Receptionist Agent",
            "type": "ai_memory",
            "index": 0
          }
        ]
      ]
    },
    "Parse AI Response": {
      "main": [
        [
          {
            "node": "Is Modified?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Has JSON?": {
      "main": [
        [
          {
            "node": "Route: Action Type",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Prepare Message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Route: Action Type": {
      "main": [
        [
          {
            "node": "Save New Booking",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Prepare Message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Save New Booking": {
      "main": [
        [
          {
            "node": "Prepare Message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is Modified?": {
      "main": [
        [
          {
            "node": "Update Booking",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Is Cancelled?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update Booking": {
      "main": [
        [
          {
            "node": "Prepare Message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is Cancelled?": {
      "main": [
        [
          {
            "node": "Cancel Booking",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Is Special Request?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is Special Request?": {
      "main": [
        [
          {
            "node": "Update Special Requests",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Has JSON?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update Special Requests": {
      "main": [
        [
          {
            "node": "Prepare Message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Message": {
      "main": [
        [
          {
            "node": "Send WhatsApp Message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Error Trigger": {
      "main": [
        [
          {
            "node": "Send Error Fallback",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Cancel Booking": {
      "main": [
        [
          {
            "node": "Prepare Message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  }
}
Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

Alex, AI WhatsApp Receptionist (restaurant bookings). Uses googleSheets, agent, lmChatOpenAi, memoryPostgresChat. Webhook trigger; 28 nodes.

Source: https://gist.github.com/pkpahuja/84d87afd68f5eaa12977d0597437e926 — 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 receives receipt or invoice image URLs via a webhook, uses OpenAI Vision to extract structured totals and currency, validates them against expected booking values, logs the result to Goo

Slack, Google Sheets, Error Trigger +2
AI & RAG

LINKEDIN AI REPLY BOT. Uses stickyNote, googleSheets, agent, lmChatOpenAi. Webhook trigger; 13 nodes.

Google Sheets, Agent, OpenAI Chat +2
AI & RAG

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

OpenAI, OpenAI Chat, Tool Calculator +7
AI & RAG

⏺ 🚀 How it works

Agent, Anthropic Chat, Output Parser Structured +6
AI & RAG

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

Postgres, Mcp Client Tool, Google Drive Tool +14