{"workflow":{"id":13923,"name":"Schedule appointments from a booking form with Google Calendar and Gmail","views":666,"recentViews":7,"totalViews":666,"createdAt":"2026-03-06T17:01:19.653Z","description":"## How it works\n\nThis workflow creates a complete appointment booking system — no external scheduling tools needed. It serves a styled HTML booking form via webhook, checks your Google Calendar for availability, and lets visitors pick an open 30-minute slot. On submission, it creates a calendar event, sends a confirmation email via Gmail, and shows a success page.\n\n- A GET webhook serves the booking page, which reads your Google Calendar to show only open time slots for the next 30 days\n- Visitors pick a date/time, fill in their name, email, and an optional message, then submit\n- A POST webhook receives the form, calculates the end time, creates a Google Calendar event, and sends a styled HTML confirmation email with an \"Add to Calendar\" link\n- The visitor sees a branded success page confirming their booking\n\n## Set up steps\n\n1. **Google Calendar OAuth2** — Connect your Google account with Calendar read/write access. Takes ~5 minutes if you already have OAuth credentials in Google Cloud Console.\n2. **Gmail OAuth2** — Connect your Gmail account for sending confirmation emails. Can reuse the same Google OAuth app.\n3. **Configure your availability** — In the \"Calculate Available Slots\" node, adjust `workingDays` (default: Mon–Fri), `startHour`/`endHour` (default: 9 AM–2 PM), `slotDuration` (default: 30 min), and the timezone offset (default: `+02:00` for EET).\n4. **Activate the workflow** — The booking form will be live at your n8n instance's webhook URL: `your-n8n-url/webhook/booking`\n5. **Optional: Customize branding** — Edit the HTML/CSS in the \"Build HTML Form\" and \"Show Success Message\" nodes to match your brand colors and copy.","workflow":{"id":"c56XJZu8a8zCCEoo","meta":{"instanceId":"96add2e303f72aa16a39eea436903593869549c40bd26622ad76490ad225eb9e","templateCredsSetupCompleted":true},"name":"5. Schedule Form - Google calendar","tags":[],"nodes":[{"id":"997c252a-ea1c-455b-8afe-392225b73d70","name":"Code in JavaScript","type":"n8n-nodes-base.code","position":[-880,560],"parameters":{"jsCode":"const webhookData = $input.first().json.body;\nconst startTime = webhookData.timeslot;\n\n// Parse the start time WITH timezone: \"2025-11-21T10:30:00+02:00\"\nconst match = startTime.match(/^(.+T)(\\d{2}):(\\d{2}):(\\d{2})(\\+\\d{2}:\\d{2})$/);\nif (!match) {\n  throw new Error('Invalid time format');\n}\n\nconst [_, prefix, hours, minutes, seconds, timezone] = match;\nconst totalMinutes = parseInt(hours) * 60 + parseInt(minutes) + 30;\nconst newHours = Math.floor(totalMinutes / 60);\nconst newMinutes = totalMinutes % 60;\n\n// IMPORTANT: Keep the timezone offset in the endTime\nconst endTime = `${prefix}${String(newHours).padStart(2, '0')}:${String(newMinutes).padStart(2, '0')}:${seconds}${timezone}`;\n\nreturn [{\n  json: {\n    body: {\n      ...webhookData,\n      startTime: startTime,\n      endTime: endTime\n    }\n  }\n}];"},"typeVersion":2},{"id":"a94a2cdc-0ad0-480d-8e58-5352f2416756","name":"Show Success Message","type":"n8n-nodes-base.respondToWebhook","position":[0,560],"parameters":{"options":{"responseHeaders":{"entries":[{"name":"Content-Type","value":"text/html"}]}},"respondWith":"text","responseBody":"<!DOCTYPE html>\n<html>\n<head>\n    <meta charset=\"UTF-8\">\n    <style>\n        body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; display: flex; justify-content: center; align-items: center; margin: 0; padding: 20px; }\n        .success-box { background: white; border-radius: 20px; padding: 60px 50px; text-align: center; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); max-width: 500px; animation: slideUp 0.5s ease; }\n        @keyframes slideUp { from { opacity: 0; transform: translateY(30px); } to { opacity: 1; transform: translateY(0); } }\n        .checkmark { width: 80px; height: 80px; border-radius: 50%; background: #4CAF50; display: inline-flex; align-items: center; justify-content: center; font-size: 50px; color: white; margin-bottom: 20px; }\n        h1 { color: #667eea; margin: 20px 0 10px 0; font-size: 32px; }\n        p { color: #666; font-size: 16px; line-height: 1.6; margin: 10px 0; }\n        .email-note { background: #f0f7ff; padding: 15px; border-radius: 10px; margin-top: 25px; border-left: 4px solid #667eea; }\n    </style>\n</head>\n<body>\n    <div class=\"success-box\">\n        <div class=\"checkmark\">✓</div>\n        <h1>Booking Confirmed!</h1>\n        <p>Your appointment has been successfully scheduled.</p>\n        <div class=\"email-note\"><p><strong>📧 Check your email</strong></p><p>A confirmation has been sent to your inbox.</p></div>\n    </div>\n</body>\n</html>"},"typeVersion":1.1},{"id":"7887c143-437b-49b1-bc52-09d0cc02e42b","name":"Send Confirmation Email","type":"n8n-nodes-base.gmail","position":[-208,560],"webhookId":"0c0fb20b-2b00-4981-9fb0-ca9f27812b91","parameters":{"sendTo":"={{ $json.email }}","message":"=<!DOCTYPE html>\n<html>\n<head>\n    <style>\n        body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; margin: 0; padding: 0; }\n        .container { max-width: 600px; margin: 0 auto; background: #ffffff; }\n        .header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 40px 30px; text-align: center; }\n        .header h1 { margin: 0; font-size: 28px; }\n        .content { padding: 40px 30px; background: #f9f9f9; }\n        .details { background: white; padding: 25px; border-radius: 8px; margin: 25px 0; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }\n        .detail-row { padding: 12px 0; border-bottom: 1px solid #eee; display: flex; }\n        .detail-row:last-child { border-bottom: none; }\n        .label { font-weight: bold; color: #667eea; min-width: 120px; }\n        .value { flex: 1; color: #333; }\n        .button-container { text-align: center; margin: 30px 0; }\n        .button { display: inline-block; padding: 14px 35px; background: #667eea; color: white !important; text-decoration: none; border-radius: 8px; font-weight: 600; }\n        .message-box { background: #fff; padding: 20px; border-radius: 8px; margin: 20px 0; border-left: 4px solid #667eea; }\n        .footer { text-align: center; color: #999; font-size: 12px; padding: 30px; background: #f0f0f0; }\n    </style>\n</head>\n<body>\n    <div class=\"container\">\n        <div class=\"header\"><h1>✅ Appointment Confirmed!</h1></div>\n        <div class=\"content\">\n            <p>Dear {{ $json.firstName }} {{ $json.lastName }},</p>\n            <p>Thank you for booking an appointment!</p>\n            <div class=\"details\">\n                <div class=\"detail-row\"><span class=\"label\">📅 Date:</span><span class=\"value\">{{ $json.formattedDate }}</span></div>\n                <div class=\"detail-row\"><span class=\"label\">🕐 Time:</span><span class=\"value\">{{ $json.formattedTime }}</span></div>\n                <div class=\"detail-row\"><span class=\"label\">⏱️ Duration:</span><span class=\"value\">30 minutes</span></div>\n                <div class=\"detail-row\"><span class=\"label\">✉️ Email:</span><span class=\"value\">{{ $json.email }}</span></div>\n            </div>\n            <div class=\"message-box\"><h3 style=\"margin-top:0;color:#667eea;\">Your Message:</h3><p style=\"margin:0;color:#666;\">{{ $json.message }}</p></div>\n            <div class=\"button-container\"><a href=\"{{ $json.htmlLink }}\" class=\"button\">📅 Add to Google Calendar</a></div>\n            <p style=\"margin-top:30px;padding:20px;background:#fff;border-radius:8px;\"><strong>Need to make changes?</strong><br>If you need to reschedule or cancel, please contact us.</p>\n        </div>\n        <div class=\"footer\"><p><strong>This is an automated confirmation email.</strong></p></div>\n    </div>\n</body>\n</html>","options":{},"subject":"=✅ Appointment Confirmed - {{ $json.formattedDate }}"},"credentials":{"gmailOAuth2":{"id":"dNdC9dt0sd3OjzvS","name":"Gmail account"}},"typeVersion":2.1},{"id":"f296c75f-87bb-4463-8504-56534c918486","name":"Format Email Data","type":"n8n-nodes-base.code","position":[-432,560],"parameters":{"jsCode":"const eventData = $input.first().json;\nconst webhookData = $('Webhook - Submit Form').first().json.body;\n\nconst startDate = new Date(eventData.start.dateTime);\nconst endDate = new Date(eventData.end.dateTime);\n\nconst formattedDate = startDate.toLocaleDateString('en-US', {\n  weekday: 'long',\n  year: 'numeric',\n  month: 'long',\n  day: 'numeric'\n});\n\nconst formattedTime = startDate.toLocaleTimeString('en-US', {\n  hour: '2-digit',\n  minute: '2-digit',\n  hour12: true\n}) + ' - ' + endDate.toLocaleTimeString('en-US', {\n  hour: '2-digit',\n  minute: '2-digit',\n  hour12: true\n});\n\nreturn [{\n  json: {\n    ...eventData,\n    formattedDate,\n    formattedTime,\n    firstName: webhookData.firstName,\n    lastName: webhookData.lastName,\n    email: webhookData.email,\n    message: webhookData.message || 'No message provided'\n  }\n}];"},"typeVersion":2},{"id":"e0130056-a309-4a1f-bf22-7dc192bd2fce","name":"Create Calendar Event","type":"n8n-nodes-base.googleCalendar","position":[-656,560],"parameters":{"end":"={{ $json.body.endTime }}","start":"={{ $json.body.timeslot }}","calendar":{"__rl":true,"mode":"list","value":"primary","cachedResultName":"Primary"},"additionalFields":{"summary":"=Meeting with {{ $json.body.firstName }} {{ $json.body.lastName }}","description":"=Name: {{ $json.body.firstName }} {{ $json.body.lastName }} \nEmail: {{ $json.body.email }}  \n\nMessage: {{ $json.body.message || 'No message provided' }}"}},"credentials":{"googleCalendarOAuth2Api":{"id":"ilF4WKKHVd67Oi8v","name":"Google Calendar account"}},"typeVersion":1.2},{"id":"3124fd95-0fd3-4f81-98e9-86b5fb76400c","name":"Webhook - Submit Form","type":"n8n-nodes-base.webhook","position":[-1088,560],"webhookId":"booking-form","parameters":{"path":"booking","options":{},"httpMethod":"POST","responseMode":"responseNode"},"typeVersion":1.1},{"id":"6c9e11ac-bda2-4e73-abf7-4b3d43d5e802","name":"Respond to Webhook","type":"n8n-nodes-base.respondToWebhook","position":[-208,336],"parameters":{"options":{"responseHeaders":{"entries":[{"name":"Content-Type","value":"text/html"}]}},"respondWith":"text","responseBody":"={{$json.html}}"},"typeVersion":1.1},{"id":"b9808377-3551-4192-9124-0b7d2f452586","name":"Build HTML Form","type":"n8n-nodes-base.code","position":[-416,336],"parameters":{"jsCode":"// Get all slots\nconst items = $input.all();\nconst slots = items.map(item => item.json);\n\n// FIXED: Manual time formatter (NO timezone conversion)\nfunction formatSlotTimeFromValue(value) {\n  const [datePart, timePart] = value.split(\"T\"); // \"2025-11-21\", \"08:00:00\" (no timezone offset anymore)\n  const [hour, minute] = timePart.split(\":\");\n\n  let h = parseInt(hour, 10);\n  const ampm = h >= 12 ? \"PM\" : \"AM\";\n  h = h % 12 || 12;\n\n  return `${h}:${minute} ${ampm}`;\n}\n\n// Group slots by date WITHOUT using new Date for timezone\nconst slotsByDate = {};\nslots.forEach(slot => {\n  const [datePart] = slot.value.split(\"T\"); // \"2025-11-21\"\n  \n  if (!slotsByDate[datePart]) {\n    // Use a fixed date string so browser cannot shift timezone\n    const displayDateObj = new Date(datePart + \"T00:00:00\");\n\n    slotsByDate[datePart] = {\n      date: displayDateObj,\n      dateKey: datePart,\n      displayDate: displayDateObj.toLocaleDateString('en-US', {\n        weekday: 'long',\n        month: 'long',\n        day: 'numeric',\n        year: 'numeric'\n      }),\n      shortDate: displayDateObj.toLocaleDateString('en-US', {\n        month: 'short',\n        day: 'numeric'\n      }),\n      slots: []\n    };\n  }\n\n  slotsByDate[datePart].slots.push({\n    value: slot.value,\n    time: formatSlotTimeFromValue(slot.value)\n  });\n});\n\n// Convert to array and sort\nconst groupedSlots = Object.values(slotsByDate).sort((a, b) => a.date - b.date);\n\nconst slotsJson = JSON.stringify(groupedSlots);\n\nconst html = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>Book an Appointment</title>\n    <style>\n        * { margin: 0; padding: 0; box-sizing: border-box; }\n        \n        body {\n            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;\n            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n            min-height: 100vh;\n            display: flex;\n            justify-content: center;\n            align-items: center;\n            padding: 20px;\n        }\n        \n        .container {\n            background: white;\n            border-radius: 20px;\n            box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);\n            max-width: 700px;\n            width: 100%;\n            padding: 40px;\n        }\n        \n        h1 {\n            color: #667eea;\n            text-align: center;\n            margin-bottom: 10px;\n            font-size: 32px;\n        }\n        \n        .subtitle {\n            text-align: center;\n            color: #666;\n            margin-bottom: 30px;\n            font-size: 14px;\n        }\n        \n        /* Date Navigator */\n        .date-navigator {\n            display: flex;\n            align-items: center;\n            justify-content: space-between;\n            margin-bottom: 20px;\n            padding: 15px;\n            background: #f9f9f9;\n            border-radius: 12px;\n        }\n        \n        .nav-button {\n            background: #667eea;\n            color: white;\n            border: none;\n            width: 40px;\n            height: 40px;\n            border-radius: 50%;\n            cursor: pointer;\n            font-size: 20px;\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            transition: all 0.2s ease;\n        }\n        \n        .nav-button:hover {\n            background: #5568d3;\n            transform: scale(1.1);\n        }\n        \n        .nav-button:disabled {\n            background: #ccc;\n            cursor: not-allowed;\n            transform: scale(1);\n        }\n        \n        .current-date {\n            font-size: 18px;\n            font-weight: 600;\n            color: #667eea;\n            text-align: center;\n            flex: 1;\n        }\n        \n        /* Time Slots */\n        .slots-container {\n            min-height: 200px;\n            margin-bottom: 30px;\n        }\n        \n        .time-slots {\n            display: grid;\n            grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));\n            gap: 10px;\n            animation: fadeIn 0.3s ease;\n        }\n        \n        @keyframes fadeIn {\n            from { opacity: 0; transform: translateY(10px); }\n            to { opacity: 1; transform: translateY(0); }\n        }\n        \n        .time-slot {\n            padding: 12px;\n            border: 2px solid #e0e0e0;\n            border-radius: 10px;\n            background: white;\n            cursor: pointer;\n            text-align: center;\n            transition: all 0.2s ease;\n            font-size: 15px;\n            font-weight: 500;\n        }\n        \n        .time-slot:hover {\n            border-color: #667eea;\n            background: #f0f7ff;\n            transform: translateY(-2px);\n        }\n        \n        .time-slot.selected {\n            background: #667eea;\n            color: white;\n            border-color: #667eea;\n        }\n        \n        .no-slots-message {\n            text-align: center;\n            padding: 40px;\n            color: #999;\n            font-style: italic;\n        }\n        \n        /* Selected Info */\n        .selected-info {\n            background: #f0f7ff;\n            padding: 15px 20px;\n            border-radius: 10px;\n            margin-bottom: 25px;\n            border-left: 4px solid #667eea;\n            display: none;\n        }\n        \n        .selected-info.visible {\n            display: block;\n            animation: fadeIn 0.3s ease;\n        }\n        \n        .selected-info strong {\n            color: #667eea;\n        }\n        \n        /* Form */\n        .form-divider {\n            height: 2px;\n            background: linear-gradient(90deg, transparent, #e0e0e0, transparent);\n            margin: 30px 0;\n        }\n        \n        .form-section-title {\n            font-size: 18px;\n            color: #667eea;\n            margin-bottom: 20px;\n            font-weight: 600;\n        }\n        \n        .form-group {\n            margin-bottom: 20px;\n        }\n        \n        label {\n            display: block;\n            margin-bottom: 8px;\n            color: #333;\n            font-weight: 600;\n            font-size: 14px;\n        }\n        \n        input[type=\"text\"],\n        input[type=\"email\"],\n        textarea {\n            width: 100%;\n            padding: 12px 15px;\n            border: 2px solid #e0e0e0;\n            border-radius: 10px;\n            font-size: 15px;\n            transition: all 0.3s ease;\n            font-family: inherit;\n        }\n        \n        input[type=\"text\"]:focus,\n        input[type=\"email\"]:focus,\n        textarea:focus {\n            outline: none;\n            border-color: #667eea;\n            box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);\n        }\n        \n        textarea {\n            resize: vertical;\n            min-height: 100px;\n        }\n        \n        .name-group {\n            display: grid;\n            grid-template-columns: 1fr 1fr;\n            gap: 15px;\n        }\n        \n        button[type=\"submit\"] {\n            width: 100%;\n            padding: 15px;\n            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n            color: white;\n            border: none;\n            border-radius: 10px;\n            font-size: 16px;\n            font-weight: 600;\n            cursor: pointer;\n            transition: transform 0.2s ease, box-shadow 0.2s ease;\n            margin-top: 10px;\n        }\n        \n        button[type=\"submit\"]:hover {\n            transform: translateY(-2px);\n            box-shadow: 0 10px 25px rgba(102, 126, 234, 0.4);\n        }\n        \n        button[type=\"submit\"]:disabled {\n            opacity: 0.5;\n            cursor: not-allowed;\n            transform: none;\n        }\n        \n        .required {\n            color: #e74c3c;\n        }\n        \n        @media (max-width: 600px) {\n            .container {\n                padding: 30px 20px;\n            }\n            \n            .time-slots {\n                grid-template-columns: repeat(2, 1fr);\n            }\n            \n            .name-group {\n                grid-template-columns: 1fr;\n            }\n            \n            h1 {\n                font-size: 26px;\n            }\n            \n            .nav-button {\n                width: 35px;\n                height: 35px;\n                font-size: 18px;\n            }\n            \n            .current-date {\n                font-size: 16px;\n            }\n        }\n    </style>\n</head>\n<body>\n    <div class=\"container\">\n        <h1>📅 Book Your Appointment</h1>\n        <p class=\"subtitle\">Select your preferred date and time</p>\n        \n        <form method=\"POST\" action=\"\" id=\"bookingForm\">\n            <!-- Date Navigator -->\n            <div class=\"date-navigator\">\n                <button type=\"button\" class=\"nav-button\" id=\"prevDay\">‹</button>\n                <div class=\"current-date\" id=\"currentDate\"></div>\n                <button type=\"button\" class=\"nav-button\" id=\"nextDay\">›</button>\n            </div>\n            \n            <!-- Time Slots -->\n            <div class=\"slots-container\">\n                <div class=\"time-slots\" id=\"timeSlotsContainer\"></div>\n            </div>\n            \n            <input type=\"hidden\" name=\"timeslot\" id=\"selectedSlot\" required>\n            \n            <!-- Selected Slot Info -->\n            <div class=\"selected-info\" id=\"selectedInfo\">\n                <strong>Selected time:</strong> <span id=\"selectedSlotDisplay\"></span>\n            </div>\n            \n            <!-- Form Divider -->\n            <div class=\"form-divider\"></div>\n            \n            <!-- User Details Form -->\n            <div class=\"form-section-title\">Your Details</div>\n            \n            <div class=\"name-group\">\n                <div class=\"form-group\">\n                    <label for=\"firstName\">First Name <span class=\"required\">*</span></label>\n                    <input type=\"text\" id=\"firstName\" name=\"firstName\" required>\n                </div>\n                \n                <div class=\"form-group\">\n                    <label for=\"lastName\">Last Name <span class=\"required\">*</span></label>\n                    <input type=\"text\" id=\"lastName\" name=\"lastName\" required>\n                </div>\n            </div>\n            \n            <div class=\"form-group\">\n                <label for=\"email\">Email Address <span class=\"required\">*</span></label>\n                <input type=\"email\" id=\"email\" name=\"email\" required>\n            </div>\n            \n            <div class=\"form-group\">\n                <label for=\"message\">Message (Optional)</label>\n                <textarea id=\"message\" name=\"message\" placeholder=\"Add any additional information...\"></textarea>\n            </div>\n            \n            <button type=\"submit\" id=\"submitButton\">Confirm Booking</button>\n        </form>\n    </div>\n    \n    <script>\n        const slotsByDate = ${slotsJson};\n        let currentDayIndex = 0;\n        let selectedSlotElement = null;\n        \n        const prevButton = document.getElementById('prevDay');\n        const nextButton = document.getElementById('nextDay');\n        const currentDateDisplay = document.getElementById('currentDate');\n        const timeSlotsContainer = document.getElementById('timeSlotsContainer');\n        const selectedSlotInput = document.getElementById('selectedSlot');\n        const selectedSlotDisplay = document.getElementById('selectedSlotDisplay');\n        const selectedInfo = document.getElementById('selectedInfo');\n        const submitButton = document.getElementById('submitButton');\n        \n        function renderDay(index) {\n            currentDayIndex = index;\n            \n            // Update navigation buttons\n            prevButton.disabled = index === 0;\n            nextButton.disabled = index === slotsByDate.length - 1;\n            \n            // Update date display\n            const dayData = slotsByDate[index];\n            currentDateDisplay.textContent = dayData.displayDate;\n            \n            // Clear previous slots\n            timeSlotsContainer.innerHTML = '';\n            \n            // Render time slots\n            if (dayData.slots.length > 0) {\n                dayData.slots.forEach(slot => {\n                    const timeSlot = document.createElement('div');\n                    timeSlot.className = 'time-slot';\n                    timeSlot.textContent = slot.time;\n                    timeSlot.dataset.value = slot.value;\n                    timeSlot.dataset.label = dayData.displayDate + ' at ' + slot.time;\n                    \n                    timeSlot.addEventListener('click', function() {\n                        // Remove previous selection\n                        if (selectedSlotElement) {\n                            selectedSlotElement.classList.remove('selected');\n                        }\n                        \n                        // Select new slot\n                        this.classList.add('selected');\n                        selectedSlotElement = this;\n                        selectedSlotInput.value = this.dataset.value;\n                        selectedSlotDisplay.textContent = this.dataset.label;\n                        selectedInfo.classList.add('visible');\n                        submitButton.disabled = false;\n                    });\n                    \n                    timeSlotsContainer.appendChild(timeSlot);\n                });\n            } else {\n                timeSlotsContainer.innerHTML = '<div class=\"no-slots-message\">No available slots for this day</div>';\n            }\n        }\n        \n        // Navigation\n        prevButton.addEventListener('click', () => {\n            if (currentDayIndex > 0) {\n                renderDay(currentDayIndex - 1);\n            }\n        });\n        \n        nextButton.addEventListener('click', () => {\n            if (currentDayIndex < slotsByDate.length - 1) {\n                renderDay(currentDayIndex + 1);\n            }\n        });\n        \n        // Keyboard navigation\n        document.addEventListener('keydown', (e) => {\n            if (e.key === 'ArrowLeft' && currentDayIndex > 0) {\n                renderDay(currentDayIndex - 1);\n            } else if (e.key === 'ArrowRight' && currentDayIndex < slotsByDate.length - 1) {\n                renderDay(currentDayIndex + 1);\n            }\n        });\n        \n        // Form validation\n        document.getElementById('bookingForm').addEventListener('submit', function(e) {\n            if (!selectedSlotInput.value) {\n                e.preventDefault();\n                alert('Please select a time slot first');\n            }\n        });\n        \n        // Initialize with first day\n        if (slotsByDate.length > 0) {\n            renderDay(0);\n            submitButton.disabled = true;\n        } else {\n            currentDateDisplay.textContent = 'No available dates';\n            timeSlotsContainer.innerHTML = '<div class=\"no-slots-message\">😔 No available slots in the next 30 days</div>';\n            prevButton.disabled = true;\n            nextButton.disabled = true;\n            submitButton.disabled = true;\n        }\n    </script>\n</body>\n</html>`;\n\nreturn [{ json: { html: html } }];"},"typeVersion":2},{"id":"796ed038-04a1-479e-8ac8-f162b22319ad","name":"Calculate Available Slots","type":"n8n-nodes-base.code","position":[-640,336],"parameters":{"jsCode":"// PROPER TIMEZONE FIX\n// Server timezone: Berlin (CET/CEST, UTC+1)\n// Target timezone: Greece (EET/EEST, UTC+2)\n// Difference: Greece is 1 hour ahead of Berlin\n\n// Configuration\nconst workingDays = [1, 2, 3, 4, 5];\nconst startHour = 9;\nconst endHour = 14;\nconst slotDuration = 30;\n\nconst now = new Date();\nconst inputItems = $input.all();\n\n// Get existing events - only timed events within 24 hours\nconst existingEvents = [];\nif (inputItems.length > 0) {\n  for (const item of inputItems) {\n    const event = item.json;\n    \n    if (!event.start || !event.start.dateTime) {\n      continue;\n    }\n    \n    const eventStart = new Date(event.start.dateTime);\n    const eventEnd = new Date(event.end.dateTime);\n    const eventDurationHours = (eventEnd - eventStart) / (1000 * 60 * 60);\n    \n    if (eventDurationHours > 24) {\n      continue;\n    }\n    \n    const windowEnd = new Date(now);\n    windowEnd.setDate(now.getDate() + 30);\n    \n    if (eventStart < windowEnd && eventEnd > now) {\n      existingEvents.push({ start: eventStart, end: eventEnd });\n    }\n  }\n}\n\n// Generate slots\nconst slots = [];\n\nfor (let day = 0; day < 30; day++) {\n  const currentDate = new Date(now);\n  currentDate.setDate(now.getDate() + day);\n  \n  const year = currentDate.getFullYear();\n  const month = currentDate.getMonth() + 1;\n  const dayOfMonth = currentDate.getDate();\n  const dayOfWeek = currentDate.getDay();\n  \n  if (!workingDays.includes(dayOfWeek)) continue;\n  \n  for (let hour = startHour; hour < endHour; hour++) {\n    for (let minute = 0; minute < 60; minute += slotDuration) {\n      // Create slot time with EXPLICIT Greek timezone offset\n      // This tells Google Calendar: \"This is the time in Greece (UTC+2)\"\n      const slotStartString = `${year}-${String(month).padStart(2, '0')}-${String(dayOfMonth).padStart(2, '0')}T${String(hour).padStart(2, '0')}:${String(minute).padStart(2, '0')}:00+02:00`;\n      const slotStart = new Date(slotStartString);\n      const slotEnd = new Date(slotStart.getTime() + slotDuration * 60000);\n      \n      if (slotStart <= now) continue;\n      \n      let isAvailable = true;\n      for (const event of existingEvents) {\n        if ((slotStart >= event.start && slotStart < event.end) ||\n            (slotEnd > event.start && slotEnd <= event.end) ||\n            (slotStart <= event.start && slotEnd >= event.end)) {\n          isAvailable = false;\n          break;\n        }\n      }\n      \n      if (isAvailable) {\n        // CRITICAL: Send the FULL timestamp WITH timezone to Google Calendar\n        // This ensures Google knows it's Greek time, not Berlin time\n        const valueWithTimezone = slotStartString;\n        \n        // Format label for display\n        const weekdays = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];\n        const months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];\n        \n        const displayHour12 = hour % 12 || 12;\n        const ampm = hour >= 12 ? 'PM' : 'AM';\n        \n        const label = `${weekdays[dayOfWeek]}, ${months[month - 1]} ${dayOfMonth}, ${year} at ${displayHour12}:${String(minute).padStart(2, '0')} ${ampm}`;\n        \n        slots.push({\n          value: valueWithTimezone,\n          label: label\n        });\n      }\n    }\n  }\n}\n\nif (slots.length === 0) {\n  return [{ json: { noSlots: true, slots: [] } }];\n}\n\nreturn slots.map(slot => ({ json: slot }));"},"typeVersion":2},{"id":"4b727001-37aa-42df-bb4c-98b75bbe3510","name":"Get Calendar Events","type":"n8n-nodes-base.googleCalendar","position":[-864,336],"parameters":{"options":{"timeMax":"={{ $now.plus({ days: 30 }).toISO() }}","timeMin":"={{ $now.toISO() }}"},"calendar":{"__rl":true,"mode":"list","value":"primary","cachedResultName":"Primary"},"operation":"getAll","returnAll":true},"credentials":{"googleCalendarOAuth2Api":{"id":"ilF4WKKHVd67Oi8v","name":"Google Calendar account"}},"typeVersion":1.2},{"id":"1f9bede9-b36a-4ebf-9697-12c53d5a3146","name":"Webhook - Show Form","type":"n8n-nodes-base.webhook","position":[-1088,336],"webhookId":"booking-form","parameters":{"path":"booking","options":{},"responseMode":"responseNode"},"typeVersion":1.1},{"id":"1085bf5c-a45a-4bf9-ade3-dd76aab87cf5","name":"Sticky Note","type":"n8n-nodes-base.stickyNote","position":[-1056,-80],"parameters":{"width":864,"height":352,"content":"## Self-Hosted Booking Form with Google Calendar\n\nThis workflow has **two paths**:\n\n**Top row (GET):** Serves the booking form\nWebhook → Get Calendar Events → Calculate Available Slots → Build HTML Form → Respond\n\n**Bottom row (POST):** Processes a booking submission\nWebhook → Calculate End Time → Create Calendar Event → Format Email Data → Send Confirmation → Show Success Page\n\n### Quick Start\n1. Connect Google Calendar OAuth2\n2. Connect Gmail OAuth2\n3. Adjust availability settings in \"Calculate Available Slots\"\n4. Activate & visit: `<your-n8n-url>/webhook/booking`"},"typeVersion":1}],"active":true,"pinData":{},"settings":{"executionOrder":"v1"},"versionId":"3bdcbe12-4da6-4a76-85a7-7b591c3e988a","connections":{"Build HTML Form":{"main":[[{"node":"Respond to Webhook","type":"main","index":0}]]},"Format Email Data":{"main":[[{"node":"Send Confirmation Email","type":"main","index":0}]]},"Code in JavaScript":{"main":[[{"node":"Create Calendar Event","type":"main","index":0}]]},"Get Calendar Events":{"main":[[{"node":"Calculate Available Slots","type":"main","index":0}]]},"Webhook - Show Form":{"main":[[{"node":"Get Calendar Events","type":"main","index":0}]]},"Create Calendar Event":{"main":[[{"node":"Format Email Data","type":"main","index":0}]]},"Webhook - Submit Form":{"main":[[{"node":"Code in JavaScript","type":"main","index":0}]]},"Send Confirmation Email":{"main":[[{"node":"Show Success Message","type":"main","index":0}]]},"Calculate Available Slots":{"main":[[{"node":"Build HTML Form","type":"main","index":0}]]}}},"lastUpdatedBy":1,"workflowInfo":{"nodeCount":12,"nodeTypes":{"n8n-nodes-base.code":{"count":4},"n8n-nodes-base.gmail":{"count":1},"n8n-nodes-base.webhook":{"count":2},"n8n-nodes-base.stickyNote":{"count":1},"n8n-nodes-base.googleCalendar":{"count":2},"n8n-nodes-base.respondToWebhook":{"count":2}}},"status":"published","readyToDemo":null,"user":{"name":"Theodoros Mastromanolis","username":"teomastro","bio":"Software Engineer, Greece","verified":true,"links":["https://teomastro.com/"],"avatar":"https://gravatar.com/avatar/c6cdc90428b7945c9b4d04fdc5f5922c7cac54304d6d18a114d13a1986cd7085?r=pg&d=retro&size=200"},"nodes":[{"id":47,"icon":"file:webhook.svg","name":"n8n-nodes-base.webhook","codex":{"data":{"alias":["HTTP","API","Build","WH"],"resources":{"generic":[{"url":"https://n8n.io/blog/learn-how-to-automatically-cross-post-your-content-with-n8n/","icon":"✍️","label":"Learn how to automatically cross-post your content with n8n"},{"url":"https://n8n.io/blog/running-n8n-on-ships-an-interview-with-maranics/","icon":"🛳","label":"Running n8n on ships: An interview with Maranics"},{"url":"https://n8n.io/blog/how-to-build-a-low-code-self-hosted-url-shortener/","icon":"🔗","label":"How to build a low-code, self-hosted URL shortener in 3 steps"},{"url":"https://n8n.io/blog/what-are-apis-how-to-use-them-with-no-code/","icon":" 🪢","label":"What are APIs and how to use them with no code"},{"url":"https://n8n.io/blog/5-tasks-you-can-automate-with-notion-api/","icon":"⚡️","label":"5 tasks you can automate with the new Notion API "},{"url":"https://n8n.io/blog/how-a-digital-strategist-uses-n8n-for-online-marketing/","icon":"💻","label":"How a digital strategist uses n8n for online marketing"},{"url":"https://n8n.io/blog/the-ultimate-guide-to-automate-your-video-collaboration-with-whereby-mattermost-and-n8n/","icon":"📹","label":"The ultimate guide to automate your video collaboration with Whereby, Mattermost, and n8n"},{"url":"https://n8n.io/blog/how-to-automatically-give-kudos-to-contributors-with-github-slack-and-n8n/","icon":"👏","label":"How to automatically give kudos to contributors with GitHub, Slack, and n8n"},{"url":"https://n8n.io/blog/5-workflow-automations-for-mattermost-that-we-love-at-n8n/","icon":"🤖","label":"5 workflow automations for Mattermost that we love at n8n"},{"url":"https://n8n.io/blog/why-this-product-manager-loves-workflow-automation-with-n8n/","icon":"🧠","label":"Why this Product Manager loves workflow automation with n8n"},{"url":"https://n8n.io/blog/creating-custom-incident-response-workflows-with-n8n/","label":"How to automate every step of an incident response workflow"},{"url":"https://n8n.io/blog/learn-to-build-powerful-api-endpoints-using-webhooks/","icon":"🧰","label":"Learn to Build Powerful API Endpoints Using Webhooks"},{"url":"https://n8n.io/blog/learn-how-to-use-webhooks-with-mattermost-slash-commands/","icon":"🦄","label":"Learn how to use webhooks with Mattermost slash commands"},{"url":"https://n8n.io/blog/how-goomer-automated-their-operations-with-over-200-n8n-workflows/","icon":"🛵","label":"How Goomer automated their operations with over 200 n8n workflows"}],"primaryDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.webhook/"}]},"categories":["Development","Core Nodes"],"nodeVersion":"1.0","codexVersion":"1.0","subcategories":{"Core Nodes":["Helpers"]}}},"group":"[\"trigger\"]","defaults":{"name":"Webhook"},"iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCI+PHBhdGggZmlsbD0iIzM3NDc0ZiIgZD0iTTM1IDM3Yy0yLjIgMC00LTEuOC00LTRzMS44LTQgNC00IDQgMS44IDQgNC0xLjggNC00IDQiLz48cGF0aCBmaWxsPSIjMzc0NzRmIiBkPSJNMzUgNDNjLTMgMC01LjktMS40LTcuOC0zLjdsMy4xLTIuNWMxLjEgMS40IDIuOSAyLjMgNC43IDIuMyAzLjMgMCA2LTIuNyA2LTZzLTIuNy02LTYtNmMtMSAwLTIgLjMtMi45LjdsLTEuNyAxTDIzLjMgMTZsMy41LTEuOSA1LjMgOS40YzEtLjMgMi0uNSAzLS41IDUuNSAwIDEwIDQuNSAxMCAxMFM0MC41IDQzIDM1IDQzIi8+PHBhdGggZmlsbD0iIzM3NDc0ZiIgZD0iTTE0IDQzQzguNSA0MyA0IDM4LjUgNCAzM2MwLTQuNiAzLjEtOC41IDcuNS05LjdsMSAzLjlDOS45IDI3LjkgOCAzMC4zIDggMzNjMCAzLjMgMi43IDYgNiA2czYtMi43IDYtNnYtMmgxNXY0SDIzLjhjLS45IDQuNi01IDgtOS44IDgiLz48cGF0aCBmaWxsPSIjZTkxZTYzIiBkPSJNMTQgMzdjLTIuMiAwLTQtMS44LTQtNHMxLjgtNCA0LTQgNCAxLjggNCA0LTEuOCA0LTQgNCIvPjxwYXRoIGZpbGw9IiMzNzQ3NGYiIGQ9Ik0yNSAxOWMtMi4yIDAtNC0xLjgtNC00czEuOC00IDQtNCA0IDEuOCA0IDQtMS44IDQtNCA0Ii8+PHBhdGggZmlsbD0iI2U5MWU2MyIgZD0ibTE1LjcgMzQtMy40LTIgNS45LTkuN2MtMi0xLjktMy4yLTQuNS0zLjItNy4zIDAtNS41IDQuNS0xMCAxMC0xMHMxMCA0LjUgMTAgMTBjMCAuOS0uMSAxLjctLjMgMi41bC0zLjktMWMuMS0uNS4yLTEgLjItMS41IDAtMy4zLTIuNy02LTYtNnMtNiAyLjctNiA2YzAgMi4xIDEuMSA0IDIuOSA1LjFsMS43IDF6Ii8+PC9zdmc+"},"displayName":"Webhook","typeVersion":2,"nodeCategories":[{"id":5,"name":"Development"},{"id":9,"name":"Core Nodes"}]},{"id":317,"icon":"file:googleCalendar.svg","name":"n8n-nodes-base.googleCalendar","codex":{"data":{"resources":{"generic":[{"url":"https://n8n.io/blog/how-to-host-virtual-coffee-breaks-with-n8n/","icon":"☕️","label":"How to host virtual coffee breaks with n8n"},{"url":"https://n8n.io/blog/supercharging-your-conference-registration-process-with-n8n/","icon":"🎫","label":"Supercharging your conference registration process with n8n"},{"url":"https://n8n.io/blog/automate-google-apps-for-productivity/","icon":"💡","label":"15 Google apps you can combine and automate to increase productivity"},{"url":"https://n8n.io/blog/your-business-doesnt-need-you-to-operate/","icon":" 🖥️","label":"Hey founders! Your business doesn't need you to operate"},{"url":"https://n8n.io/blog/5-workflow-automations-for-mattermost-that-we-love-at-n8n/","icon":"🤖","label":"5 workflow automation for Mattermost that we love at n8n"},{"url":"https://n8n.io/blog/tracking-time-spent-in-meetings-with-google-calendar-twilio-and-n8n/","icon":"🗓","label":"Tracking Time Spent in Meetings With Google Calendar, Twilio, and n8n"}],"primaryDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.googlecalendar/"}],"credentialDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/credentials/google/oauth-single-service/"}]},"categories":["Productivity"],"nodeVersion":"1.0","codexVersion":"1.0"}},"group":"[\"input\"]","defaults":{"name":"Google Calendar"},"iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiBmaWxsPSIjZmZmIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiB2aWV3Qm94PSIwIDAgODEgODIiPjx1c2UgeGxpbms6aHJlZj0iI2EiIHg9Ii41IiB5PSIuNSIvPjxzeW1ib2wgaWQ9ImEiIG92ZXJmbG93PSJ2aXNpYmxlIj48ZyBmaWxsLXJ1bGU9Im5vbnplcm8iIHN0cm9rZT0ibm9uZSI+PHBhdGggZD0iTTYxLjA1MiAxOC45NDdIMTguOTQ3djQyLjEwNWg0Mi4xMDV6Ii8+PHBhdGggZmlsbD0iI2VhNDMzNSIgZD0iTTYxLjA1MyA4MCA4MCA2MS4wNTNINjEuMDUzeiIvPjxwYXRoIGZpbGw9IiNmYmJjMDQiIGQ9Ik04MCAxOC45NDdINjEuMDUzdjQyLjEwNUg4MHoiLz48cGF0aCBmaWxsPSIjMzRhODUzIiBkPSJNNjEuMDUyIDYxLjA1M0gxOC45NDdWODBoNDIuMTA1eiIvPjxwYXRoIGZpbGw9IiMxODgwMzgiIGQ9Ik0wIDYxLjA1M3YxMi42MzJBNi4zMTQgNi4zMTQgMCAwIDAgNi4zMTYgODBoMTIuNjMyVjYxLjA1M3oiLz48cGF0aCBmaWxsPSIjMTk2N2QyIiBkPSJNODAgMTguOTQ3VjYuMzE2QTYuMzE0IDYuMzE0IDAgMCAwIDczLjY4NSAwSDYxLjA1M3YxOC45NDd6Ii8+PHBhdGggZmlsbD0iIzQyODVmNCIgZD0iTTYxLjA1MyAwSDYuMzE2QTYuMzE0IDYuMzE0IDAgMCAwIDAgNi4zMTZ2NTQuNzM3aDE4Ljk0N1YxOC45NDdoNDIuMTA1VjB6TTI3LjU4NCA1MS42MTFjLTEuNTc0LTEuMDYzLTIuNjYzLTIuNjE2LTMuMjU4LTQuNjY4bDMuNjUzLTEuNTA1cS40OTggMS44OTQgMS43MzcgMi45MzdjMS4yMzkgMS4wNDMgMS44MjEgMS4wMzcgMi45ODkgMS4wMzdxMS43OTIgMCAzLjA3OS0xLjA4OWMxLjI4Ny0xLjA4OSAxLjI5LTEuNjUzIDEuMjktMi43NzRhMy40NCAzLjQ0IDAgMCAwLTEuMzU4LTIuODExYy0uOTA1LS43MjctMi4wNDItMS4wODktMy40LTEuMDg5aC0yLjExMXYtMy42MTZIMzIuMXExLjc1MiAwIDIuOTUzLS45NDdjMS4yMDEtLjk0NyAxLjItMS40OTUgMS4yLTIuNTk1cTAtMS40NjctMS4wNzQtMi4zNDJjLTEuMDc0LS44NzUtMS42MjEtLjg3OS0yLjcyMS0uODc5cS0xLjYxLS4wMDItMi41NTguODU4Yy0uOTQ4Ljg2LTEuMTA2IDEuMzAxLTEuMzc5IDIuMTExbC0zLjYxNi0xLjUwNWMuNDc5LTEuMzU4IDEuMzU4LTIuNTU4IDIuNjQ3LTMuNTk1czIuOTM3LTEuNTU4IDQuOTM3LTEuNTU4cTIuMjItLjAwMiAzLjk4OS44NThjMS43NjkuODYgMi4xMDUgMS4zNjggMi43NzQgMi4zNzlzMSAyLjE1MyAxIDMuNDE2cTAgMS45MzItLjkzMiAzLjI3NGMtLjkzMiAxLjM0Mi0xLjM4NCAxLjU3OS0yLjI4OSAyLjA1OHYuMjE2YTYuOTUgNi45NSAwIDAgMSAyLjkzNyAyLjI4OXExLjE0NiAxLjUzOCAxLjE0NyAzLjY4NGMuMDAxIDIuMTQ2LS4zNjMgMi43MTEtMS4wODkgMy44MzJzLTEuNzMyIDIuMDA1LTMuMDA1IDIuNjQ3Yy0xLjI3OS42NDItMi43MTYuOTY4LTQuMzExLjk2OC0xLjg0Ny4wMDUtMy41NTMtLjUyNi01LjEyNi0xLjU4OXptMjIuNDM3LTE4LjEyNi00LjAxIDIuOS0yLjAwNS0zLjA0MiA3LjE5NS01LjE4OWgyLjc1OHYyNC40NzloLTMuOTM3VjMzLjQ4NHoiLz48L2c+PC9zeW1ib2w+PC9zdmc+"},"displayName":"Google Calendar","typeVersion":1,"nodeCategories":[{"id":4,"name":"Productivity"}]},{"id":356,"icon":"file:gmail.svg","name":"n8n-nodes-base.gmail","codex":{"data":{"alias":["email","human","form","wait","hitl","approval"],"resources":{"generic":[{"url":"https://n8n.io/blog/why-business-process-automation-with-n8n-can-change-your-daily-life/","icon":"🧬","label":"Why business process automation with n8n can change your daily life"},{"url":"https://n8n.io/blog/supercharging-your-conference-registration-process-with-n8n/","icon":"🎫","label":"Supercharging your conference registration process with n8n"},{"url":"https://n8n.io/blog/no-code-ecommerce-workflow-automations/","icon":"store","label":"6 e-commerce workflows to power up your Shopify s"},{"url":"https://n8n.io/blog/how-to-get-started-with-crm-automation-and-no-code-workflow-ideas/","icon":"👥","label":"How to get started with CRM automation (with 3 no-code workflow ideas"},{"url":"https://n8n.io/blog/automate-google-apps-for-productivity/","icon":"💡","label":"15 Google apps you can combine and automate to increase productivity"},{"url":"https://n8n.io/blog/your-business-doesnt-need-you-to-operate/","icon":" 🖥️","label":"Hey founders! Your business doesn't need you to operate"},{"url":"https://n8n.io/blog/using-automation-to-boost-productivity-in-the-workplace/","icon":"💪","label":"Using Automation to Boost Productivity in the Workplace"}],"primaryDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.gmail/"}],"credentialDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/credentials/google/oauth-single-service/"}]},"categories":["Communication","HITL"],"nodeVersion":"1.0","codexVersion":"1.0","subcategories":{"HITL":["Human in the Loop"]}}},"group":"[\"transform\"]","defaults":{"name":"Gmail"},"iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNTYiIGhlaWdodD0iMTkzIiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWlkWU1pZCI+PHBhdGggZmlsbD0iIzQyODVGNCIgZD0iTTU4LjE4MiAxOTIuMDVWOTMuMTRMMjcuNTA3IDY1LjA3NyAwIDQ5LjUwNHYxMjUuMDkxYzAgOS42NTggNy44MjUgMTcuNDU1IDE3LjQ1NSAxNy40NTV6Ii8+PHBhdGggZmlsbD0iIzM0QTg1MyIgZD0iTTE5Ny44MTggMTkyLjA1aDQwLjcyN2M5LjY1OSAwIDE3LjQ1NS03LjgyNiAxNy40NTUtMTcuNDU1VjQ5LjUwNWwtMzEuMTU2IDE3LjgzNy0yNy4wMjYgMjUuNzk4eiIvPjxwYXRoIGZpbGw9IiNFQTQzMzUiIGQ9Im01OC4xODIgOTMuMTQtNC4xNzQtMzguNjQ3IDQuMTc0LTM2Ljk4OUwxMjggNjkuODY4bDY5LjgxOC01Mi4zNjQgNC42NyAzNC45OTItNC42NyA0MC42NDRMMTI4IDE0NS41MDR6Ii8+PHBhdGggZmlsbD0iI0ZCQkMwNCIgZD0iTTE5Ny44MTggMTcuNTA0VjkzLjE0TDI1NiA0OS41MDRWMjYuMjMxYzAtMjEuNTg1LTI0LjY0LTMzLjg5LTQxLjg5LTIwLjk0NXoiLz48cGF0aCBmaWxsPSIjQzUyMjFGIiBkPSJtMCA0OS41MDQgMjYuNzU5IDIwLjA3TDU4LjE4MiA5My4xNFYxNy41MDRMNDEuODkgNS4yODZDMjQuNjEtNy42NiAwIDQuNjQ2IDAgMjYuMjN6Ii8+PC9zdmc+"},"displayName":"Gmail","typeVersion":2,"nodeCategories":[{"id":6,"name":"Communication"},{"id":28,"name":"HITL"}]},{"id":535,"icon":"file:webhook.svg","name":"n8n-nodes-base.respondToWebhook","codex":{"data":{"resources":{"primaryDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.respondtowebhook/"}]},"categories":["Core Nodes","Utility"],"nodeVersion":"1.0","codexVersion":"1.0","subcategories":{"Core Nodes":["Helpers"]}}},"group":"[\"transform\"]","defaults":{"name":"Respond to Webhook"},"iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCI+PHBhdGggZmlsbD0iIzM3NDc0ZiIgZD0iTTM1IDM3Yy0yLjIgMC00LTEuOC00LTRzMS44LTQgNC00IDQgMS44IDQgNC0xLjggNC00IDQiLz48cGF0aCBmaWxsPSIjMzc0NzRmIiBkPSJNMzUgNDNjLTMgMC01LjktMS40LTcuOC0zLjdsMy4xLTIuNWMxLjEgMS40IDIuOSAyLjMgNC43IDIuMyAzLjMgMCA2LTIuNyA2LTZzLTIuNy02LTYtNmMtMSAwLTIgLjMtMi45LjdsLTEuNyAxTDIzLjMgMTZsMy41LTEuOSA1LjMgOS40YzEtLjMgMi0uNSAzLS41IDUuNSAwIDEwIDQuNSAxMCAxMFM0MC41IDQzIDM1IDQzIi8+PHBhdGggZmlsbD0iIzM3NDc0ZiIgZD0iTTE0IDQzQzguNSA0MyA0IDM4LjUgNCAzM2MwLTQuNiAzLjEtOC41IDcuNS05LjdsMSAzLjlDOS45IDI3LjkgOCAzMC4zIDggMzNjMCAzLjMgMi43IDYgNiA2czYtMi43IDYtNnYtMmgxNXY0SDIzLjhjLS45IDQuNi01IDgtOS44IDgiLz48cGF0aCBmaWxsPSIjZTkxZTYzIiBkPSJNMTQgMzdjLTIuMiAwLTQtMS44LTQtNHMxLjgtNCA0LTQgNCAxLjggNCA0LTEuOCA0LTQgNCIvPjxwYXRoIGZpbGw9IiMzNzQ3NGYiIGQ9Ik0yNSAxOWMtMi4yIDAtNC0xLjgtNC00czEuOC00IDQtNCA0IDEuOCA0IDQtMS44IDQtNCA0Ii8+PHBhdGggZmlsbD0iI2U5MWU2MyIgZD0ibTE1LjcgMzQtMy40LTIgNS45LTkuN2MtMi0xLjktMy4yLTQuNS0zLjItNy4zIDAtNS41IDQuNS0xMCAxMC0xMHMxMCA0LjUgMTAgMTBjMCAuOS0uMSAxLjctLjMgMi41bC0zLjktMWMuMS0uNS4yLTEgLjItMS41IDAtMy4zLTIuNy02LTYtNnMtNiAyLjctNiA2YzAgMi4xIDEuMSA0IDIuOSA1LjFsMS43IDF6Ii8+PC9zdmc+"},"displayName":"Respond to Webhook","typeVersion":2,"nodeCategories":[{"id":7,"name":"Utility"},{"id":9,"name":"Core Nodes"}]},{"id":565,"icon":"fa:sticky-note","name":"n8n-nodes-base.stickyNote","codex":{"data":{"alias":["Comments","Notes","Sticky"],"categories":["Core Nodes"],"nodeVersion":"1.0","codexVersion":"1.0","subcategories":{"Core Nodes":["Helpers"]}}},"group":"[\"input\"]","defaults":{"name":"Sticky Note","color":"#FFD233"},"iconData":{"icon":"sticky-note","type":"icon"},"displayName":"Sticky Note","typeVersion":1,"nodeCategories":[{"id":9,"name":"Core Nodes"}]},{"id":834,"icon":"file:code.svg","name":"n8n-nodes-base.code","codex":{"data":{"alias":["cpde","Javascript","JS","Python","Script","Custom Code","Function"],"details":"The Code node allows you to execute JavaScript in your workflow.","resources":{"primaryDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.code/"}]},"categories":["Development","Core Nodes"],"nodeVersion":"1.0","codexVersion":"1.0","subcategories":{"Core Nodes":["Helpers","Data Transformation"]}}},"group":"[\"transform\"]","defaults":{"name":"Code"},"iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTEyIiBoZWlnaHQ9IjUxMiIgdmlld0JveD0iMCAwIDUxMiA1MTIiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMF8xMTcxXzQ0MSkiPgo8cGF0aCBkPSJNMTcwLjI4MyA0OEgxOTYuNUMyMDMuMTI3IDQ4IDIwOC41IDQyLjYyNzQgMjA4LjUgMzZWMTJDMjA4LjUgNS4zNzI1OCAyMDMuMTI3IDAgMTk2LjUgMEgxNzAuMjgzQzEyNi4xIDAgOTAuMjgzIDM1LjgxNzIgOTAuMjgzIDgwVjE3NkM5MC4yODMgMjA2LjkyOCA2NS4yMTA5IDIzMiAzNC4yODMgMjMySDIzQzE2LjM3MjYgMjMyIDExIDIzNy4zNzIgMTEgMjQ0VjI2OEMxMSAyNzQuNjI3IDE2LjM3MjQgMjgwIDIyLjk5OTYgMjgwTDM0LjI4MyAyODBDNjUuMjEwOSAyODAgOTAuMjgzIDMwNS4wNzIgOTAuMjgzIDMzNlY0NDBDOTAuMjgzIDQ3OS43NjQgMTIyLjUxOCA1MTIgMTYyLjI4MyA1MTJIMTk2LjVDMjAzLjEyNyA1MTIgMjA4LjUgNTA2LjYyNyAyMDguNSA1MDBWNDc2QzIwOC41IDQ2OS4zNzMgMjAzLjEyNyA0NjQgMTk2LjUgNDY0SDE2Mi4yODNDMTQ5LjAyOCA0NjQgMTM4LjI4MyA0NTMuMjU1IDEzOC4yODMgNDQwVjMzNkMxMzguMjgzIDMwOS4wMjIgMTI4LjAxMSAyODQuNDQzIDExMS4xNjQgMjY1Ljk2MUMxMDYuMTA5IDI2MC40MTYgMTA2LjEwOSAyNTEuNTg0IDExMS4xNjQgMjQ2LjAzOUMxMjguMDExIDIyNy41NTcgMTM4LjI4MyAyMDIuOTc4IDEzOC4yODMgMTc2VjgwQzEzOC4yODMgNjIuMzI2OSAxNTIuNjEgNDggMTcwLjI4MyA0OFoiIGZpbGw9IiNGRjk5MjIiLz4KPHBhdGggZD0iTTMwNSAzNkMzMDUgNDIuNjI3NCAzMTAuMzczIDQ4IDMxNyA0OEgzNDIuOTc5QzM2MC42NTIgNDggMzc0Ljk3OCA2Mi4zMjY5IDM3NC45NzggODBWMTc2QzM3NC45NzggMjAyLjk3OCAzODUuMjUxIDIyNy41NTcgNDAyLjA5OCAyNDYuMDM5QzQwNy4xNTMgMjUxLjU4NCA0MDcuMTUzIDI2MC40MTYgNDAyLjA5OCAyNjUuOTYxQzM4NS4yNTEgMjg0LjQ0MyAzNzQuOTc4IDMwOS4wMjIgMzc0Ljk3OCAzMzZWNDMyQzM3NC45NzggNDQ5LjY3MyAzNjAuNjUyIDQ2NCAzNDIuOTc5IDQ2NEgzMTdDMzEwLjM3MyA0NjQgMzA1IDQ2OS4zNzMgMzA1IDQ3NlY1MDBDMzA1IDUwNi42MjcgMzEwLjM3MyA1MTIgMzE3IDUxMkgzNDIuOTc5QzM4Ny4xNjEgNTEyIDQyMi45NzggNDc2LjE4MyA0MjIuOTc4IDQzMlYzMzZDNDIyLjk3OCAzMDUuMDcyIDQ0OC4wNTEgMjgwIDQ3OC45NzkgMjgwSDQ5MEM0OTYuNjI3IDI4MCA1MDIgMjc0LjYyOCA1MDIgMjY4VjI0NEM1MDIgMjM3LjM3MyA0OTYuNjI4IDIzMiA0OTAgMjMyTDQ3OC45NzkgMjMyQzQ0OC4wNTEgMjMyIDQyMi45NzggMjA2LjkyOCA0MjIuOTc4IDE3NlY4MEM0MjIuOTc4IDM1LjgxNzIgMzg3LjE2MSAwIDM0Mi45NzkgMEgzMTdDMzEwLjM3MyAwIDMwNSA1LjM3MjU4IDMwNSAxMlYzNloiIGZpbGw9IiNGRjk5MjIiLz4KPC9nPgo8ZGVmcz4KPGNsaXBQYXRoIGlkPSJjbGlwMF8xMTcxXzQ0MSI+CjxyZWN0IHdpZHRoPSI1MTIiIGhlaWdodD0iNTEyIiBmaWxsPSJ3aGl0ZSIvPgo8L2NsaXBQYXRoPgo8L2RlZnM+Cjwvc3ZnPgo="},"displayName":"Code","typeVersion":2,"nodeCategories":[{"id":5,"name":"Development"},{"id":9,"name":"Core Nodes"}]}],"categories":[{"id":40,"name":"Support Chatbot"}],"image":[]}}