The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"name": "VenueDesk - Create Recurring Booking (Fixed)",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "create-recurring-booking",
"responseMode": "responseNode",
"options": {}
},
"id": "6fad2659-1add-4943-937b-57fa89996816",
"name": "Webhook: Create Recurring Booking",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
-600,
-400
],
"credentials": {}
},
{
"parameters": {
"jsCode": "// 1. Get raw input\nconst items = $input.all();\nlet rawData = items[0]?.json || {};\nlet finalResult = {};\n\ntry {\n if (rawData.hasOwnProperty('0')) {\n const combinedString = Object.values(rawData).join('');\n finalResult = JSON.parse(combinedString);\n } else if (rawData.body && typeof rawData.body === 'object') {\n finalResult = rawData.body;\n } else if (typeof rawData.body === 'string') {\n finalResult = JSON.parse(rawData.body);\n } else {\n finalResult = rawData;\n }\n} catch (e) {\n finalResult = rawData;\n finalResult._error = \"Parsing failed: \" + e.message;\n}\n\nconst output = {\n ...finalResult,\n tenant_id: parseInt(finalResult.tenant_id || 1001),\n day_of_week: parseInt(finalResult.day_of_week ?? -1),\n _debug_parsed: true\n};\n\nreturn [{ json: output }];"
},
"id": "ef099f00-1cdc-4adf-9ab9-0d4ca64b3765",
"name": "Debug: Check Input Data",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-380,
-400
]
},
{
"parameters": {
"method": "POST",
"url": "https://api.venuedesk.co.uk/recurring/upsert-customer",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "={{ 'Bearer ' + ($('Debug: Check Input Data').first().json?.jwt || '') }}"
}
]
},
"sendBody": true,
"contentType": "json",
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ customer_name: ($('Debug: Check Input Data').first().json?.customer_name || '').trim(), customer_email: ($('Debug: Check Input Data').first().json?.customer_email || '').trim().toLowerCase(), customer_phone: ($('Debug: Check Input Data').first().json?.customer_phone || '').trim() }) }}",
"options": {}
},
"id": "crb-api-upsert-cust-001",
"name": "API: Upsert Customer",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
-160,
-400
]
},
{
"parameters": {
"jsCode": "// Flatten adapter \u2014 preserves $('DB: Upsert Customer') named references in Code: Validate\nconst resp = $input.first().json;\nreturn [{ json: resp.data || resp }];"
},
"id": "crb-flatten-upsert-cust-001",
"name": "DB: Upsert Customer",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
60,
-400
]
},
{
"parameters": {
"jsCode": "const inputData = $('Debug: Check Input Data').first().json;\nconst wb = inputData || {};\n\nconsole.log('Received for Date Gen:', wb);\n\nconst specificDates = (wb.specific_dates || '').trim();\nconst frequency = wb.frequency || 'weekly';\nconst startDateStr = wb.start_date || '';\nconst endDateStr = wb.end_date || '';\nconst dayOfWeek = parseInt(wb.day_of_week ?? '-1');\nconst ratePerSession = parseFloat(wb.rate_per_session || '0') || 0;\nconst monthlyRate = parseFloat(wb.monthly_rate || wb.monthly_fee || '0') || 0;\nconst billingFreq = (wb.billing_frequency || 'monthly').toLowerCase();\nconst paymentTiming = wb.payment_timing || 'in_advance';\nconst sessFreq = (wb.frequency || wb.session_frequency || 'weekly').toLowerCase();\nconst sessionsPerMonth = { weekly: 4, fortnightly: 2, monthly: 1 }[sessFreq] || 4;\nconst effectiveMonthlyRate = monthlyRate > 0\n ? monthlyRate\n : parseFloat((ratePerSession * sessionsPerMonth).toFixed(2));\nconst periodAmount = parseFloat(wb.period_amount || '0') ||\n (billingFreq === 'fortnightly' ? effectiveMonthlyRate / 2 : effectiveMonthlyRate) ||\n ratePerSession;\n\nif (!startDateStr && !specificDates) {\n return [{ json: { error: \"Missing start_date or specific_dates\", date_count: 0, dates: \"\", payment_timing: paymentTiming } }];\n}\n\nlet dates = [];\n\nif (specificDates) {\n dates = specificDates.split(',').map(d => d.trim()).filter(d => /^\\d{4}-\\d{2}-\\d{2}$/.test(d)).sort();\n} else {\n const startDate = new Date(startDateStr + 'T12:00:00');\n const maxDate = endDateStr ? new Date(endDateStr + 'T12:00:00') : new Date(startDate.getFullYear(), startDate.getMonth() + 3, startDate.getDate());\n const dow = dayOfWeek >= 0 ? dayOfWeek : startDate.getDay();\n const daysToAdd = (dow - startDate.getDay() + 7) % 7;\n const cursor = new Date(startDate.getTime() + daysToAdd * 86400000);\n while (cursor <= maxDate && dates.length < 100) {\n dates.push(cursor.getFullYear()+'-'+String(cursor.getMonth()+1).padStart(2,'0')+'-'+String(cursor.getDate()).padStart(2,'0'));\n if (frequency === 'monthly') { cursor.setMonth(cursor.getMonth() + 1); }\n else if (frequency === 'fortnightly') { cursor.setDate(cursor.getDate() + 14); }\n else { cursor.setDate(cursor.getDate() + 7); }\n }\n}\n\nlet monthlyPeriods, monthlyEnds, monthlyAmounts, monthlySessions,\n firstPeriodStart, firstPeriodEnd, firstMonthSessions, firstMonthAmount;\n\nif (paymentTiming === 'per_session') {\n monthlyPeriods = dates.slice();\n monthlyEnds = dates.slice();\n monthlyAmounts = dates.map(() => ratePerSession.toFixed(2));\n monthlySessions = dates.map(() => 1);\n firstPeriodStart = dates[0] || '';\n firstPeriodEnd = dates[0] || '';\n firstMonthSessions = dates.length > 0 ? 1 : 0;\n firstMonthAmount = ratePerSession.toFixed(2);\n\n} else if (billingFreq === 'fortnightly') {\n const anchor = new Date(dates[0] + 'T12:00:00');\n const periodMap = {};\n for (const d of dates) {\n const dt = new Date(d + 'T12:00:00');\n const diffDays = Math.round((dt - anchor) / 86400000);\n const winIdx = Math.floor(diffDays / 14);\n const winStart = new Date(anchor.getTime() + winIdx * 14 * 86400000);\n const winEnd = new Date(winStart.getTime() + 13 * 86400000);\n const ks = winStart.getFullYear()+'-'+String(winStart.getMonth()+1).padStart(2,'0')+'-'+String(winStart.getDate()).padStart(2,'0');\n const ke = winEnd.getFullYear() +'-'+String(winEnd.getMonth()+1 ).padStart(2,'0')+'-'+String(winEnd.getDate() ).padStart(2,'0');\n if (!periodMap[ks]) periodMap[ks] = { count: 0, period_start: ks, period_end: ke };\n periodMap[ks].count++;\n }\n const sortedKeys = Object.keys(periodMap).sort();\n monthlyPeriods = sortedKeys.map(k => periodMap[k].period_start);\n monthlyEnds = sortedKeys.map(k => periodMap[k].period_end);\n monthlyAmounts = sortedKeys.map(() => periodAmount.toFixed(2));\n monthlySessions = sortedKeys.map(k => periodMap[k].count);\n const fk = sortedKeys[0];\n const fp = fk ? periodMap[fk] : null;\n firstPeriodStart = fp ? fp.period_start : '';\n firstPeriodEnd = fp ? fp.period_end : '';\n firstMonthSessions = fp ? fp.count : 0;\n firstMonthAmount = periodAmount.toFixed(2);\n\n} else {\n const monthMap = {};\n for (const d of dates) {\n const dt = new Date(d + 'T12:00:00');\n const y = dt.getFullYear();\n const m = dt.getMonth();\n const key = y + '-' + String(m + 1).padStart(2, '0');\n if (!monthMap[key]) {\n const mEnd = new Date(y, m + 1, 0);\n monthMap[key] = {\n count: 0,\n period_start: key + '-01',\n period_end: key + '-' + String(mEnd.getDate()).padStart(2, '0')\n };\n }\n monthMap[key].count++;\n }\n const sortedKeys = Object.keys(monthMap).sort();\n monthlyPeriods = sortedKeys.map(k => monthMap[k].period_start);\n monthlyEnds = sortedKeys.map(k => monthMap[k].period_end);\n monthlyAmounts = sortedKeys.map(() => periodAmount.toFixed(2));\n monthlySessions = sortedKeys.map(k => monthMap[k].count);\n const fk = sortedKeys[0];\n const fm = fk ? monthMap[fk] : null;\n firstPeriodStart = fm ? fm.period_start : '';\n firstPeriodEnd = fm ? fm.period_end : '';\n firstMonthSessions = fm ? fm.count : 0;\n firstMonthAmount = periodAmount.toFixed(2);\n}\n\nreturn [{ json: {\n dates: dates.join(','),\n date_count: dates.length,\n payment_timing: paymentTiming,\n billing_frequency: billingFreq,\n monthly_rate: monthlyRate.toFixed(2),\n period_amount: periodAmount.toFixed(2),\n monthly_periods_csv: monthlyPeriods.join(','),\n monthly_ends_csv: monthlyEnds.join(','),\n monthly_amounts_csv: monthlyAmounts.join(','),\n monthly_sessions_csv: monthlySessions.join(','),\n monthly_count: monthlyPeriods.length,\n period_start: firstPeriodStart,\n period_end: firstPeriodEnd,\n first_month_sessions: firstMonthSessions,\n first_month_amount: firstMonthAmount\n} }];"
},
"id": "4940ab6f-d0dc-4f94-857a-82b45d7b1f45",
"name": "Code: Generate Dates",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
280,
-400
]
},
{
"parameters": {
"jsCode": "const wb = $('Debug: Check Input Data').first().json;\nconst customerRow = $('DB: Upsert Customer').first().json;\nconst dateGen = $('Code: Generate Dates').first().json;\n\nconst customer_id = customerRow?.customer_id;\nconst room_id = wb?.room_id;\nconst tenant_id = wb?.tenant_id;\n\nconsole.log(\"Validation Check:\", { customer_id, room_id, tenant_id, date_count: dateGen.date_count });\n\nif (!customer_id) {\n throw new Error(\"Validation Error: The database did not return a customer_id. Check 'DB: Upsert Customer' output.\");\n}\n\nif (!room_id || !tenant_id) {\n throw new Error(`Validation Error: Missing Webhook data. Room: ${room_id}, Tenant: ${tenant_id}`);\n}\n\nif (!dateGen.dates || dateGen.date_count === 0) {\n throw new Error(\"Validation Error: No dates were generated for this booking.\");\n}\n\nreturn [{\n json: {\n customer_id,\n room_id,\n tenant_id,\n dates: dateGen.dates,\n date_count: dateGen.date_count,\n first_month_amount: dateGen.first_month_amount,\n customer_name: customerRow.full_name || wb.customer_name,\n customer_email: customerRow.email || wb.customer_email\n }\n}];"
},
"id": "d000a19a-fdf3-4743-aec0-899ac9a9815b",
"name": "Code: Validate",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
500,
-400
]
},
{
"parameters": {
"method": "POST",
"url": "https://api.venuedesk.co.uk/recurring/check-clashes",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "={{ 'Bearer ' + ($('Debug: Check Input Data').first().json?.jwt || '') }}"
}
]
},
"sendBody": true,
"contentType": "json",
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ dates: $('Code: Validate').first().json.dates || '', room_id: $('Code: Validate').first().json.room_id, start_time: $('Debug: Check Input Data').first().json?.start_time || '09:00', end_time: $('Debug: Check Input Data').first().json?.end_time || '23:59' }) }}",
"options": {
"continueOnFail": true
}
},
"id": "crb-api-clash-001",
"name": "API: Check Clashes",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
720,
-400
]
},
{
"parameters": {
"jsCode": "// Flatten adapter \u2014 Code: Filter Dates reads $input.first().json.clashed_dates\nconst resp = $input.first().json;\nconst data = resp.data || resp;\nreturn [{ json: { clashed_dates: data.clashed_dates || [] } }];"
},
"id": "crb-flatten-clash-001",
"name": "DB: Check Room Clashes",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
940,
-400
]
},
{
"parameters": {
"jsCode": "const validate = $('Code: Validate').first().json;\nconst allDates = (validate.dates || '').split(',').filter(Boolean).sort();\nconst clashRow = $input.first().json;\nconst clashed = new Set((clashRow.clashed_dates || []).map(d => String(d).trim().slice(0,10)));\n\nif (clashed.size === 0) {\n return [{ json: {\n filtered_dates: validate.dates || '',\n dates_count: allDates.length,\n has_conflict: false,\n blocked: false,\n warning: null\n }}];\n}\n\nconst sortedClashed = [...clashed].sort();\nconst firstConflict = sortedClashed[0];\nconst safeDates = allDates.filter(d => d < firstConflict);\n\nconst fmtDate = d => {\n const dt = new Date(d + 'T12:00:00');\n return dt.toLocaleDateString('en-GB', {day:'numeric', month:'long', year:'numeric'});\n};\n\nif (safeDates.length === 0) {\n return [{ json: {\n filtered_dates: '',\n dates_count: 0,\n has_conflict: true,\n blocked: true,\n first_conflict_date: firstConflict,\n warning: 'The first session date (' + fmtDate(firstConflict) + ') is already booked for this room at this time. Please choose a different start date or room.'\n }}];\n}\n\nreturn [{ json: {\n filtered_dates: safeDates.join(','),\n dates_count: safeDates.length,\n has_conflict: true,\n blocked: false,\n first_conflict_date: firstConflict,\n clashed_count: clashed.size,\n warning: 'Recurring series created for ' + safeDates.length + ' session(s) up to ' + fmtDate(safeDates[safeDates.length-1]) + '. Bookings stop before ' + fmtDate(firstConflict) + ' \u2014 that date is already taken by an existing booking.'\n}}];"
},
"id": "crb-filter-001",
"name": "Code: Filter Dates",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1160,
-400
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict"
},
"conditions": [
{
"id": "avail-cond-rb-001",
"leftValue": "={{ $('Code: Filter Dates').first().json.blocked }}",
"rightValue": true,
"operator": {
"type": "boolean",
"operation": "notEquals"
}
}
],
"combinator": "and"
}
},
"id": "crb-if-avail-001",
"name": "IF: Room Available?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
1380,
-400
]
},
{
"parameters": {
"method": "POST",
"url": "https://api.venuedesk.co.uk/recurring/create-rule",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "={{ 'Bearer ' + ($('Debug: Check Input Data').first().json?.jwt || '') }}"
}
]
},
"sendBody": true,
"contentType": "json",
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ customer_id: $('Code: Validate').first().json.customer_id, room_id: $('Code: Validate').first().json.room_id, day_of_week: parseInt($('Debug: Check Input Data').first().json?.day_of_week ?? 1), start_time: $('Debug: Check Input Data').first().json?.start_time || '', end_time: $('Debug: Check Input Data').first().json?.end_time || '', rate_per_session: parseFloat($('Debug: Check Input Data').first().json?.rate_per_session || 0) || 0, frequency: $('Debug: Check Input Data').first().json?.frequency || 'weekly', end_date: $('Debug: Check Input Data').first().json?.end_date || '', billing_day: parseInt($('Debug: Check Input Data').first().json?.billing_day || 0) || null, total_months: parseInt($('Debug: Check Input Data').first().json?.total_months || 0) || null, upfront_paid: $('Debug: Check Input Data').first().json?.upfront_paid === true || $('Debug: Check Input Data').first().json?.upfront_paid === 'true', monthly_fee: parseFloat($('Debug: Check Input Data').first().json?.monthly_rate || $('Debug: Check Input Data').first().json?.monthly_fee || 0) || 0, payment_timing: $('Debug: Check Input Data').first().json?.payment_timing || 'in_advance', billing_frequency: $('Debug: Check Input Data').first().json?.billing_frequency || 'monthly' }) }}",
"options": {}
},
"id": "crb-api-insert-rule-001",
"name": "API: Insert Rule",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1600,
-400
]
},
{
"parameters": {
"jsCode": "// Flatten adapter \u2014 preserves $('DB: Insert Rule') references in API: Insert Series body and Respond\nconst resp = $input.first().json;\nreturn [{ json: resp.data || resp }];"
},
"id": "crb-flatten-rule-001",
"name": "DB: Insert Rule",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1820,
-400
]
},
{
"parameters": {
"method": "POST",
"url": "https://api.venuedesk.co.uk/recurring/create-series",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "={{ 'Bearer ' + ($('Debug: Check Input Data').first().json?.jwt || '') }}"
}
]
},
"sendBody": true,
"contentType": "json",
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ customer_id: $('Code: Validate').first().json.customer_id, room_id: $('Code: Validate').first().json.room_id, series_name: ($('Debug: Check Input Data').first().json?.series_name || $('Debug: Check Input Data').first().json?.customer_name || 'Recurring Session').trim(), frequency: $('Debug: Check Input Data').first().json?.frequency || 'weekly', start_date: $('Debug: Check Input Data').first().json?.start_date || '', end_date: $('Debug: Check Input Data').first().json?.end_date || '', start_time: $('Debug: Check Input Data').first().json?.start_time || '', end_time: $('Debug: Check Input Data').first().json?.end_time || '', rate_per_session: parseFloat($('Debug: Check Input Data').first().json?.rate_per_session || 0) || 0, total_sessions: $('Code: Generate Dates').first().json.date_count || 0, billing_frequency: $('Debug: Check Input Data').first().json?.billing_frequency || 'monthly', payment_timing: $('Debug: Check Input Data').first().json?.payment_timing || 'in_advance' }) }}",
"options": {}
},
"id": "crb-api-insert-series-001",
"name": "API: Insert Series",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
2040,
-400
]
},
{
"parameters": {
"jsCode": "// Flatten adapter \u2014 preserves $('DB: Insert Series') references in API: Insert Bookings and Respond\nconst resp = $input.first().json;\nreturn [{ json: resp.data || resp }];"
},
"id": "crb-flatten-series-001",
"name": "DB: Insert Series",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2260,
-400
]
},
{
"parameters": {
"method": "POST",
"url": "https://api.venuedesk.co.uk/recurring/insert-bookings",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "={{ 'Bearer ' + ($('Debug: Check Input Data').first().json?.jwt || '') }}"
}
]
},
"sendBody": true,
"contentType": "json",
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ customer_id: $('Code: Validate').first().json.customer_id, room_id: $('Code: Validate').first().json.room_id, dates_csv: $('Code: Filter Dates').first().json.filtered_dates || '', start_time: $('Debug: Check Input Data').first().json?.start_time || '', end_time: $('Debug: Check Input Data').first().json?.end_time || '', rate_per_session: parseFloat($('Debug: Check Input Data').first().json?.rate_per_session || 0) || 0, rule_id: $('DB: Insert Rule').first().json.rule_id || null, series_id: $('DB: Insert Series').first().json.series_id || null, series_label: 'Recurring', status: 'confirmed' }) }}",
"options": {}
},
"id": "crb-api-insert-bookings-001",
"name": "API: Insert Bookings",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
2480,
-400
]
},
{
"parameters": {
"method": "POST",
"url": "https://api.venuedesk.co.uk/recurring/insert-payment-schedule",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "={{ 'Bearer ' + ($('Debug: Check Input Data').first().json?.jwt || '') }}"
}
]
},
"sendBody": true,
"contentType": "json",
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ rule_id: $('DB: Insert Rule').first().json.rule_id, customer_id: $('Code: Validate').first().json.customer_id, periods_csv: $('Code: Generate Dates').first().json.monthly_periods_csv || '', ends_csv: $('Code: Generate Dates').first().json.monthly_ends_csv || '', amounts_csv: $('Code: Generate Dates').first().json.monthly_amounts_csv || '0', payment_timing: $('Code: Generate Dates').first().json.payment_timing || 'in_advance', total_months: parseInt($('Debug: Check Input Data').first().json?.total_months || 0) || null, billing_day: parseInt($('Debug: Check Input Data').first().json?.billing_day || 0) || null, upfront_paid: $('Debug: Check Input Data').first().json?.upfront_paid === true || $('Debug: Check Input Data').first().json?.upfront_paid === 'true' }) }}",
"options": {}
},
"id": "crb-api-insert-schedule-001",
"name": "API: Insert Payment Schedule",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
2700,
-400
]
},
{
"parameters": {
"jsCode": "// Flatten adapter \u2014 Respond: Created reads $('DB: Insert Payment Schedule').first().json.due_date etc\nconst resp = $input.first().json;\nconst firstRow = (resp.data && resp.data[0]) || resp;\nreturn [{ json: firstRow }];"
},
"id": "crb-flatten-schedule-001",
"name": "DB: Insert Payment Schedule",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2920,
-400
]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ JSON.stringify({\n status: 'created',\n rule_id: $('DB: Insert Rule').first().json.rule_id,\n series_id: $('DB: Insert Series').first().json.series_id || null,\n series_reference: $('DB: Insert Rule').first().json.series_reference || null,\n customer_id: $('Code: Validate').first().json.customer_id,\n customer_name: $('Code: Validate').first().json.customer_name,\n booking_count: $('Code: Filter Dates').first().json.dates_count,\n agreed_price: $('DB: Insert Series').first().json.agreed_price || null,\n amount_due: $('Code: Generate Dates').first().json.first_month_amount,\n first_month_sessions: $('Code: Generate Dates').first().json.first_month_sessions || null,\n frequency: $('Debug: Check Input Data').first().json.frequency || null,\n total_cycles: $('DB: Insert Payment Schedule').first().json.total_cycles || null,\n remaining_cycles: $('DB: Insert Payment Schedule').first().json.remaining_cycles || null,\n billing_day: $('DB: Insert Payment Schedule').first().json.billing_day || null,\n due_date: $('DB: Insert Payment Schedule').first().json.due_date || null,\n partial_booking: $('Code: Filter Dates').first().json.has_conflict || false,\n warning: $('Code: Filter Dates').first().json.warning || null,\n first_conflict_date: $('Code: Filter Dates').first().json.first_conflict_date || null\n}) }}",
"options": {
"responseCode": 201,
"responseHeaders": {
"entries": [
{
"name": "Access-Control-Allow-Origin",
"value": "*"
}
]
}
}
},
"id": "1b94f119-f719-4fba-bbc2-3dc5c14171a0",
"name": "Respond: Created",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
3140,
-400
]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ JSON.stringify({ success: false, status: 'unavailable', message: $('Code: Filter Dates').first().json.warning || 'Room not available for the requested dates.' }) }}",
"options": {
"responseCode": 409
}
},
"id": "crb-respond-blocked-001",
"name": "Respond: Not Available",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
1600,
-180
]
}
],
"connections": {
"Webhook: Create Recurring Booking": {
"main": [
[
{
"node": "Debug: Check Input Data",
"type": "main",
"index": 0
}
]
]
},
"Debug: Check Input Data": {
"main": [
[
{
"node": "API: Upsert Customer",
"type": "main",
"index": 0
}
]
]
},
"API: Upsert Customer": {
"main": [
[
{
"node": "DB: Upsert Customer",
"type": "main",
"index": 0
}
]
]
},
"DB: Upsert Customer": {
"main": [
[
{
"node": "Code: Generate Dates",
"type": "main",
"index": 0
}
]
]
},
"Code: Generate Dates": {
"main": [
[
{
"node": "Code: Validate",
"type": "main",
"index": 0
}
]
]
},
"Code: Validate": {
"main": [
[
{
"node": "API: Check Clashes",
"type": "main",
"index": 0
}
]
]
},
"API: Check Clashes": {
"main": [
[
{
"node": "DB: Check Room Clashes",
"type": "main",
"index": 0
}
]
]
},
"DB: Check Room Clashes": {
"main": [
[
{
"node": "Code: Filter Dates",
"type": "main",
"index": 0
}
]
]
},
"Code: Filter Dates": {
"main": [
[
{
"node": "IF: Room Available?",
"type": "main",
"index": 0
}
]
]
},
"IF: Room Available?": {
"main": [
[
{
"node": "API: Insert Rule",
"type": "main",
"index": 0
}
],
[
{
"node": "Respond: Not Available",
"type": "main",
"index": 0
}
]
]
},
"API: Insert Rule": {
"main": [
[
{
"node": "DB: Insert Rule",
"type": "main",
"index": 0
}
]
]
},
"DB: Insert Rule": {
"main": [
[
{
"node": "API: Insert Series",
"type": "main",
"index": 0
}
]
]
},
"API: Insert Series": {
"main": [
[
{
"node": "DB: Insert Series",
"type": "main",
"index": 0
}
]
]
},
"DB: Insert Series": {
"main": [
[
{
"node": "API: Insert Bookings",
"type": "main",
"index": 0
}
]
]
},
"API: Insert Bookings": {
"main": [
[
{
"node": "API: Insert Payment Schedule",
"type": "main",
"index": 0
}
]
]
},
"API: Insert Payment Schedule": {
"main": [
[
{
"node": "DB: Insert Payment Schedule",
"type": "main",
"index": 0
}
]
]
},
"DB: Insert Payment Schedule": {
"main": [
[
{
"node": "Respond: Created",
"type": "main",
"index": 0
}
]
]
}
},
"active": true,
"settings": {
"executionOrder": "v1"
},
"versionId": "crb-v3-http-2026-04-27",
"id": "CreateRecurringBooking",
"tags": []
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
VenueDesk - Create Recurring Booking (Fixed). Uses httpRequest. Webhook trigger; 19 nodes.
Source: https://github.com/AndyJay72/VenueDesk/blob/main/n8n-workflows/CreateRecurringBooking.json — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
This n8n template provides enterprise-level version control for your workflows using GitHub integration. Stop losing hours to broken workflows and manual exports – get proper commit history, visual di
This flow creates dummy files for every item added in your *Arrs (Radarr/Sonarr) with the tag .
This workflow receives webhook requests from a content calendar and uses the X API v2 to publish text posts, threads, image/video posts, and polls, as well as delete existing posts and run a credentia
This workflow acts as a central API gateway for all technical indicator agents in the Binance Spot Market Quant AI system. It listens for incoming webhook requests and dynamically routes them to the c
Sign PDF documents with legally-compliant digital signatures using X.509 certificates. Supports multiple PAdES signature levels (B, T, LT, LTA) with optional visible stamps.