AutomationFlowsAI & RAG › Automate WhatsApp Bookings with GPT-4 & Cal.com

Automate WhatsApp Bookings with GPT-4 & Cal.com

Original n8n title: Automate Whatsapp Booking System with Gpt 4 Assistant Cal Com and SMS Reminders

12-Automate_WhatsApp_Booking_System_with_GPT_4_Assistant__Cal_com_and_SMS_Reminders. Uses agent, lmChatOpenAi, memoryBufferWindow, googleSheetsTool. Webhook trigger; 26 nodes.

Webhook trigger★★★★☆ complexityAI-powered26 nodesAgentOpenAI ChatMemory Buffer WindowGoogle Sheets ToolTool Http RequestGoogle SheetsHTTP RequestSms77
AI & RAG Trigger: Webhook Nodes: 26 Complexity: ★★★★☆ AI nodes: yes Added:

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 →

Download .json
{
  "active": false,
  "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"
    }
  ],
  "settings": {
    "executionOrder": "v1"
  },
  "shared": [
    {
      "createdAt": "2025-08-13T17:53:53.290Z",
      "updatedAt": "2025-08-13T17:53:53.290Z",
      "role": "workflow:owner",
      "workflowId": "1PDBTJsZ1pNgzeQ0",
      "projectId": "6FPie4k3QSdqcndC"
    }
  ],
  "staticData": null,
  "tags": [
    {
      "createdAt": "2025-07-09T21:41:38.773Z",
      "updatedAt": "2025-07-09T21:41:38.773Z",
      "id": "G5Lcoe2jTgqCJuSy",
      "name": "OpenAI"
    },
    {
      "createdAt": "2025-07-09T23:31:21.052Z",
      "updatedAt": "2025-07-09T23:31:21.052Z",
      "id": "JXtwH1KWn3HpvHrm",
      "name": "templates"
    },
    {
      "createdAt": "2025-07-09T23:31:21.059Z",
      "updatedAt": "2025-07-09T23:31:21.059Z",
      "id": "giPr8wYqaJHn1OWx",
      "name": "creator"
    },
    {
      "createdAt": "2025-07-09T21:41:38.763Z",
      "updatedAt": "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"
}
Pro

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/workflows/OpenAI/1PDBTJsZ1pNgzeQ0.json — original creator credit. Request a take-down →

More AI & RAG workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

AI & RAG

🧠 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

Tool Workflow, Memory Buffer Window, Agent +10
AI & RAG

12-Automate_WhatsApp_Booking_System_with_GPT_4_Assistant__Cal_com_and_SMS_Reminders. Uses agent, lmChatOpenAi, memoryBufferWindow, googleSheetsTool. Webhook trigger; 26 nodes.

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

Automate_WhatsApp_Booking_System_with_GPT_4_Assistant__Cal_com_and_SMS_Reminders. Uses agent, lmChatOpenAi, memoryBufferWindow, googleSheetsTool. Webhook trigger; 26 nodes.

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

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 —

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

Automate WhatsApp bookings with an AI assistant and smart SMS reminders (24/7). Uses agent, lmChatOpenAi, memoryBufferWindow, googleSheetsTool. Webhook trigger; 25 nodes.

Agent, OpenAI Chat, Memory Buffer Window +5