{
  "nodes": [
    {
      "id": "c46f192b-d4f6-4227-979b-d257151a33d9",
      "name": "GET: Verify Webhook",
      "type": "n8n-nodes-base.webhook",
      "position": [
        80,
        144
      ],
      "parameters": {
        "path": "whatsapp-webhook",
        "options": {},
        "responseMode": "responseNode"
      },
      "typeVersion": 2
    },
    {
      "id": "f70a36e8-e057-42ca-b3cc-a3b3dc868464",
      "name": "Verify Token",
      "type": "n8n-nodes-base.code",
      "position": [
        304,
        144
      ],
      "parameters": {
        "jsCode": "// Handle GET verification request from WhatsApp\nconst query = $input.first().json.query || {};\nconst VERIFY_TOKEN = $env.WHATSAPP_VERIFY_TOKEN || 'your_verify_token_here';\n\nif (query['hub.mode'] !== 'subscribe') {\n  throw new Error('Invalid hub.mode');\n}\n\nif (query['hub.verify_token'] !== VERIFY_TOKEN) {\n  throw new Error('Invalid verify token');\n}\n\nreturn {\n  json: {\n    challenge: query['hub.challenge']\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "32acd5a4-c26c-4bf8-9f40-9a49a19dfdac",
      "name": "Return Challenge",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        544,
        144
      ],
      "parameters": {
        "options": {
          "responseCode": 200
        },
        "respondWith": "text",
        "responseBody": "={{ $json.challenge }}"
      },
      "typeVersion": 1.1
    },
    {
      "id": "6283825a-5b66-40d4-94b9-dc9dc24cefb5",
      "name": "Is Regular Message?",
      "type": "n8n-nodes-base.if",
      "position": [
        -128,
        560
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "bde30647-9802-41d4-a346-3355ab24dca6",
              "operator": {
                "type": "boolean",
                "operation": "equals"
              },
              "leftValue": "={{ $json.isRegularMessage }}",
              "rightValue": true
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "525cce30-ed11-4735-9bc3-536d2143efc1",
      "name": "Return 200 OK (Regular Message)",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        784,
        544
      ],
      "parameters": {
        "options": {
          "responseCode": 200
        },
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ status: 'received' }) }}"
      },
      "typeVersion": 1.1
    },
    {
      "id": "d5aeb338-cab9-4e24-a216-cabb36213f46",
      "name": "POST: Receive Messages1",
      "type": "n8n-nodes-base.webhook",
      "position": [
        -576,
        560
      ],
      "parameters": {
        "path": "whatsapp-webhook",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 2
    },
    {
      "id": "c062dd3a-da07-4f8a-954d-d6672a8f422a",
      "name": "Decrypt WhatsApp Request1",
      "type": "n8n-nodes-base.code",
      "position": [
        -352,
        560
      ],
      "parameters": {
        "jsCode": "const crypto = require('crypto');\n\nconst PRIVATE_KEY = $env.WHATSAPP_PRIVATE_KEY || '';\nconst PASSPHRASE = $env.WHATSAPP_PRIVATE_KEY_PASSPHRASE || '';\n\nlet privateKey = PRIVATE_KEY;\nif (!PRIVATE_KEY.includes('\\n') && PRIVATE_KEY.includes('\\\\n')) {\n  privateKey = PRIVATE_KEY.replace(/\\\\n/g, '\\n');\n}\n\nconst body = $input.first().json.body;\n\n// Check if this is a regular WhatsApp message (not a Flow request)\nif (body?.entry) {\n  const entry = body.entry[0];\n  const changes = entry?.changes?.[0];\n  const value = changes?.value;\n  const messages = value?.messages;\n\n  if (messages && messages.length > 0) {\n    const message = messages[0];\n    const from = message.from;\n    const messageType = message.type;\n\n    // Handle interactive button replies\n    if (messageType === 'interactive' && message.interactive?.type === 'button_reply') {\n      const buttonId = message.interactive.button_reply.id;\n      const buttonTitle = message.interactive.button_reply.title;\n      const parts = buttonId.split('_');\n      const action = parts[0];\n      const recordId = parts.slice(1).join('_');\n\n      return {\n        json: {\n          messageType: 'button_reply',\n          action: action,\n          recordId: recordId,\n          customerPhone: from,\n          buttonTitle: buttonTitle,\n          isRegularMessage: true\n        }\n      };\n    }\n\n    // Handle text messages\n    if (messageType === 'text') {\n      return {\n        json: {\n          messageType: 'text_message',\n          from: from,\n          text: message.text?.body || '',\n          isRegularMessage: true\n        }\n      };\n    }\n\n    // Other message types\n    return {\n      json: {\n        messageType: messageType,\n        from: from,\n        raw: message,\n        isRegularMessage: true\n      }\n    };\n  }\n\n  // Status update or other non-message webhook\n  return {\n    json: {\n      messageType: 'status_update',\n      isRegularMessage: true,\n      raw: body\n    }\n  };\n}\n\n// This is an encrypted Flow request - decrypt it\nfunction decryptRequest(body) {\n  const { encrypted_aes_key, encrypted_flow_data, initial_vector } = body;\n\n  const decryptedAesKey = crypto.privateDecrypt(\n    {\n      key: privateKey,\n      passphrase: PASSPHRASE,\n      oaepHash: 'sha256',\n      padding: crypto.constants.RSA_PKCS1_OAEP_PADDING\n    },\n    Buffer.from(encrypted_aes_key, 'base64')\n  );\n\n  const flowDataBuffer = Buffer.from(encrypted_flow_data, 'base64');\n  const ivBuffer = Buffer.from(initial_vector, 'base64');\n\n  const authTag = flowDataBuffer.subarray(-16);\n  const encryptedData = flowDataBuffer.subarray(0, -16);\n\n  const decipher = crypto.createDecipheriv('aes-128-gcm', decryptedAesKey, ivBuffer);\n  decipher.setAuthTag(authTag);\n\n  const decrypted = Buffer.concat([\n    decipher.update(encryptedData),\n    decipher.final()\n  ]);\n\n  return {\n    decryptedData: JSON.parse(decrypted.toString('utf8')),\n    aesKey: decryptedAesKey,\n    iv: ivBuffer\n  };\n}\n\nfunction encryptResponse(response, aesKey, iv) {\n  const flippedIv = Buffer.alloc(iv.length);\n  for (let i = 0; i < iv.length; i++) {\n    flippedIv[i] = ~iv[i] & 0xff;\n  }\n  const cipher = crypto.createCipheriv('aes-128-gcm', aesKey, flippedIv);\n  const encrypted = Buffer.concat([\n    cipher.update(JSON.stringify(response), 'utf8'),\n    cipher.final(),\n    cipher.getAuthTag()\n  ]);\n  return encrypted.toString('base64');\n}\n\nconst { decryptedData, aesKey, iv } = decryptRequest(body);\n\nif (decryptedData.action === 'ping') {\n  const pingResponse = {\n    version: decryptedData.version,\n    data: { status: 'active' }\n  };\n\n  return {\n    json: {\n      action: 'ping',\n      response: encryptResponse(pingResponse, aesKey, iv),\n      isRegularMessage: false\n    }\n  };\n}\n\nreturn {\n  json: {\n    decrypted: decryptedData,\n    action: decryptedData.action,\n    screen: decryptedData.screen,\n    data: decryptedData.data || {},\n    flow_token: decryptedData.flow_token,\n    aesKeyBase64: aesKey.toString('base64'),\n    ivBase64: iv.toString('base64'),\n    version: decryptedData.version,\n    isRegularMessage: false\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "b37944ec-53ee-4548-8b1f-7bc2c99a44e1",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -608,
        304
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 480,
        "content": "## Whatsapp Single Entry Point: Webhook\n\nBecause of Meta's Single App to Single Webhook restriction, all messages come through the same webhook (GET / POST). \n\nConditional statements after this are then used to filter what happens"
      },
      "typeVersion": 1
    },
    {
      "id": "bfc3eb34-787e-4959-9402-73cc0d218f0a",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        16,
        1136
      ],
      "parameters": {
        "color": 7,
        "width": 3072,
        "height": 1280,
        "content": "## WhatsApp Flow: Booking\n\nFlows are attached to messaging templates and are activated by the WhatsApp User\n\nAt each stage of the WhatsApp Flow, data is being fed to the experience. E.g Services Available, Calendar Availability, & Confirmation. On complete - a booking is made into Airtable and a Calendar Event is made\n\n"
      },
      "typeVersion": 1
    },
    {
      "id": "d6f2ba59-2038-49ca-8057-1e379baeed25",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        32,
        384
      ],
      "parameters": {
        "color": 7,
        "width": 1296,
        "height": 656,
        "content": "## Intelligent Templating Assignment & Responses\n\nWe want to be able to fire off our template messages and flows that make sense to the customers requests. If they want more information, we can send them a link to the services website.\nIf they want to book - then a Calendar Flow, a quote, then a quote flow. "
      },
      "typeVersion": 1
    },
    {
      "id": "b3551661-9a21-412b-a0b0-b6500d74c05b",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        32,
        32
      ],
      "parameters": {
        "color": 7,
        "width": 704,
        "height": 304,
        "content": "## Utility: WhatsApp Webhook Check\nRequired for setting up your n8n webhook in Meta"
      },
      "typeVersion": 1
    },
    {
      "id": "9b87e762-c71d-4073-adb5-265a8f262aa1",
      "name": "Return Ping Response",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        304,
        1312
      ],
      "parameters": {
        "options": {
          "responseCode": 200
        },
        "respondWith": "text",
        "responseBody": "={{ $json.response }}"
      },
      "typeVersion": 1.1
    },
    {
      "id": "a9bc81f7-d4c5-4351-abf4-42be2a295e82",
      "name": "Handle INIT - Return Consultation Types",
      "type": "n8n-nodes-base.code",
      "position": [
        304,
        1472
      ],
      "parameters": {
        "jsCode": "// Handle INIT action - return initial screen data\nconst formatDate = (d) => d.toISOString().split('T')[0];\nconst today = new Date();\nconst maxDate = new Date();\nmaxDate.setDate(today.getDate() + 30);\n\nconst response = {\n  version: $json.version || '3.0',\n  screen: 'SERVICE_SELECTION',\n  data: {\n    consultation_types: [\n      { id: '30_min', title: '30 Minute Call' },\n      { id: '60_min', title: '60 Minute Call' }\n    ],\n    min_date: formatDate(today),\n    max_date: formatDate(maxDate)\n  }\n};\n\nreturn {\n  json: {\n    response,\n    aesKeyBase64: $json.aesKeyBase64,\n    ivBase64: $json.ivBase64\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "df7e496c-140b-49e8-89ca-e4cd68f07929",
      "name": "Handle Confirm Booking",
      "type": "n8n-nodes-base.code",
      "position": [
        336,
        2016
      ],
      "parameters": {
        "jsCode": "// Handle confirm booking - return CONFIRMATION screen data\nconst data = $json.data;\nconst aesKeyBase64 = $json.aesKeyBase64;\nconst ivBase64 = $json.ivBase64;\nconst version = $json.version;\n\nconst response = {\n  version: version || '3.0',\n  screen: 'CONFIRMATION',\n  data: {\n    customer_name: data.customer_name,\n    customer_email: data.customer_email || '',\n    consultation_type: data.consultation_type,\n    consultation_type_label: data.consultation_type_label,\n    appointment_date: data.appointment_date,\n    appointment_time: data.appointment_time\n  }\n};\n\nreturn {\n  json: {\n    response,\n    aesKeyBase64,\n    ivBase64\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "f4321129-d5bc-4d7b-ae11-6b1e6d7f39d4",
      "name": "Send WhatsApp Confirmation",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        2128,
        2224
      ],
      "parameters": {
        "url": "https://graph.facebook.com/v23.0/{{WHATSAPP_PHONE_NUMBER_ID}}/messages",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"messaging_product\": \"whatsapp\",\n  \"to\": \"{{ $('Check Customer Exists1').first().json.flowToken }}\",\n  \"type\": \"text\",\n  \"text\": {\n    \"body\": \"Your consultation has been confirmed!\\n\\n{{ $('Check Customer Exists1').first().json.booking.consultationLabel }}\\nDate: {{ $('Check Customer Exists1').first().json.booking.appointmentDateDisplay }}\\nTime: {{ $('Check Customer Exists1').first().json.booking.appointmentTimeDisplay }}\\n\\nWe look forward to speaking with you!\"\n  }\n}",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpBearerAuth"
      },
      "typeVersion": 4.2
    },
    {
      "id": "269f5e8b-ab1b-49cc-b6b1-e4615e79b74a",
      "name": "Merge Paths",
      "type": "n8n-nodes-base.code",
      "position": [
        2480,
        1824
      ],
      "parameters": {
        "jsCode": "// Merge all paths to encryption node\nreturn $input.all();"
      },
      "typeVersion": 2
    },
    {
      "id": "5c9e17c9-00e5-4d04-82ee-6adaed550ff4",
      "name": "Encrypt Response",
      "type": "n8n-nodes-base.code",
      "position": [
        2688,
        1824
      ],
      "parameters": {
        "jsCode": "// WhatsApp Flow Encryption - AES-128-GCM\nconst crypto = require('crypto');\n\nfunction encryptResponse(response, aesKeyBase64, ivBase64) {\n  const aesKey = Buffer.from(aesKeyBase64, 'base64');\n  const iv = Buffer.from(ivBase64, 'base64');\n  \n  const flippedIv = Buffer.alloc(iv.length);\n  for (let i = 0; i < iv.length; i++) {\n    flippedIv[i] = ~iv[i] & 0xff;\n  }\n  \n  const cipher = crypto.createCipheriv('aes-128-gcm', aesKey, flippedIv);\n  \n  const responseStr = JSON.stringify(response);\n  const encrypted = Buffer.concat([\n    cipher.update(responseStr, 'utf8'),\n    cipher.final(),\n    cipher.getAuthTag()\n  ]);\n  \n  return encrypted.toString('base64');\n}\n\nconst { response, aesKeyBase64, ivBase64 } = $json;\nconst encryptedResponse = encryptResponse(response, aesKeyBase64, ivBase64);\n\nreturn {\n  json: {\n    encryptedResponse\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "dd67e257-f900-4f75-a0a3-61e8b04cbad2",
      "name": "Respond to Webhook",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        2912,
        1824
      ],
      "parameters": {
        "options": {
          "responseHeaders": {
            "entries": [
              {
                "name": "Content-Type",
                "value": "text/plain"
              }
            ]
          }
        },
        "respondWith": "text",
        "responseBody": "={{ $json.encryptedResponse }}"
      },
      "typeVersion": 1.1
    },
    {
      "id": "439fa7ab-c609-4d15-9fed-943e59c5e0d9",
      "name": "Switch on Action",
      "type": "n8n-nodes-base.switch",
      "position": [
        80,
        1328
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "outputKey": "PING",
              "conditions": {
                "options": {
                  "version": 1,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "993ed1e9-64a4-427a-b8fc-5256edde1946",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.action }}",
                    "rightValue": "ping"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "INIT",
              "conditions": {
                "options": {
                  "version": 1,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "007df454-eab1-4b7a-9e1d-e82781e8e9a7",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.action }}",
                    "rightValue": "INIT"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "SERVICE_SELECTION",
              "conditions": {
                "options": {
                  "version": 1,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "46e256b4-e275-43a4-9f6d-d33c338d463a",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.action }}",
                    "rightValue": "data_exchange"
                  },
                  {
                    "id": "64878584-3e5b-40a1-a0bf-ee11cf51f416",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.screen }}",
                    "rightValue": "SERVICE_SELECTION"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "DATE_REFRESH",
              "conditions": {
                "options": {
                  "version": 1,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "1a7e63d4-55c8-4ebb-b305-432c69198888",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.action }}",
                    "rightValue": "data_exchange"
                  },
                  {
                    "id": "664fd262-0803-42ef-96f7-02b6ab477460",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.screen }}",
                    "rightValue": "DATE_TIME_SELECTION"
                  },
                  {
                    "id": "880d3796-67fb-4ea7-860e-ef27172ee346",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.data.action_type }}",
                    "rightValue": "refresh_slots"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "CONFIRM_BOOKING",
              "conditions": {
                "options": {
                  "version": 1,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "926d9d18-05bc-4156-861e-d3960ee07dca",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.action }}",
                    "rightValue": "data_exchange"
                  },
                  {
                    "id": "73939400-1292-4b36-b681-1d1c8aa0486a",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.screen }}",
                    "rightValue": "DATE_TIME_SELECTION"
                  },
                  {
                    "id": "72b44982-b940-4b53-a1c7-f1766b9958d5",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.data.action_type }}",
                    "rightValue": "confirm_booking"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "COMPLETE_BOOKING",
              "conditions": {
                "options": {
                  "version": 1,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "ea4498c4-fb40-492a-8703-6bec94c1fde9",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.action }}",
                    "rightValue": "data_exchange"
                  },
                  {
                    "id": "b1234567-1234-1234-1234-123456789abc",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.screen }}",
                    "rightValue": "CONFIRMATION"
                  },
                  {
                    "id": "c1234567-1234-1234-1234-123456789def",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.data.action_type }}",
                    "rightValue": "complete_booking"
                  }
                ]
              },
              "renameOutput": true
            }
          ]
        },
        "options": {
          "fallbackOutput": "none"
        }
      },
      "typeVersion": 3
    },
    {
      "id": "3e908ab3-eb23-4134-936e-85d4b14cae94",
      "name": "Switch",
      "type": "n8n-nodes-base.switch",
      "position": [
        144,
        528
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "outputKey": "INTERACTIVE",
              "conditions": {
                "options": {
                  "version": 3,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "b2fd458a-376d-4b87-817e-a92151d3b3ff",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.messageType }}",
                    "rightValue": "interactive"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "STATUS_UPDATE",
              "conditions": {
                "options": {
                  "version": 3,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "903cf5bf-4180-40da-a7eb-6dd58b37a1b1",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.messageType }}",
                    "rightValue": "status_updated"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "TEXT_MESSAGE",
              "conditions": {
                "options": {
                  "version": 3,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "bd24999f-353e-40b9-b64c-b6e6c04ac51e",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.messageType }}",
                    "rightValue": "text_message"
                  }
                ]
              },
              "renameOutput": true
            }
          ]
        },
        "options": {}
      },
      "typeVersion": 3.4
    },
    {
      "id": "95e15ae0-f4c2-489b-887d-3f2f41c73e11",
      "name": "OpenAI Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        352,
        832
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4o",
          "cachedResultName": "gpt-4o"
        },
        "options": {},
        "builtInTools": {}
      },
      "typeVersion": 1.3
    },
    {
      "id": "52f72a66-1e70-4545-87b4-03f230148e3b",
      "name": "whatsapp_consult_template",
      "type": "n8n-nodes-base.httpRequestTool",
      "position": [
        704,
        832
      ],
      "parameters": {
        "url": "https://graph.facebook.com/v23.0/{{WHATSAPP_PHONE_NUMBER_ID}}/messages",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"messaging_product\": \"whatsapp\",\n  \"to\": \"{{ $('Is Regular Message?').item.json.from }}\",\n  \"type\": \"template\",\n  \"template\": {\n    \"name\": \"personal_consultation_booking\",\n    \"language\": {\n      \"code\": \"en\"\n    },\n    \"components\": [\n      {\n        \"type\": \"button\",\n        \"sub_type\": \"flow\",\n        \"index\": \"0\",\n        \"parameters\": [\n          {\n            \"type\": \"action\",\n            \"action\": {\n              \"flow_token\": \"{{ $json.from }}\"\n            }\n          }\n        ]\n      }\n    ]\n  }\n}\n",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpBearerAuth",
        "toolDescription": "whatsapp_consult_template - use when a user's intent is to book an appointment"
      },
      "typeVersion": 4.3
    },
    {
      "id": "38e2ebc5-be9c-4a8e-ae25-d922ae1660f2",
      "name": "whatsapp_message_tool",
      "type": "n8n-nodes-base.httpRequestTool",
      "position": [
        528,
        832
      ],
      "parameters": {
        "url": "https://graph.facebook.com/v23.0/{{WHATSAPP_PHONE_NUMBER_ID}}/messages",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n    \"messaging_product\": \"whatsapp\",    \n    \"recipient_type\": \"individual\",\n    \"to\": \"{{ $('Is Regular Message?').item.json.from }}\",\n    \"type\": \"text\",\n    \"text\": {\n        \"preview_url\": false,\n        \"body\": \"{{ $fromAI('JSON', ``, 'json') }}\"\n    }\n}",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpBearerAuth",
        "toolDescription": "**whatsapp_message**\nUsage: When the user asks a questions, or you require more information to understand their intent. You can reply"
      },
      "typeVersion": 4.3
    },
    {
      "id": "9833334d-fd52-4f0e-ab53-cb16e20463f8",
      "name": "Search Existing Customer",
      "type": "n8n-nodes-base.airtable",
      "notes": "UPDATED: Now searches by customer_email instead of phone_number",
      "onError": "continueRegularOutput",
      "position": [
        416,
        2224
      ],
      "parameters": {
        "base": {
          "__rl": true,
          "mode": "url",
          "value": "=https://airtable.com/{{AIRTABLE_BASE_ID}}",
          "__regex": "https://airtable.com/([a-zA-Z0-9]{2,})"
        },
        "table": {
          "__rl": true,
          "mode": "list",
          "value": "{{AIRTABLE_CUSTOMERS_TABLE_ID}}",
          "cachedResultUrl": "https://airtable.com/{{AIRTABLE_BASE_ID}}/{{AIRTABLE_CUSTOMERS_TABLE_ID}}",
          "cachedResultName": "Customers"
        },
        "options": {},
        "operation": "search",
        "authentication": "airtableOAuth2Api",
        "filterByFormula": "={customer_email}='{{ $json.booking.customerEmail }}'"
      },
      "typeVersion": 2.1,
      "alwaysOutputData": true
    },
    {
      "id": "dc192778-333b-42ec-8726-55a05427efa8",
      "name": "Lookup Service",
      "type": "n8n-nodes-base.airtable",
      "notes": "NEW: Lookup service record by service_key (30_min or 60_min) to get record ID for linking",
      "position": [
        1088,
        2224
      ],
      "parameters": {
        "base": {
          "__rl": true,
          "mode": "url",
          "value": "=https://airtable.com/{{AIRTABLE_BASE_ID}}",
          "__regex": "https://airtable.com/([a-zA-Z0-9]{2,})"
        },
        "table": {
          "__rl": true,
          "mode": "list",
          "value": "{{AIRTABLE_SERVICES_TABLE_ID}}",
          "cachedResultUrl": "https://airtable.com/{{AIRTABLE_BASE_ID}}/{{AIRTABLE_SERVICES_TABLE_ID}}",
          "cachedResultName": "Services"
        },
        "options": {
          "fields": [
            "service_name"
          ]
        },
        "operation": "search",
        "authentication": "airtableOAuth2Api",
        "filterByFormula": "={service_key}='{{ $('Check Customer Exists1').first().json.booking.serviceKey }}'"
      },
      "typeVersion": 2.1
    },
    {
      "id": "0b9e6c1e-0106-41dc-b1c8-d0a53492b81b",
      "name": "Prepare Booking Data1",
      "type": "n8n-nodes-base.code",
      "notes": "UPDATED: Added serviceKey, eventDate, eventTime fields for new schema",
      "position": [
        208,
        2224
      ],
      "parameters": {
        "jsCode": "// Prepare booking data for Airtable and Calendar\nconst data = $json.data;\n\n// Parse YYYY-MM-DD date string\nconst appointmentDate = new Date(data.appointment_date + 'T00:00:00');\nconst [hours, minutes] = data.appointment_time.split(':').map(Number);\n\nappointmentDate.setHours(hours, minutes, 0, 0);\n\nconst duration = data.consultation_type === '60_min' ? 60 : 30;\nconst endTime = new Date(appointmentDate);\nendTime.setMinutes(endTime.getMinutes() + duration);\n\nconst dateStr = appointmentDate.toLocaleDateString('en-US', {\n  weekday: 'long',\n  year: 'numeric',\n  month: 'long',\n  day: 'numeric'\n});\n\nconst timeStr = appointmentDate.toLocaleTimeString('en-US', {\n  hour: 'numeric',\n  minute: '2-digit',\n  hour12: true\n});\n\nreturn {\n  json: {\n    ...$json,\n    booking: {\n      customerName: data.customer_name,\n      customerEmail: data.customer_email || '',\n      consultationType: data.consultation_type,\n      consultationLabel: data.consultation_type_label,\n      serviceKey: data.consultation_type,\n      eventDate: data.appointment_date,\n      eventTime: data.appointment_time,\n      appointmentDateTime: appointmentDate.toISOString(),\n      appointmentEndTime: endTime.toISOString(),\n      appointmentDateDisplay: dateStr,\n      appointmentTimeDisplay: timeStr,\n      duration: duration,\n      smsReminder: data.sms_reminder || false,\n      emailReminder: data.email_reminder || false\n    }\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "b4b78f14-467e-4c8f-9a04-a24066931bd1",
      "name": "Create Booking in Airtable1",
      "type": "n8n-nodes-base.airtable",
      "notes": "UPDATED: customer_email (text), service_type (linked), event_date, event_time, booking_status, flow_token. Removed reminder fields.",
      "position": [
        1312,
        2224
      ],
      "parameters": {
        "base": {
          "__rl": true,
          "mode": "url",
          "value": "=https://airtable.com/{{AIRTABLE_BASE_ID}}",
          "__regex": "https://airtable.com/([a-zA-Z0-9]{2,})"
        },
        "table": {
          "__rl": true,
          "mode": "list",
          "value": "{{AIRTABLE_BOOKINGS_TABLE_ID}}",
          "cachedResultUrl": "https://airtable.com/{{AIRTABLE_BASE_ID}}/{{AIRTABLE_BOOKINGS_TABLE_ID}}",
          "cachedResultName": "Bookings"
        },
        "columns": {
          "value": {
            "created_at": "={{ $now }}",
            "event_date": "={{ $('Prepare Booking Data1').item.json.decrypted.data.appointment_date }}",
            "event_time": "={{ $('Prepare Booking Data1').item.json.decrypted.data.appointment_time }}",
            "booking_status": "Pending",
            "customer_email": "={{ $('Upsert Customer in Airtable').item.json.fields.customer_email }}"
          },
          "schema": [
            {
              "id": "booking_id",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": true,
              "required": false,
              "displayName": "booking_id",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "customer_email",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "customer_email",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "service_type",
              "type": "array",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "service_type",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "calendar_event_id",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "calendar_event_id",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "event_date",
              "type": "dateTime",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "event_date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "event_time",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "event_time",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "created_at",
              "type": "dateTime",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "created_at",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "booking_status",
              "type": "options",
              "display": true,
              "options": [
                {
                  "name": "Pending",
                  "value": "Pending"
                },
                {
                  "name": "Confirmed",
                  "value": "Confirmed"
                },
                {
                  "name": "Cancelled",
                  "value": "Cancelled"
                }
              ],
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "booking_status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "special_requests",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "special_requests",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {
          "typecast": true
        },
        "operation": "create",
        "authentication": "airtableOAuth2Api"
      },
      "typeVersion": 2.1
    },
    {
      "id": "f199a9f1-f6ea-477b-bdd9-61a83a979837",
      "name": "Update Booking with Calendar ID1",
      "type": "n8n-nodes-base.airtable",
      "notes": "UPDATED: status -> booking_status, now sets to 'Confirmed' after calendar event created",
      "position": [
        1808,
        2224
      ],
      "parameters": {
        "base": {
          "__rl": true,
          "mode": "url",
          "value": "=https://airtable.com/{{AIRTABLE_BASE_ID}}",
          "__regex": "https://airtable.com/([a-zA-Z0-9]{2,})"
        },
        "table": {
          "__rl": true,
          "mode": "list",
          "value": "{{AIRTABLE_BOOKINGS_TABLE_ID}}",
          "cachedResultUrl": "https://airtable.com/{{AIRTABLE_BASE_ID}}/{{AIRTABLE_BOOKINGS_TABLE_ID}}",
          "cachedResultName": "Bookings"
        },
        "columns": {
          "value": {
            "id": "={{ $('Create Booking in Airtable1').item.json.id }}",
            "booking_status": "Confirmed",
            "calendar_event_id": "={{ $json.id }}"
          },
          "schema": [
            {
              "id": "id",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": true,
              "required": false,
              "displayName": "id",
              "defaultMatch": true
            },
            {
              "id": "booking_id",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": true,
              "required": false,
              "displayName": "booking_id",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "customer_email",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "customer_email",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "service_type",
              "type": "array",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "service_type",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "calendar_event_id",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "calendar_event_id",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "event_date",
              "type": "dateTime",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "event_date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "event_time",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "event_time",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "created_at",
              "type": "dateTime",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "created_at",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "booking_status",
              "type": "options",
              "display": true,
              "options": [
                {
                  "name": "Pending",
                  "value": "Pending"
                },
                {
                  "name": "Confirmed",
                  "value": "Confirmed"
                },
                {
                  "name": "Cancelled",
                  "value": "Cancelled"
                }
              ],
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "booking_status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "special_requests",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "special_requests",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "id"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "update",
        "authentication": "airtableOAuth2Api"
      },
      "typeVersion": 2.1
    },
    {
      "id": "62c6c4dd-ab5e-4da9-986b-9625d1d9f4af",
      "name": "Upsert Customer in Airtable",
      "type": "n8n-nodes-base.airtable",
      "notes": "UPDATED: Field names - customer_name, customer_email, customer_phone_number, date_created, last_interaction",
      "position": [
        880,
        2224
      ],
      "parameters": {
        "base": {
          "__rl": true,
          "mode": "url",
          "value": "=https://airtable.com/{{AIRTABLE_BASE_ID}}"
        },
        "table": {
          "__rl": true,
          "mode": "list",
          "value": "{{AIRTABLE_CUSTOMERS_TABLE_ID}}",
          "cachedResultUrl": "https://airtable.com/{{AIRTABLE_BASE_ID}}/{{AIRTABLE_CUSTOMERS_TABLE_ID}}",
          "cachedResultName": "Customers"
        },
        "columns": {
          "value": {
            "id": "={{ $json.existingCustomerId }}",
            "date_created": "={{ $now }}",
            "customer_name": "={{ $json.booking.customerName }}",
            "customer_email": "={{ $json.booking.customerEmail }}",
            "last_interaction": "={{ $now }}"
          },
          "schema": [
            {
              "id": "id",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": true,
              "required": false,
              "displayName": "id",
              "defaultMatch": true
            },
            {
              "id": "customer_id",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": true,
              "required": false,
              "displayName": "customer_id",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "customer_name",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "customer_name",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "customer_email",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "customer_email",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "customer_phone_number",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "customer_phone_number",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "date_created",
              "type": "dateTime",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "date_created",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "last_interaction",
              "type": "dateTime",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "last_interaction",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Service Requests",
              "type": "array",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "Service Requests",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "id"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {
          "typecast": true
        },
        "operation": "upsert",
        "authentication": "airtableOAuth2Api"
      },
      "typeVersion": 2.1
    },
    {
      "id": "70235408-3e69-4bc3-b3c2-3e5c6a492dce",
      "name": "Google Calendar Events (Date)",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        544,
        1840
      ],
      "parameters": {
        "url": "=https://www.googleapis.com/calendar/v3/calendars/{{ encodeURIComponent($json.calendarId) }}/events",
        "options": {},
        "sendQuery": true,
        "authentication": "predefinedCredentialType",
        "queryParameters": {
          "parameters": [
            {
              "name": "timeMin",
              "value": "={{ $json.timeMin }}"
            },
            {
              "name": "timeMax",
              "value": "={{ $json.timeMax }}"
            },
            {
              "name": "singleEvents",
              "value": "true"
            },
            {
              "name": "orderBy",
              "value": "startTime"
            },
            {
              "name": "maxResults",
              "value": "50"
            }
          ]
        },
        "nodeCredentialType": "googleCalendarOAuth2Api"
      },
      "typeVersion": 4.2
    },
    {
      "id": "32cff87f-af74-4cae-97e9-acc2830f3cad",
      "name": "Calculate Slots for Date",
      "type": "n8n-nodes-base.code",
      "position": [
        768,
        1840
      ],
      "parameters": {
        "jsCode": "// Calculate slots for selected date using Events API (including pending/tentative)\nconst events = ($json.items || []).filter(e => e.status !== 'cancelled');\nconst prev = $('Prepare Date Refresh Request1').first().json;\nconst consultationType = prev.consultationType;\nconst selectedDateStr = prev.selectedDate;\nconst customerName = prev.customerName;\nconst customerEmail = prev.customerEmail;\nconst aesKeyBase64 = prev.aesKeyBase64;\nconst ivBase64 = prev.ivBase64;\nconst version = prev.version;\n\nconst slotDuration = consultationType === '60_min' ? 60 : 30;\nconst consultationLabel = consultationType === '60_min' ? '60 Minute Call' : '30 Minute Call';\n\nconst businessStart = 9;\nconst businessEnd = 17;\n\n// Convert events to busy time blocks\nconst busyTimes = events.map(event => {\n  // Handle all-day events\n  if (event.start.date) {\n    return {\n      start: new Date(event.start.date + 'T00:00:00'),\n      end: new Date(event.end.date + 'T00:00:00')\n    };\n  }\n  return {\n    start: new Date(event.start.dateTime),\n    end: new Date(event.end.dateTime)\n  };\n});\n\nconst selectedDate = new Date(selectedDateStr + 'T00:00:00');\nconst now = new Date();\nconst availableSlots = [];\n\nconst dayOfWeek = selectedDate.getDay();\nif (dayOfWeek !== 0 && dayOfWeek !== 6) {\n  for (let hour = businessStart; hour < businessEnd; hour++) {\n    for (let minute = 0; minute < 60; minute += 30) {\n      const slotEnd = hour + (minute + slotDuration) / 60;\n      if (slotEnd > businessEnd) continue;\n\n      const slotStart = new Date(selectedDate);\n      slotStart.setHours(hour, minute, 0, 0);\n\n      // Skip past slots\n      if (slotStart <= now) continue;\n\n      const slotEndTime = new Date(slotStart);\n      slotEndTime.setMinutes(slotEndTime.getMinutes() + slotDuration);\n\n      // Check for conflicts with any event (confirmed, tentative, etc.)\n      const isConflict = busyTimes.some(busy => {\n        return (slotStart < busy.end && slotEndTime > busy.start);\n      });\n\n      if (!isConflict) {\n        const timeStr = `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`;\n        const displayHour = hour > 12 ? hour - 12 : hour;\n        const ampm = hour >= 12 ? 'PM' : 'AM';\n        const displayMin = minute.toString().padStart(2, '0');\n\n        availableSlots.push({\n          id: timeStr,\n          title: `${displayHour}:${displayMin} ${ampm}`\n        });\n      }\n    }\n  }\n}\n\nconst formatDate = (d) => d.toISOString().split('T')[0];\nconst today = new Date();\nconst minDate = formatDate(today);\nconst maxDate = formatDate(new Date(today.getTime() + 30 * 24 * 60 * 60 * 1000));\n\nconst response = {\n  version: version || '3.0',\n  screen: 'DATE_TIME_SELECTION',\n  data: {\n    customer_name: customerName,\n    customer_email: customerEmail,\n    consultation_type: consultationType,\n    consultation_type_label: consultationLabel,\n    min_date: minDate,\n    max_date: maxDate,\n    available_slots: availableSlots\n  }\n};\n\nreturn {\n  json: {\n    response,\n    aesKeyBase64,\n    ivBase64\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "2ea36a0e-ea3d-4e30-afdf-0ba322f985ea",
      "name": "Prepare Date Refresh Request1",
      "type": "n8n-nodes-base.code",
      "position": [
        336,
        1840
      ],
      "parameters": {
        "jsCode": "// Prepare Google Calendar Events request for specific selected date\nconst selectedDate = $json.data.selected_date;\n\nconst date = new Date(selectedDate + 'T00:00:00');\n\nconst dayStart = new Date(date);\ndayStart.setHours(0, 0, 0, 0);\n\nconst dayEnd = new Date(date);\ndayEnd.setHours(23, 59, 59, 999);\n\nconst calendarId = $env.GOOGLE_CALENDAR_ID || 'primary';\n\nreturn {\n  json: {\n    ...$json,\n    selectedDate: selectedDate,\n    calendarId: calendarId,\n    timeMin: dayStart.toISOString(),\n    timeMax: dayEnd.toISOString(),\n    consultationType: $json.data.consultation_type,\n    customerName: $json.data.customer_name,\n    customerEmail: $json.data.customer_email || ''\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "c821c29b-7144-49ae-8a40-528c6ac9d561",
      "name": "Check Customer Exists1",
      "type": "n8n-nodes-base.code",
      "notes": "UPDATED: Field names changed to customer_name, customer_email, customer_phone_number, date_created, last_interaction",
      "position": [
        640,
        2224
      ],
      "parameters": {
        "jsCode": "// Check if customer exists and prepare upsert data\nconst existingCustomers = $input.all();\nconst booking = $('Prepare Booking Data1').first().json.booking;\nconst flowToken = $('Prepare Booking Data1').first().json.flow_token;\nconst aesKeyBase64 = $('Prepare Booking Data1').first().json.aesKeyBase64;\nconst ivBase64 = $('Prepare Booking Data1').first().json.ivBase64;\nconst version = $('Prepare Booking Data1').first().json.version;\n\nconst customerExists = existingCustomers.length > 0 && existingCustomers[0].json.id;\nconst existingCustomerId = customerExists ? existingCustomers[0].json.id : null;\n\nreturn {\n  json: {\n    customerExists,\n    existingCustomerId,\n    booking,\n    flowToken,\n    aesKeyBase64,\n    ivBase64,\n    version,\n    customerData: {\n      customer_name: booking.customerName,\n      customer_email: booking.customerEmail,\n      customer_phone_number: flowToken,\n      date_created: new Date().toISOString().split('T')[0],\n      last_interaction: new Date().toISOString().split('T')[0]\n    }\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "955a58cf-8d98-4eda-83e2-f8d94c88a872",
      "name": "Prepare Success Response1",
      "type": "n8n-nodes-base.code",
      "notes": "FIXED: Gets encryption keys from Check Customer Exists1 instead of Search Existing Customer",
      "position": [
        2496,
        2208
      ],
      "parameters": {
        "jsCode": "// Prepare success response\nconst aesKeyBase64 = $('Check Customer Exists1').first().json.aesKeyBase64;\nconst ivBase64 = $('Check Customer Exists1').first().json.ivBase64;\nconst version = $('Check Customer Exists1').first().json.version;\n\nconst response = {\n  version: version || '3.0',\n  screen: 'SUCCESS',\n  data: {\n    extension_message_response: {\n      params: {\n        flow_token: $('Check Customer Exists1').first().json.flowToken,\n        booking_confirmed: true\n      }\n    }\n  }\n};\n\nreturn {\n  json: {\n    response,\n    aesKeyBase64,\n    ivBase64\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "2df0aba0-0fb1-42ce-b71b-e3df3c7496f6",
      "name": "Whatsapp Agent",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        352,
        640
      ],
      "parameters": {
        "text": "={{ $json.text }}",
        "options": {
          "systemMessage": "=## Role & Goal\n\nYou are a helpful WhatsApp Assistant for [Your Company Name]. Your goal is to respond to customers and determine the best response or WhatsApp template to serve them. \n\n## Task\n\nYou are given a text message from a user. You will then aim to understand how to help them. Based on their response, you will serve up a WhatsApp template or a message\n\n## Tools\n\nYou have access to the following tools:\n\n**whatsapp_consult_template**\nAction: Sends a whatsapp template that allows a user to book an appointment\nUsage: When the user's intent is to book a meeting\n\n**whatsapp_message**\nAction: Sends a text message back to the user\nUsage: When the user asks a questions, or you require more information to understand their intent\n\n## Examples\n- If a user messages \"Hello\", you respond with a greeting and asking how can [Your Company Name] help\n- If a user's intent is to book a consult, you will use the 'whatsapp_consult_template' too\n\n## Rules\n- MUST NOT do anything other than provide 'text' to the whatsapp_message tool. It should not be an object just pure text.\n\n"
        },
        "promptType": "define"
      },
      "typeVersion": 3.1
    },
    {
      "id": "8cbbb696-c1fa-4ae7-af16-725b43f6591e",
      "name": "Prepare Calendar Request",
      "type": "n8n-nodes-base.code",
      "position": [
        336,
        1680
      ],
      "parameters": {
        "jsCode": "// Prepare Google Calendar Events request for today only\n// Configure timezone to match your calendar\nconst TIMEZONE = 'Europe/Amsterdam';\n\nconst now = new Date();\n\n// Set timeMax to end of current day\nconst endOfDay = new Date(now);\nendOfDay.setHours(23, 59, 59, 999);\n\nconst calendarId = $env.GOOGLE_CALENDAR_ID || 'primary';\n\nreturn {\n  json: {\n    ...$json,\n    calendarId: calendarId,\n    timezone: TIMEZONE,\n    timeMin: now.toISOString(),\n    timeMax: endOfDay.toISOString(),\n    consultationType: $json.data.consultation_type,\n    customerName: $json.data.customer_name,\n    customerEmail: $json.data.customer_email || ''\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "04b1b51e-26d8-4c3f-b61b-2af8301dcce2",
      "name": "Google Calendar Events",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        544,
        1680
      ],
      "parameters": {
        "url": "=https://www.googleapis.com/calendar/v3/calendars/{{ encodeURIComponent($json.calendarId) }}/events",
        "options": {},
        "sendQuery": true,
        "authentication": "predefinedCredentialType",
        "queryParameters": {
          "parameters": [
            {
              "name": "timeMin",
              "value": "={{ $json.timeMin }}"
            },
            {
              "name": "timeMax",
              "value": "={{ $json.timeMax }}"
            },
            {
              "name": "timeZone",
              "value": "={{ $json.timezone }}"
            },
            {
              "name": "singleEvents",
              "value": "true"
            },
            {
              "name": "orderBy",
              "value": "startTime"
            },
            {
              "name": "maxResults",
              "value": "250"
            }
          ]
        },
        "nodeCredentialType": "googleCalendarOAuth2Api"
      },
      "typeVersion": 4.2
    },
    {
      "id": "3711b099-f69e-4fd6-956a-cd62436a68e5",
      "name": "Calculate Available Slots",
      "type": "n8n-nodes-base.code",
      "position": [
        768,
        1680
      ],
      "parameters": {
        "jsCode": "// Calculate available slots using calendar timezone\nconst TIMEZONE = $('Prepare Calendar Request').first().json.timezone;\nconst BUSINESS_START = 9;\nconst BUSINESS_END = 17;\n\nconst events = ($json.items || []).filter(e => e.status !== 'cancelled');\nconst consultationType = $('Prepare Calendar Request').first().json.consultationType;\nconst customerName = $('Prepare Calendar Request').first().json.customerName;\nconst customerEmail = $('Prepare Calendar Request').first().json.customerEmail;\nconst aesKeyBase64 = $('Prepare Calendar Request').first().json.aesKeyBase64;\nconst ivBase64 = $('Prepare Calendar Request').first().json.ivBase64;\nconst version = $('Prepare Calendar Request').first().json.version;\n\nconst slotDuration = consultationType === '60_min' ? 60 : 30;\nconst consultationLabel = consultationType === '60_min' ? '60 Minute Call' : '30 Minute Call';\n\n// Get current time in the calendar's timezone\nconst nowUtc = new Date();\nconst tzFormatter = new Intl.DateTimeFormat('en-CA', {\n  timeZone: TIMEZONE,\n  year: 'numeric', month: '2-digit', day: '2-digit',\n  hour: '2-digit', minute: '2-digit', hour12: false\n});\nconst parts = tzFormatter.formatToParts(nowUtc);\nconst getPart = (type) => parts.find(p => p.type === type)?.value;\nconst currentHour = parseInt(getPart('hour'), 10);\nconst currentMinute = parseInt(getPart('minute'), 10);\nconst currentDateStr = `${getPart('year')}-${getPart('month')}-${getPart('day')}`;\n\n// Convert events to busy time blocks\nconst busyTimes = events.map(event => {\n  if (event.start.date) {\n    return {\n      start: new Date(event.start.date + 'T00:00:00'),\n      end: new Date(event.end.date + 'T00:00:00')\n    };\n  }\n  return {\n    start: new Date(event.start.dateTime),\n    end: new Date(event.end.dateTime)\n  };\n});\n\nconst availableSlots = [];\n\n// Check today only for available slots (up to 8 slots)\nfor (let dayOffset = 0; dayOffset < 1 && availableSlots.length < 8; dayOffset++) {\n  const checkDate = new Date(nowUtc);\n  checkDate.setDate(checkDate.getDate() + dayOffset);\n  \n  // Get date string in timezone\n  const dateFormatter = new Intl.DateTimeFormat('en-CA', {\n    timeZone: TIMEZONE,\n    year: 'numeric', month: '2-digit', day: '2-digit'\n  });\n  const dateParts = dateFormatter.formatToParts(checkDate);\n  const getDatePart = (type) => dateParts.find(p => p.type === type)?.value;\n  const checkDateStr = `${getDatePart('year')}-${getDatePart('month')}-${getDatePart('day')}`;\n  \n  // Get day of week (0=Sun, 6=Sat)\n  const dayOfWeek = new Date(checkDateStr + 'T12:00:00').getDay();\n  if (dayOfWeek === 0 || dayOfWeek === 6) continue;\n\n  for (let hour = BUSINESS_START; hour < BUSINESS_END; hour++) {\n    for (let minute = 0; minute < 60; minute += 30) {\n      const slotEndHour = hour + Math.floor((minute + slotDuration) / 60);\n      const slotEndMin = (minute + slotDuration) % 60;\n      if (slotEndHour > BUSINESS_END || (slotEndHour === BUSINESS_END && slotEndMin > 0)) continue;\n\n      // Skip past slots for today\n      if (checkDateStr === currentDateStr) {\n        if (hour < currentHour || (hour === currentHour && minute <= currentMinute)) {\n          continue;\n        }\n      }\n\n      // Create slot times for conflict checking\n      const slotStartStr = `${checkDateStr}T${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}:00`;\n      const slotStart = new Date(slotStartStr);\n      const slotEnd = new Date(slotStart);\n      slotEnd.setMinutes(slotEnd.getMinutes() + slotDuration);\n\n      // Check for conflicts\n      const isConflict = busyTimes.some(busy => {\n        return (slotStart < busy.end && slotEnd > busy.start);\n      });\n\n      if (!isConflict && availableSlots.length < 8) {\n        const timeStr = `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`;\n        const displayHour = hour > 12 ? hour - 12 : hour;\n        const ampm = hour >= 12 ? 'PM' : 'AM';\n        const displayMin = minute.toString().padStart(2, '0');\n\n        availableSlots.push({\n          id: timeStr,\n          title: `${displayHour}:${displayMin} ${ampm}`\n        });\n      }\n    }\n  }\n}\n\nconst formatDate = (d) => {\n  const f = new Intl.DateTimeFormat('en-CA', {\n    timeZone: TIMEZONE,\n    year: 'numeric', month: '2-digit', day: '2-digit'\n  });\n  return f.format(d).split('/').join('-');\n};\n\nconst today = new Date();\nconst minDate = formatDate(today);\nconst maxDate = formatDate(new Date(today.getTime() + 30 * 24 * 60 * 60 * 1000));\n\nconst response = {\n  version: version || '3.0',\n  screen: 'DATE_TIME_SELECTION',\n  data: {\n    customer_name: customerName,\n    customer_email: customerEmail,\n    consultation_type: consultationType,\n    consultation_type_label: consultationLabel,\n    min_date: minDate,\n    max_date: maxDate,\n    available_slots: availableSlots\n  }\n};\n\nreturn {\n  json: {\n    response,\n    aesKeyBase64,\n    ivBase64\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "6fe22c3d-822f-48bc-8dec-59e052db19da",
      "name": "Create an event",
      "type": "n8n-nodes-base.googleCalendar",
      "position": [
        1520,
        2224
      ],
      "parameters": {
        "end": "={{ $('Check Customer Exists1').first().json.booking.appointmentEndTime }}",
        "start": "={{ $('Check Customer Exists1').first().json.booking.appointmentDateTime }}",
        "calendar": {
          "__rl": true,
          "mode": "list",
          "value": "{{GOOGLE_CALENDAR_ID}}",
          "cachedResultName": "{{GOOGLE_CALENDAR_ID}}"
        },
        "additionalFields": {
          "summary": "=\"Consultation: {{ $('Check Customer Exists1').first().json.booking.customerName }}",
          "attendees": [
            "={{ $json.fields.customer_email }}"
          ],
          "visibility": "default",
          "description": "={{ $('Check Customer Exists1').first().json.booking.consultationLabel }} consultation with {{ $('Check Customer Exists1').first().json.booking.customerName }}",
          "sendUpdates": "all",
          "guestsCanInviteOthers": true,
          "guestsCanSeeOtherGuests": true
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "ad24e842-7f34-49c2-b87a-71b8640c9aee",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1200,
        944
      ],
      "parameters": {
        "width": 848,
        "height": 4992,
        "content": "## WhatsApp Flow JSON\nCopy and paste this flow into WhatsApp Flow builder for a simple 30 / 60 min services\n```json\n{\n  \"version\": \"6.0\",\n  \"data_api_version\": \"3.0\",\n  \"routing_model\": {\n    \"SERVICE_SELECTION\": [\"DATE_TIME_SELECTION\"],\n    \"DATE_TIME_SELECTION\": [\"CONFIRMATION\"],\n    \"CONFIRMATION\": []\n  },\n  \"screens\": [\n    {\n      \"id\": \"SERVICE_SELECTION\",\n      \"title\": \"Book a Consultation\",\n      \"data\": {\n        \"consultation_types\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"properties\": {\n              \"id\": { \"type\": \"string\" },\n              \"title\": { \"type\": \"string\" }\n            }\n          },\n          \"__example__\": [\n            { \"id\": \"30_min\", \"title\": \"30 Minute Call\" },\n            { \"id\": \"60_min\", \"title\": \"60 Minute Call\" }\n          ]\n        }\n      },\n      \"layout\": {\n        \"type\": \"SingleColumnLayout\",\n        \"children\": [\n          {\n            \"type\": \"TextHeading\",\n            \"text\": \"Personal Business Consultation\"\n          },\n          {\n            \"type\": \"TextBody\",\n            \"text\": \"Please select your consultation type and provide your details.\"\n          },\n          {\n            \"type\": \"Form\",\n            \"name\": \"service_form\",\n            \"children\": [\n              {\n                \"type\": \"Dropdown\",\n                \"name\": \"consultation_type\",\n                \"label\": \"Type\",\n                \"required\": true,\n                \"data-source\": \"${data.consultation_types}\"\n              },\n              {\n                \"type\": \"TextInput\",\n                \"name\": \"customer_name\",\n                \"label\": \"Full Name\",\n                \"required\": true,\n                \"input-type\": \"text\",\n                \"helper-text\": \"Enter your full name\"\n              },\n              {\n                \"type\": \"TextInput\",\n                \"name\": \"customer_email\",\n                \"label\": \"Email\",\n                \"required\": true,\n                \"input-type\": \"email\",\n                \"helper-text\": \"We'll send a confirmation to this email\"\n              }\n            ]\n          },\n          {\n            \"type\": \"Footer\",\n            \"label\": \"Select Date & Time\",\n            \"on-click-action\": {\n              \"name\": \"data_exchange\",\n              \"payload\": {\n                \"consultation_type\": \"${form.consultation_type}\",\n                \"customer_name\": \"${form.customer_name}\",\n                \"customer_email\": \"${form.customer_email}\"\n              }\n            }\n          }\n        ]\n      }\n    },\n    {\n      \"id\": \"DATE_TIME_SELECTION\",\n      \"title\": \"Select Date & Time\",\n      \"data\": {\n        \"customer_name\": {\n          \"type\": \"string\",\n          \"__example__\": \"John Doe\"\n        },\n        \"consultation_type\": {\n          \"type\": \"string\",\n          \"__example__\": \"30_min\"\n        },\n        \"consultation_type_label\": {\n          \"type\": \"string\",\n          \"__example__\": \"30 Minute Call\"\n        },\n        \"customer_email\": {\n          \"type\": \"string\",\n          \"__example__\": \"john@example.com\"\n        },\n        \"min_date\": {\n          \"type\": \"string\",\n          \"__example__\": \"2025-01-15\"\n        },\n        \"max_date\": {\n          \"type\": \"string\",\n          \"__example__\": \"2025-02-14\"\n        },\n        \"available_slots\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"properties\": {\n              \"id\": { \"type\": \"string\" },\n              \"title\": { \"type\": \"string\" }\n            }\n          },\n          \"__example__\": [\n            { \"id\": \"09:00\", \"title\": \"9:00 AM\" },\n            { \"id\": \"10:00\", \"title\": \"10:00 AM\" },\n            { \"id\": \"11:00\", \"title\": \"11:00 AM\" },\n            { \"id\": \"14:00\", \"title\": \"2:00 PM\" },\n            { \"id\": \"15:00\", \"title\": \"3:00 PM\" }\n          ]\n        }\n      },\n      \"layout\": {\n        \"type\": \"SingleColumnLayout\",\n        \"children\": [\n          {\n            \"type\": \"TextHeading\",\n            \"text\": \"Choose Your Appointment\"\n          },\n          {\n            \"type\": \"TextBody\",\n            \"text\": \"Select a date and available time slot for your consultation.\"\n          },\n          {\n            \"type\": \"Form\",\n            \"name\": \"datetime_form\",\n            \"children\": [\n              {\n                \"type\": \"DatePicker\",\n                \"name\": \"appointment_date\",\n                \"label\": \"Date\",\n                \"required\": true,\n                \"min-date\": \"${data.min_date}\",\n                \"max-date\": \"${data.max_date}\",\n                \"helper-text\": \"Select a date within the next 30 days\",\n                \"on-select-action\": {\n                  \"name\": \"data_exchange\",\n                  \"payload\": {\n                    \"selected_date\": \"${form.appointment_date}\",\n                    \"consultation_type\": \"${data.consultation_type}\",\n                    \"customer_name\": \"${data.customer_name}\",\n                    \"customer_email\": \"${data.customer_email}\",\n                    \"action_type\": \"refresh_slots\"\n                  }\n                }\n              },\n              {\n                \"type\": \"Dropdown\",\n                \"name\": \"appointment_time\",\n                \"label\": \"Time\",\n                \"required\": true,\n                \"data-source\": \"${data.available_slots}\"\n              }\n            ]\n          },\n          {\n            \"type\": \"Footer\",\n            \"label\": \"Review Booking\",\n            \"on-click-action\": {\n              \"name\": \"data_exchange\",\n              \"payload\": {\n                \"customer_name\": \"${data.customer_name}\",\n                \"customer_email\": \"${data.customer_email}\",\n                \"consultation_type\": \"${data.consultation_type}\",\n                \"consultation_type_label\": \"${data.consultation_type_label}\",\n                \"appointment_date\": \"${form.appointment_date}\",\n                \"appointment_time\": \"${form.appointment_time}\",\n                \"action_type\": \"confirm_booking\"\n              }\n            }\n          }\n        ]\n      }\n    },\n    {\n      \"id\": \"CONFIRMATION\",\n      \"title\": \"Confirm Booking\",\n      \"terminal\": true,\n      \"data\": {\n        \"customer_name\": {\n          \"type\": \"string\",\n          \"__example__\": \"John Doe\"\n        },\n        \"customer_email\": {\n          \"type\": \"string\",\n          \"__example__\": \"john@example.com\"\n        },\n        \"consultation_type\": {\n          \"type\": \"string\",\n          \"__example__\": \"30_min\"\n        },\n        \"consultation_type_label\": {\n          \"type\": \"string\",\n          \"__example__\": \"30 Minute Call\"\n        },\n        \"appointment_date\": {\n          \"type\": \"string\",\n          \"__example__\": \"2025-01-15\"\n        },\n        \"appointment_time\": {\n          \"type\": \"string\",\n          \"__example__\": \"10:00\"\n        }\n      },\n      \"layout\": {\n        \"type\": \"SingleColumnLayout\",\n        \"children\": [\n          {\n            \"type\": \"TextHeading\",\n            \"text\": \"Booking Summary\"\n          },\n          {\n            \"type\": \"TextSubheading\",\n            \"text\": \"Please review your booking details\"\n          },\n          {\n            \"type\": \"TextCaption\",\n            \"text\": \"Name\"\n          },\n          {\n            \"type\": \"TextBody\",\n            \"text\": \"${data.customer_name}\"\n          },\n          {\n            \"type\": \"TextCaption\",\n            \"text\": \"Consultation\"\n          },\n          {\n            \"type\": \"TextBody\",\n            \"text\": \"${data.consultation_type_label}\"\n          },\n          {\n            \"type\": \"TextCaption\",\n            \"text\": \"Date\"\n          },\n          {\n            \"type\": \"TextBody\",\n            \"text\": \"${data.appointment_date}\"\n          },\n          {\n            \"type\": \"TextCaption\",\n            \"text\": \"Time\"\n          },\n          {\n            \"type\": \"TextBody\",\n            \"text\": \"${data.appointment_time}\"\n          },\n          {\n            \"type\": \"Form\",\n            \"name\": \"confirmation_form\",\n            \"children\": [\n              {\n                \"type\": \"OptIn\",\n                \"name\": \"sms_reminder\",\n                \"label\": \"Send me SMS reminders\",\n                \"required\": false\n              },\n              {\n                \"type\": \"OptIn\",\n                \"name\": \"email_reminder\",\n                \"label\": \"Send me email reminders\",\n                \"required\": false\n              }\n            ]\n          },\n          {\n            \"type\": \"Footer\",\n            \"label\": \"Confirm Booking\",\n            \"on-click-action\": {\n              \"name\": \"data_exchange\",\n              \"payload\": {\n                \"customer_name\": \"${data.customer_name}\",\n                \"customer_email\": \"${data.customer_email}\",\n                \"consultation_type\": \"${data.consultation_type}\",\n                \"consultation_type_label\": \"${data.consultation_type_label}\",\n                \"appointment_date\": \"${data.appointment_date}\",\n                \"appointment_time\": \"${data.appointment_time}\",\n                \"sms_reminder\": \"${form.sms_reminder}\",\n                \"email_reminder\": \"${form.email_reminder}\",\n                \"action_type\": \"complete_booking\"\n              }\n            }\n          }\n        ]\n      }\n    }\n  ]\n}\n```"
      },
      "typeVersion": 1
    },
    {
      "id": "5925f871-5a5e-4218-8e82-28ee84f168dc",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1200,
        304
      ],
      "parameters": {
        "width": 528,
        "height": 624,
        "content": "## Book meetings with Whatsapp and sync to Airtable and Google Calendar\n\n#### How it works\n\nThis workflows has an AI Agent that aims to classify a user's intent via WhatsApp Messages, and if they wish to book a meeting, will send a WhatsApp template with a predefined flow, that allows them to book a consultation meeting of 30 / 60 mins.\n\nDuring the WhatsApp flow, Calendar Availability is provided.\n\nOn completion, the booking is synced to both Airtable and Google Calendar, and a confirmation message is sent.\n\n#### Setup\n\n- Create a Meta Developer App + Whatsapp Business Account\n- Create a User in your Developer App for Bearer Token\n- Create a Pass Phrase for Webhook Integration and strore as a variable in n8n\n- Generate a public RSA key and upload to WhatsApp Business Enryption endpoint\n- Set up Templates and Flows in Whatsapp (Flow JSON in Sticky)\n- Attach Flow to template"
      },
      "typeVersion": 1
    }
  ],
  "connections": {
    "Switch": {
      "main": [
        [
          {
            "node": "Return 200 OK (Regular Message)",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Return 200 OK (Regular Message)",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Whatsapp Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge Paths": {
      "main": [
        [
          {
            "node": "Encrypt Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Verify Token": {
      "main": [
        [
          {
            "node": "Return Challenge",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Lookup Service": {
      "main": [
        [
          {
            "node": "Create Booking in Airtable1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Whatsapp Agent": {
      "main": [
        [
          {
            "node": "Return 200 OK (Regular Message)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create an event": {
      "main": [
        [
          {
            "node": "Update Booking with Calendar ID1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Encrypt Response": {
      "main": [
        [
          {
            "node": "Respond to Webhook",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Switch on Action": {
      "main": [
        [
          {
            "node": "Return Ping Response",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Handle INIT - Return Consultation Types",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Prepare Calendar Request",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Prepare Date Refresh Request1",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Handle Confirm Booking",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Prepare Booking Data1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "Whatsapp Agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "GET: Verify Webhook": {
      "main": [
        [
          {
            "node": "Verify Token",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is Regular Message?": {
      "main": [
        [
          {
            "node": "Switch",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Switch on Action",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Booking Data1": {
      "main": [
        [
          {
            "node": "Search Existing Customer",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "whatsapp_message_tool": {
      "ai_tool": [
        [
          {
            "node": "Whatsapp Agent",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Check Customer Exists1": {
      "main": [
        [
          {
            "node": "Upsert Customer in Airtable",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Calendar Events": {
      "main": [
        [
          {
            "node": "Calculate Available Slots",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Handle Confirm Booking": {
      "main": [
        [
          {
            "node": "Merge Paths",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "POST: Receive Messages1": {
      "main": [
        [
          {
            "node": "Decrypt WhatsApp Request1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Calculate Slots for Date": {
      "main": [
        [
          {
            "node": "Merge Paths",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Calendar Request": {
      "main": [
        [
          {
            "node": "Google Calendar Events",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search Existing Customer": {
      "main": [
        [
          {
            "node": "Check Customer Exists1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Calculate Available Slots": {
      "main": [
        [
          {
            "node": "Merge Paths",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Decrypt WhatsApp Request1": {
      "main": [
        [
          {
            "node": "Is Regular Message?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Success Response1": {
      "main": [
        [
          {
            "node": "Merge Paths",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "whatsapp_consult_template": {
      "ai_tool": [
        [
          {
            "node": "Whatsapp Agent",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Send WhatsApp Confirmation": {
      "main": [
        [
          {
            "node": "Prepare Success Response1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Booking in Airtable1": {
      "main": [
        [
          {
            "node": "Create an event",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Upsert Customer in Airtable": {
      "main": [
        [
          {
            "node": "Lookup Service",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Calendar Events (Date)": {
      "main": [
        [
          {
            "node": "Calculate Slots for Date",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Date Refresh Request1": {
      "main": [
        [
          {
            "node": "Google Calendar Events (Date)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update Booking with Calendar ID1": {
      "main": [
        [
          {
            "node": "Send WhatsApp Confirmation",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Handle INIT - Return Consultation Types": {
      "main": [
        [
          {
            "node": "Merge Paths",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}