This workflow corresponds to n8n.io template #4949 — we link there as the canonical source.
This workflow follows the Agent → Google Sheets recipe pattern — see all workflows that pair these two integrations.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"id": "IyhsZQh7TFAGsDqL",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "Automate WhatsApp bookings with an AI assistant and smart SMS reminders (24/7)",
"tags": [],
"nodes": [
{
"id": "1d45c88a-5c71-42bb-9207-a7060a64030b",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-500,
480
],
"parameters": {
"color": 4,
"width": 1620,
"height": 280,
"content": "# \ud83d\udfe9 STEP 3 \u2014 Send SMS Reminder Before Appointment\n\n"
},
"typeVersion": 1
},
{
"id": "1274c8bb-78fd-445f-b3bb-c1adedba3acc",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-500,
-540
],
"parameters": {
"width": 1620,
"height": 460,
"content": "# \ud83d\udfeb STEP 1 \u2014 Qualify User and Suggest Appointment"
},
"typeVersion": 1
},
{
"id": "bb147090-c21c-4b75-bfd7-92b1b61646f5",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
-500,
-60
],
"parameters": {
"color": 3,
"width": 1620,
"height": 520,
"content": "# \ud83d\udfe5 STEP 2 \u2014 Book Appointment Automatically\n\n"
},
"typeVersion": 1
},
{
"id": "3b2b3031-55a6-4c98-a6a5-75eb746b2c99",
"name": "Webhook Trigger (WhatsApp Input)",
"type": "n8n-nodes-base.webhook",
"position": [
-960,
-80
],
"parameters": {
"path": "6438cd95-74cb-4f40-a1a5-853706fe96f6",
"options": {},
"httpMethod": "POST",
"responseMode": "responseNode"
},
"typeVersion": 2
},
{
"id": "6c7ffde6-b3bc-4162-b45d-8e483e347d49",
"name": "Switch: Confirm vs Chat Flow",
"type": "n8n-nodes-base.switch",
"position": [
-740,
-80
],
"parameters": {
"rules": {
"values": [
{
"outputKey": "Discussion",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "2b0aa9e5-7215-435b-8b66-fddb9973c7d0",
"operator": {
"type": "string",
"operation": "notContains"
},
"leftValue": "={{ $json.body.userInput }}",
"rightValue": "confirm"
}
]
},
"renameOutput": true
},
{
"outputKey": "Confirm booking",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "ed81f2e7-f97d-4581-bc84-ed703db2ec08",
"operator": {
"type": "string",
"operation": "contains"
},
"leftValue": "={{ $json.body.userInput }}",
"rightValue": "confirm"
}
]
},
"renameOutput": true
}
]
},
"options": {}
},
"typeVersion": 3.2
},
{
"id": "f7f43574-cfed-4448-983f-dc53343ec4ab",
"name": "AI Booking Assistant (Dr Firas)",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
40,
-460
],
"parameters": {
"text": "=input : {{ $json.body.userInput }}",
"options": {
"systemMessage": "=#IMPORTANT \nLes rendez-vous ne peuvent \u00eatre pris qu\u2019apr\u00e8s avoir collect\u00e9 le nom, l\u2019e-mail/Courriel et le service choisi.\n\n#IDENTIT\u00c9 \nVous \u00eates l\u2019assistant de Dr Firas, coach expert en automatisation n8n, sur WhatsApp.\n\n#CONTEXTE \n- Dr Firas propose des coachings et ateliers en ligne pour ma\u00eetriser n8n \n- Horaires : du lundi au vendredi, 9 h\u201318 h (heure de Paris) \n- Fran\u00e7ais et anglais possibles\n- La date d'aujourd'hui est : {{$now}}\n\n#SERVICES ET MAPPING (CRITIQUE) \n- \u00ab Audit express n8n \u00bb (30 min) \u2013 40 $ \u2013 Event ID : 2638115 \n- \u00ab Coaching Workflow n8n \u00bb (60 min) \u2013 65 $ \u2013 Event ID : 2638127 \n- \u00ab Atelier Automatisation Avanc\u00e9e \u00bb (90 min) \u2013 99 $ \u2013 Event ID : 2638131 \n\nQuand l\u2019utilisateur choisit un service, m\u00e9morisez l\u2019Event ID correspondant.\n\n#TON \nDynamique, professionnel et chaleureux. Emojis l\u00e9gers \ud83d\udc4d\ud83e\udd16.\n\n#FLUX DE CONVERSATION\n\n## 1. Accueil \n\u00ab Bonjour ! Je suis l\u2019assistant de Dr Firas, pr\u00eat \u00e0 vous guider pour votre session n8n. Puis-je vous poser quelques questions ? \u00bb\n\n## 2. Choix du service (une question \u00e0 la fois) \na) \u00ab Quel service souhaitez-vous ? \u00bb \n- Si \u00ab je ne sais pas \u00bb ou \u00ab que proposez-vous \u00bb \u2192 listez les 3 services avec dur\u00e9e, prix et b\u00e9n\u00e9fice court. \n- Sinon, continuez.\n\n## 3. Collecte des infos client \n\u00ab Parfait ! Pour r\u00e9server votre [nom du service], j\u2019ai besoin de votre pr\u00e9nom et de votre e-mail. \u00bb\n\n\u2192 Apr\u00e8s avoir re\u00e7u nom + e-mail/Courriel , enregistrez dans le Google Sheet Prospect (nom, e-mail, t\u00e9l\u00e9phone={{ $json.body.phoneNumber }}, service, Event ID, r\u00e9sum\u00e9).\n\n## 4. Proposition de cr\u00e9neaux \n\u00ab Souhaitez-vous voir les disponibilit\u00e9s imm\u00e9diatement ? \u00bb \n- Si oui : \n 1. Appelez l\u2019outil **Get Availability** avec : \n - Event ID du service \n - startTime = maintenant (ISO 8601, UTC, ex. `2025-06-13T12:00:00Z`) \n - endTime = +48 h (ISO 8601, UTC) \n - max 5 cr\u00e9neaux \n 2. Convertissez chaque horaire UTC en heure de Paris (+02:00) et affichez-les en plain text (ex. \u201c14:30\u201d).\n\n## 5. Choix du cr\u00e9neau \n\u00ab Lequel de ces cr\u00e9neaux vous convient ? \u00bb \n- L\u2019utilisateur r\u00e9pond date+heure \u2192 \n - Demandez \u00ab Tapez \u201coui\u201d pour confirmer ce cr\u00e9neau \u00bb. \n - \u00c0 \u00ab oui \u00bb, enregistrez via Google Sheet Update_Leads (nom, e-mail, service, Event ID, date+heure). \n - Puis : \u00ab Votre rendez-vous est fix\u00e9 au [date] \u00e0 [heure] (heure de Paris). Tapez \u201cconfirm\u201d pour finaliser. \u00bb\n\n## 6. Confirmation finale \n- \u00c0 \u201cconfirm\u201d \u2192 \n - Confirmez la r\u00e9servation et rappelez la politique d\u2019annulation 24 h \u00e0 l\u2019avance. \n - Proposez un lien d\u2019ajout au calendrier si besoin.\n\n#CONTRAINTES OUTIL \u201cGet Availability\u201d \n- Toujours ISO 8601 UTC \n- startTime = maintenant, endTime = +48 h \n- Maximum 5 cr\u00e9neaux \n- Plain text, < 400 car.\n\n#R\u00c8GLES G\u00c9N\u00c9RALES \n- Une question \u00e0 la fois \n- Ne jamais redemander une info d\u00e9j\u00e0 fournie \n- \u201cRESET\u201d relance la conversation depuis le d\u00e9but \n"
},
"promptType": "define"
},
"typeVersion": 1.8
},
{
"id": "03d391f5-43b5-462e-8c56-cdce426f9751",
"name": "LLM: GPT-4o Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
-200,
-260
],
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "gpt-4o",
"cachedResultName": "gpt-4o"
},
"options": {}
},
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.2
},
{
"id": "365fc08f-6514-4182-b77f-86a13ef55576",
"name": "AI Conversation Memory",
"type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
"position": [
-40,
-260
],
"parameters": {
"sessionKey": "={{ $json.body.contactId }}",
"sessionIdType": "customKey",
"contextWindowLength": 50
},
"typeVersion": 1.3
},
{
"id": "96eeb9b5-aea9-46cc-893f-4ea3697a3fb5",
"name": "Create New Prospect in Google Sheet",
"type": "n8n-nodes-base.googleSheetsTool",
"position": [
140,
-260
],
"parameters": {
"columns": {
"value": {
"Nom": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Nom', ``, 'string') }}",
"Courriel": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Courriel', ``, 'string') }}",
"R\u00e9sum\u00e9": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('R_sum_', `ici il y a le r\u00e9sum\u00e9 de toute la conversation`, 'string') }}",
"ID du contact": "={{ $json.body.contactId }}",
"Num\u00e9ro de t\u00e9l\u00e9phone": "={{ $json.body.phoneNumber }}"
},
"schema": [
{
"id": "Nom",
"type": "string",
"display": true,
"required": false,
"displayName": "Nom",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Courriel",
"type": "string",
"display": true,
"required": false,
"displayName": "Courriel",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Num\u00e9ro de t\u00e9l\u00e9phone",
"type": "string",
"display": true,
"required": false,
"displayName": "Num\u00e9ro de t\u00e9l\u00e9phone",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "R\u00e9sum\u00e9",
"type": "string",
"display": true,
"required": false,
"displayName": "R\u00e9sum\u00e9",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "R\u00e9serv\u00e9",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "R\u00e9serv\u00e9",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Nom de l\u2019\u00e9v\u00e9nement",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "Nom de l\u2019\u00e9v\u00e9nement",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "ID de l\u2019\u00e9v\u00e9nement",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "ID de l\u2019\u00e9v\u00e9nement",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Date du rendez-vous",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "Date du rendez-vous",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Rappel SMS envoy\u00e9",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "Rappel SMS envoy\u00e9",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "ID du contact",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "ID du contact",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"ID du contact"
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "appendOrUpdate",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1PsuURJg5nxVnb18OMDShjC-sTA9i_32pyBSjfswOpqc/edit#gid=0",
"cachedResultName": "mes RDV"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1PsuURJg5nxVnb18OMDShjC-sTA9i_32pyBSjfswOpqc",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1PsuURJg5nxVnb18OMDShjC-sTA9i_32pyBSjfswOpqc/edit?usp=drivesdk",
"cachedResultName": "MES RDV"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.5
},
{
"id": "9fcb6a32-34e7-4c5a-997a-cd0fe58f62cd",
"name": "Update Prospect with Booking Details",
"type": "n8n-nodes-base.googleSheetsTool",
"position": [
320,
-260
],
"parameters": {
"columns": {
"value": {
"ID du contact": "={{ $json.body.contactId }}",
"Date du rendez-vous": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Date_du_rendez-vous', ``, 'string') }}",
"ID de l\u2019\u00e9v\u00e9nement": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('ID_de_l__v_nement', ``, 'string') }}",
"Nom de l\u2019\u00e9v\u00e9nement": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Nom_de_l__v_nement', ``, 'string') }}"
},
"schema": [
{
"id": "Nom",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "Nom",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Courriel",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "Courriel",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Num\u00e9ro de t\u00e9l\u00e9phone",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "Num\u00e9ro de t\u00e9l\u00e9phone",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "R\u00e9sum\u00e9",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "R\u00e9sum\u00e9",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "R\u00e9serv\u00e9",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "R\u00e9serv\u00e9",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Nom de l\u2019\u00e9v\u00e9nement",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Nom de l\u2019\u00e9v\u00e9nement",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "ID de l\u2019\u00e9v\u00e9nement",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "ID de l\u2019\u00e9v\u00e9nement",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Date du rendez-vous",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Date du rendez-vous",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Rappel SMS envoy\u00e9",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "Rappel SMS envoy\u00e9",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "ID du contact",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "ID du contact",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"ID du contact"
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "update",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1PsuURJg5nxVnb18OMDShjC-sTA9i_32pyBSjfswOpqc/edit#gid=0",
"cachedResultName": "mes RDV"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1PsuURJg5nxVnb18OMDShjC-sTA9i_32pyBSjfswOpqc",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1PsuURJg5nxVnb18OMDShjC-sTA9i_32pyBSjfswOpqc/edit?usp=drivesdk",
"cachedResultName": "MES RDV"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.5
},
{
"id": "9e1a6470-0937-4190-9d24-259ea2749190",
"name": "Fetch Available Time Slots",
"type": "@n8n/n8n-nodes-langchain.toolHttpRequest",
"position": [
520,
-260
],
"parameters": {
"url": "https://api.cal.com/v2/slots/available",
"sendQuery": true,
"sendHeaders": true,
"parametersQuery": {
"values": [
{
"name": "eventTypeId"
},
{
"name": "startTime"
},
{
"name": "endTime"
}
]
},
"toolDescription": "Appelez cet outil pour r\u00e9cup\u00e9rer la disponibilit\u00e9 des rendez-vous.\nVous devez imp\u00e9rativement utiliser le fuseau horaire de Paris.\nRespectez strictement le format ISO pour les dates, par exemple :\n2025-01-01T09:00:00-02:00\nExemple de sch\u00e9ma d\u2019entr\u00e9e :\n{\n \"startTime\": \"...\",\n \"endTime\": \"...\"\n}",
"parametersHeaders": {
"values": [
{
"name": "Authorization",
"value": "Bearer YOUR_TOKEN_HERE",
"valueProvider": "fieldValue"
},
{
"name": "Content-Type",
"value": "application/json",
"valueProvider": "fieldValue"
}
]
}
},
"typeVersion": 1.1
},
{
"id": "5d8dc1ce-020f-4df3-88cc-7f61594a3347",
"name": "Send Response to WhatsApp",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
700,
-460
],
"parameters": {
"options": {},
"respondWith": "allIncomingItems"
},
"typeVersion": 1.1
},
{
"id": "5cd00123-c0ea-48f9-85e6-e496a0bd6620",
"name": "Retrieve Prospect Details",
"type": "n8n-nodes-base.googleSheets",
"position": [
-420,
140
],
"parameters": {
"options": {},
"filtersUI": {
"values": [
{
"lookupValue": "={{ $('Webhook Trigger (WhatsApp Input)').item.json.body.contactId }}",
"lookupColumn": "ID du contact"
}
]
},
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1PsuURJg5nxVnb18OMDShjC-sTA9i_32pyBSjfswOpqc/edit#gid=0",
"cachedResultName": "mes RDV"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1PsuURJg5nxVnb18OMDShjC-sTA9i_32pyBSjfswOpqc",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1PsuURJg5nxVnb18OMDShjC-sTA9i_32pyBSjfswOpqc/edit?usp=drivesdk",
"cachedResultName": "MES RDV"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.5
},
{
"id": "9696140f-857f-42b6-9bb9-3d25f5d893d4",
"name": "Normalize Booking Time (UTC Format)",
"type": "n8n-nodes-base.code",
"position": [
-200,
140
],
"parameters": {
"jsCode": "const input = $input.first().json['Date du rendez-vous']; // e.g.,\n\"2025-06-04T09:00:00+02:00\"\nfunction normalizeToUTCZFormat(dateString) {\ntry {\nif\n(/^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?Z$/.test(dateString)\n) {\nreturn new Date(dateString).toISOString();\n}\nconst date = new Date(dateString);\nif (isNaN(date.getTime())) {\nthrow new Error(\"Invalid date format\");\n}\nreturn date.toISOString(); // Returns in UTC with Z\n} catch (e) {\nreturn `Invalid input: ${e.message}`;\n}\n}\nreturn [\n{\njson: {\noriginal: input,\nnormalized: normalizeToUTCZFormat(input),\n},\n},\n];"
},
"typeVersion": 2
},
{
"id": "6d2ef19a-93d1-46f1-bc31-ad1010d3bb4a",
"name": "Send Booking",
"type": "n8n-nodes-base.httpRequest",
"position": [
20,
140
],
"parameters": {
"url": "https://api.cal.com/v2/bookings",
"method": "POST",
"options": {},
"jsonBody": "={\n\"eventTypeId\": {{ $('Retrieve Prospect Details').item.json['ID de l\u2019\u00e9v\u00e9nement'] }},\n\"start\": \"{{ $json.normalized }}\",\n\"attendee\": {\n\"name\": \"{{ $('Retrieve Prospect Details').item.json.Nom }}\",\n\"email\": \"{{ $('Retrieve Prospect Details').item.json.Courriel }}\",\n\"timeZone\": \"Europe/Paris\"\n},\n\"bookingFieldsResponses\": {\n\"title\": \"{{ $('Retrieve Prospect Details').item.json['R\u00e9sum\u00e9'] }}\"\n}\n}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "Bearer YOUR_TOKEN_HERE"
},
{
"name": "Content-Type",
"value": "application/json"
},
{
"name": "cal-api-version",
"value": "2024-08-13"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "472dc55f-b42f-483f-b6cc-1f8c25f0158e",
"name": "Format Date for Readability (Display)",
"type": "n8n-nodes-base.code",
"position": [
240,
140
],
"parameters": {
"jsCode": "const input = $('Retrieve Prospect Details').first().json['Date du rendez-vous']; \nfunction formatToReadableDate(dateString) {\ntry {\nconst date = new Date(dateString);\nif (isNaN(date.getTime())) throw new Error(\"Invalid date\");\nconst options = {\ntimeZone: \"Europe/Berlin\", // Ensures correct local time\nmonth: \"long\",\nday: \"numeric\",\nhour: \"numeric\",\nminute: \"2-digit\",\nhour12: true,\n};\nconst formatted = date.toLocaleString(\"en-US\", options);\n// Example output: \"June 4, 09:00 AM\" \u2192 make it shorter like\n\"June 4, 9am\"\nreturn formatted\n.replace(\":00\", \"\") // remove :00 if exact hour\n.replace(\"AM\", \"am\")\n.replace(\"PM\", \"pm\");\n} catch (e) {\nreturn `Invalid input: ${e.message}`;\n}\n}\nreturn [\n{\njson: {\noriginal: input,\nformatted: formatToReadableDate(input),\n},\n},\n]; "
},
"typeVersion": 2
},
{
"id": "7f0f86bc-8199-4d81-8bce-e96ced534f6e",
"name": "Check Booking Status (Success or Error)",
"type": "n8n-nodes-base.switch",
"position": [
460,
140
],
"parameters": {
"rules": {
"values": [
{
"outputKey": "succeeded",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "cbf9b691-0bc1-4a14-b35d-96664d82bc91",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $('Send Booking').item.json.status }}",
"rightValue": "success"
}
]
},
"renameOutput": true
},
{
"outputKey": "Failed",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "f78214f9-ecd4-4913-aef0-9a2a453b052c",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $('Send Booking').item.json.status }}",
"rightValue": "error"
}
]
},
"renameOutput": true
}
]
},
"options": {}
},
"typeVersion": 3.2
},
{
"id": "108c9a6b-a345-4f33-ab22-113b6e63914b",
"name": "Webhook Response: Booking Confirmed",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
700,
-20
],
"parameters": {
"options": {},
"respondWith": "json",
"responseBody": "\n[\n{\n\"output\": \"Votre r\u00e9servation a \u00e9t\u00e9 effectu\u00e9e avec succ\u00e8s. Vous recevrez bient\u00f4t un e-mail de confirmation pour votre massage, RDV : .\"\n}\n]"
},
"typeVersion": 1.1
},
{
"id": "8c3d9734-7aad-4445-9810-29d7045fbc78",
"name": "Webhook Response: Booking Failed",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
700,
280
],
"parameters": {
"options": {},
"respondWith": "json",
"responseBody": "[\n{\n\"output\": \"Il semble que le cr\u00e9neau horaire que vous avez choisi ne soit plus disponible. Veuillez en s\u00e9lectionner un autre.\"\n}\n]"
},
"typeVersion": 1.1
},
{
"id": "2a3ed659-5cec-497b-bde8-f6a15585cc41",
"name": "Mark Booking as Confirmed in Sheet",
"type": "n8n-nodes-base.googleSheets",
"position": [
920,
140
],
"parameters": {
"columns": {
"value": {
"R\u00e9serv\u00e9": "confirm",
"ID du contact": "={{ $('Retrieve Prospect Details').item.json['ID du contact'] }}"
},
"schema": [
{
"id": "Nom",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "Nom",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Courriel",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "Courriel",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Num\u00e9ro de t\u00e9l\u00e9phone",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "Num\u00e9ro de t\u00e9l\u00e9phone",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "R\u00e9sum\u00e9",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "R\u00e9sum\u00e9",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "R\u00e9serv\u00e9",
"type": "string",
"display": true,
"required": false,
"displayName": "R\u00e9serv\u00e9",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Nom de l\u2019\u00e9v\u00e9nement",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "Nom de l\u2019\u00e9v\u00e9nement",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "ID de l\u2019\u00e9v\u00e9nement",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "ID de l\u2019\u00e9v\u00e9nement",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Date du rendez-vous",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "Date du rendez-vous",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Rappel SMS envoy\u00e9",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "Rappel SMS envoy\u00e9",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "ID du contact",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "ID du contact",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "row_number",
"type": "string",
"display": true,
"removed": true,
"readOnly": true,
"required": false,
"displayName": "row_number",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"ID du contact"
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "update",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1PsuURJg5nxVnb18OMDShjC-sTA9i_32pyBSjfswOpqc/edit#gid=0",
"cachedResultName": "mes RDV"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1PsuURJg5nxVnb18OMDShjC-sTA9i_32pyBSjfswOpqc",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1PsuURJg5nxVnb18OMDShjC-sTA9i_32pyBSjfswOpqc/edit?usp=drivesdk",
"cachedResultName": "MES RDV"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.5
},
{
"id": "5bfa51a3-e939-41e8-a1df-b4e59c4feef1",
"name": "Trigger: Check Appointments Every Hour",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
-420,
580
],
"parameters": {
"rule": {
"interval": [
{
"field": "hours"
}
]
}
},
"typeVersion": 1.2
},
{
"id": "0413b7b7-b523-4e22-ad4a-40d7342abed9",
"name": "Read Upcoming Appointments from Sheet",
"type": "n8n-nodes-base.googleSheets",
"position": [
-200,
580
],
"parameters": {
"options": {},
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1PsuURJg5nxVnb18OMDShjC-sTA9i_32pyBSjfswOpqc/edit#gid=0",
"cachedResultName": "mes RDV"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1PsuURJg5nxVnb18OMDShjC-sTA9i_32pyBSjfswOpqc",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1PsuURJg5nxVnb18OMDShjC-sTA9i_32pyBSjfswOpqc/edit?usp=drivesdk",
"cachedResultName": "MES RDV"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.5
},
{
"id": "408e1330-29e4-4de7-b681-1bb372b5f841",
"name": "Filter Appointments Within Next 2 Hours",
"type": "n8n-nodes-base.code",
"position": [
20,
580
],
"parameters": {
"jsCode": "const nowParis = new Date(\n new Date().toLocaleString(\"en-US\", { timeZone: \"Europe/Paris\" })\n);\nconst nowTimestamp = nowParis.getTime();\nconst twoHoursFromNow = nowTimestamp + 2 * 60 * 60 * 1000;\n\nreturn items\n .filter(item => {\n const booked = item.json[\"R\u00e9serv\u00e9\"]?.toLowerCase() === \"confirm\";\n const reminderSent = item.json[\"Rappel SMS envoy\u00e9\"]?.toLowerCase() === \"envoyer\";\n const rawDate = item.json[\"Date du rendez-vous\"];\n if (!booked || reminderSent || !rawDate) {\n return false;\n }\n\n const appointmentTimestamp = Date.parse(rawDate);\n return appointmentTimestamp > nowTimestamp &&\n appointmentTimestamp <= twoHoursFromNow;\n })\n .map(item => {\n const rawDate = item.json[\"Date du rendez-vous\"];\n const date = new Date(rawDate);\n if (isNaN(date.getTime())) {\n item.json.appointmentDisplayTime = \"(Invalid date)\";\n return item;\n }\n\n const options = {\n timeZone: \"Europe/Paris\",\n month: \"long\",\n day: \"numeric\",\n hour: \"numeric\",\n minute: \"2-digit\",\n hour12: true\n };\n\n const formatted = date.toLocaleString(\"en-US\", options)\n .replace(\":00\", \"\")\n .replace(\"AM\", \"AM\")\n .replace(\"PM\", \"PM\");\n\n item.json.appointmentDisplayTime = formatted;\n return item;\n });\n"
},
"typeVersion": 2
},
{
"id": "0a9d6747-85d2-400e-a9fd-b5768ae4605c",
"name": "Send SMS Reminder",
"type": "n8n-nodes-base.sms77",
"position": [
240,
580
],
"parameters": {
"to": "={{ $('Read Upcoming Appointments from Sheet').item.json['Num\u00e9ro de t\u00e9l\u00e9phone'] }}",
"message": "=Bonjour {{ $json.Nom }}, ceci est un petit rappel : votre rendez-vous pour {{ $json['Nom de l\u2019\u00e9v\u00e9nement'] }} est pr\u00e9vu dans les 2 prochaines heures ",
"options": {}
},
"credentials": {
"sms77Api": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "3a01ec5e-57ea-43ac-8e59-97d66e85454f",
"name": "Mark SMS as Sent in Sheet",
"type": "n8n-nodes-base.googleSheets",
"position": [
460,
580
],
"parameters": {
"columns": {
"value": {
"ID du contact": "={{ $('Read Upcoming Appointments from Sheet').item.json['ID du contact'] }}",
"Rappel SMS envoy\u00e9": "envoyer"
},
"schema": [
{
"id": "Nom",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "Nom",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Courriel",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "Courriel",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Num\u00e9ro de t\u00e9l\u00e9phone",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "Num\u00e9ro de t\u00e9l\u00e9phone",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "R\u00e9sum\u00e9",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "R\u00e9sum\u00e9",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "R\u00e9serv\u00e9",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "R\u00e9serv\u00e9",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Nom de l\u2019\u00e9v\u00e9nement",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "Nom de l\u2019\u00e9v\u00e9nement",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "ID de l\u2019\u00e9v\u00e9nement",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "ID de l\u2019\u00e9v\u00e9nement",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Date du rendez-vous",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "Date du rendez-vous",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Rappel SMS envoy\u00e9",
"type": "string",
"display": true,
"required": false,
"displayName": "Rappel SMS envoy\u00e9",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "ID du contact",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "ID du contact",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "row_number",
"type": "string",
"display": true,
"removed": true,
"readOnly": true,
"required": false,
"displayName": "row_number",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"ID du contact"
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "update",
"sheetName": {
"__rl": true,
"mode": "id",
"value": "="
},
"documentId": {
"__rl": true,
"mode": "id",
"value": "="
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.5
}
],
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "5c82e86c-e3b2-4b70-b686-a782edbaee55",
"connections": {
"Send Booking": {
"main": [
[
{
"node": "Format Date for Readability (Display)",
"type": "main",
"index": 0
}
]
]
},
"Send SMS Reminder": {
"main": [
[
{
"node": "Mark SMS as Sent in Sheet",
"type": "main",
"index": 0
}
]
]
},
"AI Conversation Memory": {
"ai_memory": [
[
{
"node": "AI Booking Assistant (Dr Firas)",
"type": "ai_memory",
"index": 0
}
]
]
},
"LLM: GPT-4o Chat Model": {
"ai_languageModel": [
[
{
"node": "AI Booking Assistant (Dr Firas)",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Retrieve Prospect Details": {
"main": [
[
{
"node": "Normalize Booking Time (UTC Format)",
"type": "main",
"index": 0
}
]
]
},
"Fetch Available Time Slots": {
"ai_tool": [
[
{
"node": "AI Booking Assistant (Dr Firas)",
"type": "ai_tool",
"index": 0
}
]
]
},
"Switch: Confirm vs Chat Flow": {
"main": [
[
{
"node": "AI Booking Assistant (Dr Firas)",
"type": "main",
"index": 0
}
],
[
{
"node": "Retrieve Prospect Details",
"type": "main",
"index": 0
}
]
]
},
"AI Booking Assistant (Dr Firas)": {
"main": [
[
{
"node": "Send Response to WhatsApp",
"type": "main",
"index": 0
}
]
]
},
"Webhook Trigger (WhatsApp Input)": {
"main": [
[
{
"node": "Switch: Confirm vs Chat Flow",
"type": "main",
"index": 0
}
]
]
},
"Create New Prospect in Google Sheet": {
"ai_tool": [
[
{
"node": "AI Booking Assistant (Dr Firas)",
"type": "ai_tool",
"index": 0
}
]
]
},
"Normalize Booking Time (UTC Format)": {
"main": [
[
{
"node": "Send Booking",
"type": "main",
"index": 0
}
]
]
},
"Webhook Response: Booking Confirmed": {
"main": [
[
{
"node": "Mark Booking as Confirmed in Sheet",
"type": "main",
"index": 0
}
]
]
},
"Update Prospect with Booking Details": {
"ai_tool": [
[
{
"node": "AI Booking Assistant (Dr Firas)",
"type": "ai_tool",
"index": 0
}
]
]
},
"Format Date for Readability (Display)": {
"main": [
[
{
"node": "Check Booking Status (Success or Error)",
"type": "main",
"index": 0
}
]
]
},
"Read Upcoming Appointments from Sheet": {
"main": [
[
{
"node": "Filter Appointments Within Next 2 Hours",
"type": "main",
"index": 0
}
]
]
},
"Trigger: Check Appointments Every Hour": {
"main": [
[
{
"node": "Read Upcoming Appointments from Sheet",
"type": "main",
"index": 0
}
]
]
},
"Check Booking Status (Success or Error)": {
"main": [
[
{
"node": "Webhook Response: Booking Confirmed",
"type": "main",
"index": 0
}
],
[
{
"node": "Webhook Response: Booking Failed",
"type": "main",
"index": 0
}
]
]
},
"Filter Appointments Within Next 2 Hours": {
"main": [
[
{
"node": "Send SMS Reminder",
"type": "main",
"index": 0
}
]
]
}
}
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
googleSheetsOAuth2ApiopenAiApisms77Api
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow is designed for solo entrepreneurs, consultants, coaches, clinics, or any business that handles client appointments and wants to automate the entire scheduling experience via WhatsApp — without the need for live agents.
Source: https://n8n.io/workflows/4949/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
🧠 Gwen – The AI Voice Marketing Agent Gwen is your intelligent voice-powered marketing assistant built in n8n. She combines the power of OpenAI, ElevenLabs, and automation workflows to handle content
12-Automate_WhatsApp_Booking_System_with_GPT_4_Assistant__Cal_com_and_SMS_Reminders. Uses agent, lmChatOpenAi, memoryBufferWindow, googleSheetsTool. Webhook trigger; 26 nodes.
12-Automate_WhatsApp_Booking_System_with_GPT_4_Assistant__Cal_com_and_SMS_Reminders. Uses agent, lmChatOpenAi, memoryBufferWindow, googleSheetsTool. Webhook trigger; 26 nodes.
Automate_WhatsApp_Booking_System_with_GPT_4_Assistant__Cal_com_and_SMS_Reminders. Uses agent, lmChatOpenAi, memoryBufferWindow, googleSheetsTool. Webhook trigger; 26 nodes.
Automate WhatsApp bookings with an AI assistant and smart SMS reminders (24/7). Uses agent, lmChatOpenAi, memoryBufferWindow, googleSheetsTool. Webhook trigger; 25 nodes.