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 →
{
"active": false,
"activeVersion": null,
"activeVersionId": null,
"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
}
]
]
}
},
"createdAt": "2025-07-14T19:04:42.550Z",
"id": "1PDBTJsZ1pNgzeQ0",
"isArchived": false,
"meta": {
"templateCredsSetupCompleted": true
},
"name": "12-Automate_WhatsApp_Booking_System_with_GPT_4_Assistant__Cal_com_and_SMS_Reminders",
"nodes": [
{
"parameters": {
"content": "# \ud83d\udfe9 STEP 3 \u2014 Send SMS Reminder Before Appointment\n\n",
"height": 280,
"width": 1620,
"color": 4
},
"id": "ba45b8f5-9994-429d-9cec-678c61c92e02",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
680,
580
],
"typeVersion": 1
},
{
"parameters": {
"content": "# \ud83d\udfeb STEP 1 \u2014 Qualify User and Suggest Appointment",
"height": 460,
"width": 1620
},
"id": "0546dd3d-410f-415f-91e7-996b792172e8",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
680,
-440
],
"typeVersion": 1
},
{
"parameters": {
"content": "# \ud83d\udfe5 STEP 2 \u2014 Book Appointment Automatically\n\n",
"height": 520,
"width": 1620,
"color": 3
},
"id": "0c759ada-7bc0-41a4-9e90-6a218e4d9d78",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
680,
40
],
"typeVersion": 1
},
{
"parameters": {
"httpMethod": "POST",
"path": "6438cd95-74cb-4f40-a1a5-853706fe96f6",
"responseMode": "responseNode",
"options": {}
},
"id": "d503fe3d-97f6-4a87-a14b-bfd722918718",
"name": "Webhook Trigger (WhatsApp Input)",
"type": "n8n-nodes-base.webhook",
"position": [
220,
20
],
"typeVersion": 2
},
{
"parameters": {
"rules": {
"values": [
{
"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": "Discussion"
},
{
"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,
"outputKey": "Confirm booking"
}
]
},
"options": {}
},
"id": "8a542667-082f-4ff7-bb77-4a7af7d0935d",
"name": "Switch: Confirm vs Chat Flow",
"type": "n8n-nodes-base.switch",
"position": [
440,
20
],
"typeVersion": 3.2
},
{
"parameters": {
"promptType": "define",
"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"
}
},
"id": "ea315e7e-2e57-42d6-a36d-c8cd71ab4da5",
"name": "AI Booking Assistant (Dr Firas)",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
1220,
-360
],
"typeVersion": 1.8
},
{
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "gpt-4o",
"cachedResultName": "gpt-4o"
},
"options": {}
},
"id": "cc11f00b-0955-4f78-9d4f-f7968fa3365a",
"name": "LLM: GPT-4o Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
980,
-160
],
"typeVersion": 1.2
},
{
"parameters": {
"sessionIdType": "customKey",
"sessionKey": "={{ $json.body.contactId }}",
"contextWindowLength": 50
},
"id": "b3a01146-deea-4670-9491-a7466b35ac2c",
"name": "AI Conversation Memory",
"type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
"position": [
1140,
-160
],
"typeVersion": 1.3
},
{
"parameters": {
"operation": "appendOrUpdate",
"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"
},
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1PsuURJg5nxVnb18OMDShjC-sTA9i_32pyBSjfswOpqc/edit#gid=0",
"cachedResultName": "mes RDV"
},
"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": {}
},
"id": "48ce1621-9899-4dc9-853e-50864a4fc3b1",
"name": "Create New Prospect in Google Sheet",
"type": "n8n-nodes-base.googleSheetsTool",
"position": [
1320,
-160
],
"typeVersion": 4.5
},
{
"parameters": {
"operation": "update",
"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"
},
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1PsuURJg5nxVnb18OMDShjC-sTA9i_32pyBSjfswOpqc/edit#gid=0",
"cachedResultName": "mes RDV"
},
"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": {}
},
"id": "3987229e-b597-443a-9e7f-db1c0335e1af",
"name": "Update Prospect with Booking Details",
"type": "n8n-nodes-base.googleSheetsTool",
"position": [
1500,
-160
],
"typeVersion": 4.5
},
{
"parameters": {
"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}",
"url": "https://api.cal.com/v2/slots/available",
"sendQuery": true,
"parametersQuery": {
"values": [
{
"name": "eventTypeId"
},
{
"name": "startTime"
},
{
"name": "endTime"
}
]
},
"sendHeaders": true,
"parametersHeaders": {
"values": [
{
"name": "Authorization",
"valueProvider": "fieldValue",
"value": "<redacted-credential>"
},
{
"name": "Content-Type",
"valueProvider": "fieldValue",
"value": "application/json"
}
]
}
},
"id": "49fb89f7-3926-47f2-ac53-d4112fe3cb97",
"name": "Fetch Available Time Slots",
"type": "@n8n/n8n-nodes-langchain.toolHttpRequest",
"position": [
1700,
-160
],
"typeVersion": 1.1
},
{
"parameters": {
"respondWith": "allIncomingItems",
"options": {}
},
"id": "b18b4600-a1d9-412b-9b99-fdf6452455e3",
"name": "Send Response to WhatsApp",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
1880,
-360
],
"typeVersion": 1.1
},
{
"parameters": {
"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"
},
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1PsuURJg5nxVnb18OMDShjC-sTA9i_32pyBSjfswOpqc/edit#gid=0",
"cachedResultName": "mes RDV"
},
"filtersUI": {
"values": [
{
"lookupColumn": "ID du contact",
"lookupValue": "={{ $('Webhook Trigger (WhatsApp Input)').item.json.body.contactId }}"
}
]
},
"options": {}
},
"id": "4b3d1aac-3e4d-41a9-9311-cff4126921b1",
"name": "Retrieve Prospect Details",
"type": "n8n-nodes-base.googleSheets",
"position": [
760,
240
],
"typeVersion": 4.5
},
{
"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];"
},
"id": "40daf6f7-4547-4a0a-85ca-cb8fe41a9184",
"name": "Normalize Booking Time (UTC Format)",
"type": "n8n-nodes-base.code",
"position": [
980,
240
],
"typeVersion": 2
},
{
"parameters": {
"method": "POST",
"url": "https://api.cal.com/v2/bookings",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "<redacted-credential>"
},
{
"name": "Content-Type",
"value": "application/json"
},
{
"name": "cal-api-version",
"value": "2024-08-13"
}
]
},
"sendBody": true,
"specifyBody": "json",
"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}",
"options": {}
},
"id": "c9c9ad17-ccd2-4eb9-9989-d64d4fbedb8e",
"name": "Send Booking",
"type": "n8n-nodes-base.httpRequest",
"position": [
1200,
240
],
"typeVersion": 4.2
},
{
"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]; "
},
"id": "f971375a-f52d-4fae-8c4f-42c1e18b3d99",
"name": "Format Date for Readability (Display)",
"type": "n8n-nodes-base.code",
"position": [
1420,
240
],
"typeVersion": 2
},
{
"parameters": {
"rules": {
"values": [
{
"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": "succeeded"
},
{
"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,
"outputKey": "Failed"
}
]
},
"options": {}
},
"id": "882c91a5-5f17-4630-a92e-c575750ff70d",
"name": "Check Booking Status (Success or Error)",
"type": "n8n-nodes-base.switch",
"position": [
1640,
240
],
"typeVersion": 3.2
},
{
"parameters": {
"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]",
"options": {}
},
"id": "7484e8d0-8eab-460c-a590-0c74ddfb4039",
"name": "Webhook Response: Booking Confirmed",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
1880,
80
],
"typeVersion": 1.1
},
{
"parameters": {
"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]",
"options": {}
},
"id": "ea0cc2ec-b397-4f90-a092-106d591f9f31",
"name": "Webhook Response: Booking Failed",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
1880,
380
],
"typeVersion": 1.1
},
{
"parameters": {
"operation": "update",
"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"
},
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1PsuURJg5nxVnb18OMDShjC-sTA9i_32pyBSjfswOpqc/edit#gid=0",
"cachedResultName": "mes RDV"
},
"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": {}
},
"id": "e6a0369a-1f16-4c49-9ef4-1afd6d9313c9",
"name": "Mark Booking as Confirmed in Sheet",
"type": "n8n-nodes-base.googleSheets",
"position": [
2100,
240
],
"typeVersion": 4.5
},
{
"parameters": {
"rule": {
"interval": [
{
"field": "hours"
}
]
}
},
"id": "6454a689-2217-4518-8eb7-989517df6114",
"name": "Trigger: Check Appointments Every Hour",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
760,
680
],
"typeVersion": 1.2
},
{
"parameters": {
"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"
},
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1PsuURJg5nxVnb18OMDShjC-sTA9i_32pyBSjfswOpqc/edit#gid=0",
"cachedResultName": "mes RDV"
},
"options": {}
},
"id": "179159e0-219e-4ccc-8e18-7e1b0821ab0d",
"name": "Read Upcoming Appointments from Sheet",
"type": "n8n-nodes-base.googleSheets",
"position": [
980,
680
],
"typeVersion": 4.5
},
{
"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"
},
"id": "88be9ec1-cd5b-4f8c-847c-735755224a64",
"name": "Filter Appointments Within Next 2 Hours",
"type": "n8n-nodes-base.code",
"position": [
1200,
680
],
"typeVersion": 2
},
{
"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": {}
},
"id": "830c68af-9c93-486c-b949-8a3901a15ed4",
"name": "Send SMS Reminder",
"type": "n8n-nodes-base.sms77",
"position": [
1420,
680
],
"typeVersion": 1
},
{
"parameters": {
"operation": "update",
"documentId": {
"__rl": true,
"mode": "id",
"value": "="
},
"sheetName": {
"__rl": true,
"mode": "id",
"value": "="
},
"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": {}
},
"id": "2a9e1ffa-bd1f-4cd4-9c9c-a766e67afe19",
"name": "Mark SMS as Sent in Sheet",
"type": "n8n-nodes-base.googleSheets",
"position": [
1640,
680
],
"typeVersion": 4.5
},
{
"parameters": {
"content": "## External Setup Guide\n[Guide](https://dr-firas.vip/)",
"height": 80,
"width": 300
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
360,
-440
],
"id": "b22a5230-4348-429b-bdfc-ac5941bbd153",
"name": "Sticky Note4"
}
],
"origin": "n8n",
"repo": {
"owner": "ashishk-yadav",
"name": "n8n-backup"
},
"settings": {
"executionOrder": "v1"
},
"shared": [
{
"updatedAt": "2026-01-06T07:13:30.055Z",
"createdAt": "2026-01-06T07:13:30.055Z",
"role": "workflow:owner",
"workflowId": "1PDBTJsZ1pNgzeQ0",
"projectId": "KWYKkSWmJNTdBWD9"
}
],
"staticData": null,
"tags": [
{
"updatedAt": "2025-07-09T21:41:38.773Z",
"createdAt": "2025-07-09T21:41:38.773Z",
"id": "G5Lcoe2jTgqCJuSy",
"name": "OpenAI"
},
{
"updatedAt": "2025-07-09T23:31:21.052Z",
"createdAt": "2025-07-09T23:31:21.052Z",
"id": "JXtwH1KWn3HpvHrm",
"name": "templates"
},
{
"updatedAt": "2025-07-09T23:31:21.059Z",
"createdAt": "2025-07-09T23:31:21.059Z",
"id": "giPr8wYqaJHn1OWx",
"name": "creator"
},
{
"updatedAt": "2025-07-09T21:41:38.763Z",
"createdAt": "2025-07-09T21:41:38.763Z",
"id": "pz5LfYMpyppJnoPT",
"name": "WooCommerce"
}
],
"triggerCount": 0,
"updatedAt": "2025-07-14T19:04:42.550Z",
"versionId": "af2ec08c-842b-479f-a33b-763880616930"
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
12-Automate_WhatsApp_Booking_System_with_GPT_4_Assistant__Cal_com_and_SMS_Reminders. Uses agent, lmChatOpenAi, memoryBufferWindow, googleSheetsTool. Webhook trigger; 26 nodes.
Source: https://github.com/ashishk-yadav/n8n-backup/blob/b98f3bb6ce2bd2837199bdcd61a27af64d0d652e/1PDBTJsZ1pNgzeQ0.json — 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.
Automate_WhatsApp_Booking_System_with_GPT_4_Assistant__Cal_com_and_SMS_Reminders. Uses agent, lmChatOpenAi, memoryBufferWindow, googleSheetsTool. Webhook trigger; 26 nodes.
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 —
Automate WhatsApp bookings with an AI assistant and smart SMS reminders (24/7). Uses agent, lmChatOpenAi, memoryBufferWindow, googleSheetsTool. Webhook trigger; 25 nodes.