AutomationFlowsWeb Scraping › Venuedesk - Create Recurring Booking (fixed)

Venuedesk - Create Recurring Booking (fixed)

VenueDesk - Create Recurring Booking (Fixed). Uses httpRequest. Webhook trigger; 19 nodes.

Webhook trigger★★★★☆ complexity19 nodesHTTP Request
Web Scraping Trigger: Webhook Nodes: 19 Complexity: ★★★★☆ Added:

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "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": []
}
Pro

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 →

More Web Scraping workflows → · Browse all categories →

Related workflows

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

Web Scraping

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

n8n, Execute Workflow Trigger, HTTP Request +1
Web Scraping

This flow creates dummy files for every item added in your *Arrs (Radarr/Sonarr) with the tag .

HTTP Request, Ssh
Web Scraping

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

HTTP Request
Web Scraping

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

HTTP Request
Web Scraping

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.

Execute Command, HTTP Request, Read Write File +1