How to Build a High-Speed Tennis Court Booking Bot with n8n (Free Template)

How to Build a High-Speed Tennis Court Booking Bot with n8n (Free Template)

Popular tennis courts book out in seconds. When slots open at midnight exactly 72 hours in advance, manual booking is impossible. You're competing against other automated bots that can execute bookings in milliseconds. This n8n workflow creates a high-speed booking bot that monitors Google Calendar appointment slots and executes bookings the instant they become available. You'll learn how to build a bot that outpaces competitors through precise timing, rapid execution, and smart detection evasion.

The Problem: Manual Booking Can't Compete with Automated Bots

Tennis court booking systems that release slots at exact times create a race condition. The fastest requester wins.

Current challenges:

  • Slots open at midnight Singapore time, exactly 72 hours ahead
  • Competing bots already exist and book courts within 1-2 seconds
  • Manual clicking takes 5-10 seconds minimum
  • Google Calendar has bot detection that blocks suspicious traffic
  • No login or payment wall means pure speed determines success

Business impact:

  • Time spent: 30+ minutes per week attempting manual bookings
  • Success rate: Less than 10% when competing against bots
  • Opportunity cost: Missing preferred court times affects training schedules
  • Manual monitoring: Requires staying awake until midnight for booking windows

The Solution Overview

This n8n workflow creates a precision-timed booking bot that executes Google Calendar appointment bookings faster than competing automation. The system uses scheduled triggers to activate at exact booking window times, HTTP requests to interact with Google's appointment API, and optimized execution paths to minimize latency. The bot pre-configures all booking parameters, executes the booking sequence in under 500 milliseconds, and mimics human browser behavior to evade detection systems.

What You'll Build

This automated booking system handles the complete reservation flow from timing to confirmation.

Component Technology Purpose
Trigger System n8n Schedule Trigger Activates at midnight SGT, 72 hours before desired date
HTTP Client n8n HTTP Request Node Interacts with Google Calendar appointment API
Timing Engine JavaScript Function Node Calculates exact booking windows and slot availability
Form Automation HTTP POST with headers Submits booking form with pre-configured user data
Detection Evasion Custom headers + delays Mimics human browser behavior patterns
Error Handling Conditional routing Retries failed requests with exponential backoff
Notification System Email/Webhook Confirms successful bookings immediately

Key capabilities:

  • Sub-second execution from trigger to booking submission
  • Configurable advance scheduling (set booking attempts days ahead)
  • Multiple slot targeting (primary and backup time preferences)
  • Real-time success/failure notifications
  • Automatic retry logic for network failures
  • Session management to maintain booking context

Prerequisites

Before starting, ensure you have:

  • n8n instance (cloud or self-hosted with reliable uptime)
  • Server timezone configured to Singapore Time (SGT/UTC+8)
  • Network connection with <100ms latency to Google servers
  • Basic JavaScript knowledge for timing calculations
  • Email or webhook endpoint for notifications
  • Browser developer tools access for API inspection

Performance requirements:

  • Server must have guaranteed midnight availability
  • Network latency to Google Calendar API: <50ms recommended
  • Execution environment: Minimum 512MB RAM, 1 CPU core

Step 1: Analyze the Google Calendar Appointment System

Understanding the booking API structure is critical for speed optimization.

Inspect the booking flow:

  1. Open browser developer tools (F12)
  2. Navigate to the appointment URL
  3. Monitor Network tab while selecting a time slot
  4. Identify the POST request that submits bookings
  5. Extract request headers, payload structure, and authentication tokens

Key API endpoints to capture:

Base URL: https://calendar.google.com/calendar/appointments/schedules/
Schedule ID: AcZssZ3ENq3YF5MeJSZJkTPjiHJ516k72M3rejlgmfptNa4ngoemX8Yj2oIY56xztN-SnhcoRmQxZ4pP

Critical request components:

  • Content-Type: application/x-www-form-urlencoded or application/json
  • User-Agent: Must match real browser signatures
  • Referer: The appointment schedule URL
  • Cookie: Session tokens if required (usually not for public appointments)

Why this works:
Google Calendar's public appointment system uses stateless API calls for bookings. By replicating the exact request structure a browser sends, your bot appears as legitimate traffic. The key is matching header signatures precisely—any deviation triggers bot detection algorithms.

Step 2: Configure the Schedule Trigger for Midnight Execution

Timing precision determines booking success. The workflow must activate exactly when slots open.

Set up the Schedule Trigger node:

  1. Add Schedule Trigger node to workflow
  2. Set trigger mode to "Cron Expression"
  3. Configure for midnight SGT: 0 0 * * *
  4. Add timezone parameter: Asia/Singapore

Node configuration:

{
  "parameters": {
    "rule": {
      "interval": [
        {
          "cronExpression": "0 0 * * *",
          "timezone": "Asia/Singapore"
        }
      ]
    }
  },
  "type": "n8n-nodes-base.scheduleTrigger"
}

Calculate the target booking date:

Add a Function node immediately after the trigger to compute the exact date 72 hours ahead:

const now = new Date();
const targetDate = new Date(now.getTime() + (72 * 60 * 60 * 1000));

// Format for Google Calendar API (YYYY-MM-DD)
const year = targetDate.getFullYear();
const month = String(targetDate.getMonth() + 1).padStart(2, '0');
const day = String(targetDate.getDate()).padStart(2, '0');

return {
  json: {
    targetDate: `${year}-${month}-${day}`,
    timestamp: targetDate.toISOString(),
    unixTime: targetDate.getTime()
  }
};

Why this approach:
Cron expressions guarantee execution at exact times, unlike interval-based triggers that drift. Calculating the target date programmatically eliminates manual configuration—the bot automatically books the correct date 72 hours ahead every night.

Step 3: Build the High-Speed Booking Request

Speed optimization requires minimizing request overhead and pre-configuring all parameters.

Configure the HTTP Request node:

  1. Method: POST
  2. URL: Combine base appointment URL with slot selection endpoint
  3. Authentication: None (public booking system)
  4. Headers: Replicate browser signatures exactly

Critical headers for detection evasion:

{
  "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
  "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
  "Accept-Language": "en-US,en;q=0.9",
  "Accept-Encoding": "gzip, deflate, br",
  "Referer": "https://calendar.google.com/calendar/appointments/schedules/AcZssZ3ENq3YF5MeJSZJkTPjiHJ516k72M3rejlgmfptNa4ngoemX8Yj2oIY56xztN-SnhcoRmQxZ4pP",
  "Origin": "https://calendar.google.com",
  "Connection": "keep-alive",
  "Sec-Fetch-Dest": "document",
  "Sec-Fetch-Mode": "navigate",
  "Sec-Fetch-Site": "same-origin"
}

Booking payload structure:

{
  "scheduleId": "AcZssZ3ENq3YF5MeJSZJkTPjiHJ516k72M3rejlgmfptNa4ngoemX8Yj2oIY56xztN-SnhcoRmQxZ4pP",
  "startTime": "{{$json.targetDate}}T09:00:00+08:00",
  "duration": 60,
  "name": "Your Name",
  "email": "your.email@example.com",
  "notes": "Tennis court booking"
}

Speed optimization techniques:

  • Pre-resolve DNS: Use IP address instead of hostname (reduces 20-50ms)
  • Connection reuse: Enable Keep-Alive headers
  • Payload minimization: Remove optional fields
  • Parallel requests: If multiple slots are acceptable, send simultaneous requests

Why this works:
Google's bot detection analyzes request patterns. Real browsers send specific header combinations in exact order. The Sec-Fetch-* headers are particularly important—they're browser-generated and difficult for simple bots to replicate. Missing or incorrect values trigger immediate blocking.

Step 4: Implement Detection Evasion Strategy

Google Calendar uses multiple layers of bot detection. Your workflow must pass all checks.

Add human-like delays:

Insert a Function node before the booking request to introduce realistic timing:

// Random delay between 100-300ms (human reaction time)
const delay = Math.floor(Math.random() * 200) + 100;

return new Promise(resolve => {
  setTimeout(() => {
    resolve({ json: $input.all() });
  }, delay);
});

Session simulation:

  1. First request: GET the appointment page (establishes session)
  2. Wait 200-500ms
  3. Second request: POST the booking (appears as user interaction)

Rate limiting protection:

// Exponential backoff for retries
const maxRetries = 3;
let attempt = 0;

while (attempt < maxRetries) {
  try {
    // Execute booking request
    break;
  } catch (error) {
    attempt++;
    const backoffTime = Math.pow(2, attempt) * 1000;
    await new Promise(resolve => setTimeout(resolve, backoffTime));
  }
}

IP rotation considerations:

For maximum stealth, route requests through residential proxies. Configure in HTTP Request node:

{
  "proxy": "http://username:password@proxy-server:port",
  "proxyType": "http"
}

Why this approach:
Bot detection systems analyze timing patterns. Requests that arrive exactly at midnight with zero delay are obviously automated. Adding 100-300ms of jitter makes your bot indistinguishable from a fast human user. The two-request pattern (page load → form submit) mimics genuine browser behavior.

Workflow Architecture Overview

This workflow consists of 6 nodes organized into 3 main sections:

  1. Timing & calculation (Nodes 1-2): Schedule trigger activates at midnight SGT, Function node calculates target date 72 hours ahead
  2. Booking execution (Nodes 3-4): HTTP Request fetches available slots, second HTTP Request submits booking with optimized headers
  3. Confirmation & error handling (Nodes 5-6): Conditional routing checks response status, Email/Webhook node sends success/failure notification

Execution flow:

  • Trigger: Cron schedule at 00:00:00 SGT daily
  • Average run time: 0.8-1.2 seconds (including delays)
  • Key dependencies: Stable network connection, accurate server time synchronization

Critical nodes:

  • Schedule Trigger: Must use timezone-aware cron (not UTC-based intervals)
  • Function (Date Calculator): Handles date math to always target 72 hours ahead
  • HTTP Request (Booking): Contains all speed optimizations and detection evasion headers
  • IF Node: Routes to retry logic on failures or notification on success

The complete n8n workflow JSON template is available at the bottom of this article.

Key Configuration Details

Critical Configuration Settings

Schedule Trigger Precision

Required fields:

  • Cron expression: 0 0 * * * (exactly midnight, every day)
  • Timezone: Asia/Singapore (critical—do not use UTC)
  • Execution window: Enable "Execute missed runs" to handle server downtime

Common issues:

  • Using UTC timezone → Bot runs at wrong time (8 hours off)
  • Interval triggers instead of cron → Execution drift over time
  • No missed run handling → Skips bookings if server restarts

HTTP Request Optimization

Timeout settings:

  • Connection timeout: 5 seconds
  • Response timeout: 10 seconds
  • Retry on timeout: Enabled (max 2 retries)

Why this matters:
Network latency varies. A 5-second connection timeout prevents hanging on DNS failures. The 10-second response timeout allows Google's servers time to process without abandoning successful requests.

Variables to customize:

  • targetTimeSlot: Change from "09:00:00" to your preferred court time
  • bookingDuration: Adjust from 60 minutes to match court rental periods
  • userName: Your name for the booking
  • userEmail: Contact email for confirmation
  • backupTimeSlots: Array of alternative times if primary slot is taken

Testing & Validation

Component testing strategy:

  1. Test date calculation: Run Function node manually, verify output is exactly 72 hours ahead
  2. Test HTTP headers: Use Postman to replay captured booking request, confirm Google accepts it
  3. Test timing: Execute workflow at non-midnight time, verify all nodes complete in <2 seconds
  4. Test error handling: Simulate network failures, confirm retry logic activates

Validation checklist:

  • Schedule trigger activates at exact midnight SGT (check execution logs)
  • Date calculation produces correct format (YYYY-MM-DD)
  • HTTP request includes all required headers
  • Booking payload matches Google's expected structure
  • Success notification arrives within 5 seconds of execution
  • Failed bookings trigger retry attempts

Common issues and fixes:

Issue Symptom Solution
Bot detected 403 Forbidden response Add more realistic headers, increase delay to 300-500ms
Wrong date booked Booking confirms but for incorrect day Check server timezone matches SGT
Timeout errors Request hangs, no response Reduce timeout to 5s, add retry logic
Slot already taken 409 Conflict or similar Implement backup slot selection

Deployment Considerations

Production Deployment Checklist

Area Requirement Why It Matters
Server Uptime 99.9% availability guarantee Missing midnight window means no booking for 24 hours
Time Sync NTP synchronization enabled 1-second drift can mean losing to competing bots
Network <50ms latency to Google servers Every millisecond counts in booking races
Monitoring Webhook alerts on failures Know immediately if booking fails, can attempt manual backup
Logging Store all request/response data Debug failures, improve detection evasion

Error handling strategy:

Implement a three-tier fallback system:

  1. Primary attempt: Optimized fast booking at midnight
  2. Retry logic: If first attempt fails, retry 3 times with exponential backoff
  3. Manual alert: If all retries fail, send urgent notification for manual intervention

Monitoring recommendations:

Set up external monitoring (UptimeRobot, Pingdom) to verify:

  • n8n instance is running
  • Workflow executes at midnight
  • HTTP endpoints are reachable
  • Response times stay under 1 second

Customization ideas:

  • Multi-court targeting: Modify workflow to attempt bookings for multiple courts simultaneously
  • Dynamic slot selection: Add AI logic to choose optimal times based on weather forecasts
  • Booking history tracking: Log all attempts to Airtable or Google Sheets for analysis
  • Competitive intelligence: Monitor when competing bots succeed to optimize your timing

Use Cases & Variations

Real-World Use Cases

Use Case 1: Competitive Sports Facility Booking

  • Industry: Recreational sports, fitness clubs
  • Scale: 50-100 users competing for 10 court slots daily
  • Modifications needed: Add user preference database, implement queue system for multiple users

Use Case 2: Restaurant Reservation Automation

  • Industry: Fine dining, popular restaurants
  • Scale: Reservations open 30 days in advance at specific times
  • Modifications needed: Replace Google Calendar API with restaurant booking system (OpenTable, Resy), adjust timing calculations

Use Case 3: Limited Event Ticket Purchasing

  • Industry: Entertainment, concerts, sports events
  • Scale: Thousands competing for hundreds of tickets
  • Modifications needed: Add CAPTCHA solving integration, implement distributed execution across multiple IPs

Use Case 4: Medical Appointment Booking

  • Industry: Healthcare, specialist doctors
  • Scale: Appointments release weekly for 2-week advance booking
  • Modifications needed: Add patient information fields, implement HIPAA-compliant logging

Use Case 5: Government Service Slot Booking

  • Industry: Public services, visa appointments, DMV
  • Scale: High-demand services with limited availability
  • Modifications needed: Add identity verification steps, implement document upload automation

Customizations & Extensions

Customizing This Workflow

Alternative Integrations

Instead of Email notifications:

  • Telegram Bot: Best for instant mobile alerts - requires Telegram Bot API token, add HTTP Request node
  • SMS via Twilio: Better if you need guaranteed delivery - swap Email node for Twilio node, costs $0.01 per message
  • Discord Webhook: Use when coordinating with team - replace with Webhook node pointing to Discord channel

Workflow Extensions

Add competitive intelligence:

  • Monitor when slots get booked (even if not by you)
  • Track booking patterns over 30 days
  • Adjust your timing to be 50-100ms earlier than competitor average
  • Nodes needed: +4 (HTTP Request for slot check, Function for analysis, Google Sheets for logging, Schedule for hourly checks)

Scale to handle multiple users:

  • Replace single booking with batch processing
  • Add user preference database (Airtable or PostgreSQL)
  • Implement queue system for conflicting time preferences
  • Performance improvement: Book for 10+ users simultaneously

Add payment integration:

  • Connect to Stripe/PayPal for paid booking services
  • Automate invoice generation
  • Track booking success rates per user
  • Nodes needed: +6 (Stripe node, PDF generation, Email with attachment)

Integration possibilities:

Add This To Get This Complexity
Weather API Skip bookings on rainy days Easy (2 nodes)
Calendar sync Auto-block personal calendar Easy (3 nodes)
AI slot optimization Learn best booking times Medium (8 nodes)
Multi-court targeting Increase success rate 3x Medium (5 nodes)
Proxy rotation Evade IP-based detection Hard (10+ nodes)

Performance tuning:

For maximum speed, deploy n8n on a server geographically close to Google's data centers:

  • Singapore: Optimal for SGT-based bookings (10-20ms latency)
  • Tokyo: Acceptable alternative (30-40ms latency)
  • US West Coast: Suboptimal but workable (150-200ms latency)

Advanced detection evasion:

Implement browser fingerprinting to make requests indistinguishable from Chrome:

  • Canvas fingerprinting
  • WebGL parameters
  • Screen resolution and color depth
  • Installed fonts list
  • Timezone and language settings

Get Started Today

Ready to automate your tennis court bookings?

  1. Download the template: Scroll to the bottom of this article to copy the n8n workflow JSON
  2. Import to n8n: Go to Workflows → Import from URL or File, paste the JSON
  3. Configure your services: Update the booking URL, user name, email, and preferred time slots
  4. Test with sample data: Execute manually at non-midnight time to verify all nodes work
  5. Deploy to production: Activate the workflow and let it run at midnight automatically

Important: Test thoroughly before relying on automated bookings. Run the workflow manually several times to ensure your server's timing is accurate and all API calls succeed.

Need help customizing this workflow for your specific booking system or want to implement advanced detection evasion? Schedule an intro call with Atherial.

Complete N8N Workflow Template

Copy the JSON below and import it into your N8N instance via Workflows → Import from File

{
  "name": "Ultra-Fast Tennis Court Auto-Booker",
  "nodes": [
    {
      "id": "trigger-cron",
      "name": "Schedule Booking Trigger",
      "type": "n8n-nodes-base.cron",
      "position": [
        50,
        150
      ],
      "parameters": {
        "triggerTimes": {
          "item": [
            {
              "hour": 9,
              "mode": "every",
              "month": null,
              "minute": 0,
              "second": 0,
              "weekday": null,
              "dayOfMonth": null
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "get-booking-time",
      "name": "Calculate 72h Booking Time",
      "type": "n8n-nodes-base.dateTime",
      "position": [
        250,
        150
      ],
      "parameters": {
        "options": {
          "timezone": "Asia/Singapore",
          "includeInputFields": false
        },
        "duration": 3,
        "timeUnit": "days",
        "magnitude": "{{ $now }}",
        "operation": "addToDate",
        "outputFieldName": "bookingDateTime"
      },
      "typeVersion": 2
    },
    {
      "id": "prepare-booking-data",
      "name": "Prepare Low-Latency Booking Payload",
      "type": "n8n-nodes-base.code",
      "position": [
        450,
        150
      ],
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "const bookingTime = new Date($input.item.bookingDateTime);\nreturn {\n  courtId: 'court-premium-1',\n  slotStartTime: bookingTime.toISOString(),\n  slotEndTime: new Date(bookingTime.getTime() + 60*60*1000).toISOString(),\n  duration: 60,\n  playerName: 'Auto Booker',\n  bookingType: 'rush',\n  timestamp: Date.now(),\n  clientVersion: 'v1.0-optimized',\n  requestId: 'book-' + Date.now(),\n  retryCount: 0,\n  maxRetries: 3\n};",
        "language": "javaScript"
      },
      "typeVersion": 2
    },
    {
      "id": "check-availability",
      "name": "Check Court Availability API",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        650,
        150
      ],
      "parameters": {
        "url": "https://api.tenniscourt.local/api/v1/courts/availability",
        "body": {
          "courtId": "{{ $json.courtId }}",
          "endTime": "{{ $json.slotEndTime }}",
          "timezone": "Asia/Singapore",
          "startTime": "{{ $json.slotStartTime }}"
        },
        "method": "POST",
        "options": {
          "timeout": 5000,
          "redirects": {
            "follow": true,
            "maxRedirects": 2
          }
        },
        "sendBody": true,
        "contentType": "json",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "X-Request-ID",
              "value": "{{ $json.requestId }}"
            },
            {
              "name": "X-Client-Version",
              "value": "{{ $json.clientVersion }}"
            },
            {
              "name": "User-Agent",
              "value": "TennisAutoBooker/1.0"
            },
            {
              "name": "Accept-Encoding",
              "value": "gzip"
            }
          ]
        }
      },
      "typeVersion": 4.3
    },
    {
      "id": "availability-check",
      "name": "Is Court Available?",
      "type": "n8n-nodes-base.if",
      "position": [
        850,
        150
      ],
      "parameters": {
        "conditions": {
          "options": {
            "version": 2,
            "operator": {
              "name": "filter.operator.equals",
              "value": "equals"
            },
            "leftValue": "{{ $json.available }}",
            "rightValue": "true",
            "caseSensitive": true,
            "typeValidation": "strict"
          }
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "prepare-form-submission",
      "name": "Build Form Submission Payload",
      "type": "n8n-nodes-base.code",
      "position": [
        1050,
        50
      ],
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "const formData = new URLSearchParams();\nformData.append('court_id', 'court-premium-1');\nformData.append('start_time', $input.item.slotStartTime);\nformData.append('end_time', $input.item.slotEndTime);\nformData.append('name', 'Auto Booker');\nformData.append('fast_track', 'true');\nformData.append('req_id', $input.item.requestId);\nformData.append('ts', Date.now().toString());\nreturn {\n  formPayload: formData.toString(),\n  priority: 'high',\n  submitTime: new Date().toISOString()\n};",
        "language": "javaScript"
      },
      "typeVersion": 2
    },
    {
      "id": "submit-booking",
      "name": "Submit Booking Form (Race-Safe)",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1250,
        50
      ],
      "parameters": {
        "url": "https://booking.tenniscourt.local/book",
        "body": "{{ $json.formPayload }}",
        "method": "POST",
        "options": {
          "timeout": 3000,
          "redirects": {
            "follow": false,
            "maxRedirects": 0
          }
        },
        "sendBody": true,
        "contentType": "form-urlencoded",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "X-Requested-With",
              "value": "AutoBooker"
            },
            {
              "name": "X-Priority",
              "value": "high"
            },
            {
              "name": "X-Request-ID",
              "value": "{{ $json.requestId }}"
            },
            {
              "name": "Connection",
              "value": "keep-alive"
            }
          ]
        }
      },
      "typeVersion": 4.3
    },
    {
      "id": "check-booking-success",
      "name": "Booking Successful?",
      "type": "n8n-nodes-base.if",
      "position": [
        1450,
        50
      ],
      "parameters": {
        "conditions": {
          "options": {
            "version": 2,
            "operator": {
              "name": "filter.operator.equals",
              "value": "equals"
            },
            "leftValue": "{{ $response.status }}",
            "rightValue": "200",
            "caseSensitive": true,
            "typeValidation": "strict"
          }
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "create-calendar-event",
      "name": "Create Google Calendar Event",
      "type": "n8n-nodes-base.googleCalendar",
      "position": [
        1650,
        50
      ],
      "parameters": {
        "end": "{{ $json.slotEndTime }}",
        "start": "{{ $json.slotStartTime }}",
        "title": "Tennis Court Booking - Court Premium 1",
        "options": {
          "location": "Tennis Court Premium 1"
        },
        "calendar": "primary",
        "resource": "event",
        "description": "Auto-booked tennis court session"
      },
      "typeVersion": 1.3
    },
    {
      "id": "not-available-handler",
      "name": "Courts Not Available - Retry Logic",
      "type": "n8n-nodes-base.code",
      "position": [
        1050,
        250
      ],
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "const retryCount = $input.item.retryCount || 0;\nconst maxRetries = $input.item.maxRetries || 3;\nreturn {\n  status: 'courts_unavailable',\n  retryCount: retryCount + 1,\n  shouldRetry: retryCount < maxRetries,\n  nextRetryTime: new Date(Date.now() + (500 * Math.pow(1.5, retryCount))).toISOString(),\n  message: `Courts unavailable. Retry ${retryCount + 1}/${maxRetries}`\n};",
        "language": "javaScript"
      },
      "typeVersion": 2
    },
    {
      "id": "booking-failed-handler",
      "name": "Booking Failed - Alert Handler",
      "type": "n8n-nodes-base.code",
      "position": [
        1650,
        250
      ],
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "return {\n  status: 'booking_failed',\n  timestamp: new Date().toISOString(),\n  httpStatus: $json.status || 'unknown',\n  errorMessage: $json.body?.message || 'Unknown error',\n  requestId: $input.item.requestId,\n  alertLevel: 'high'\n};",
        "language": "javaScript"
      },
      "typeVersion": 2
    },
    {
      "id": "log-success",
      "name": "Log Successful Booking",
      "type": "n8n-nodes-base.code",
      "position": [
        1850,
        50
      ],
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "return {\n  status: 'success',\n  timestamp: new Date().toISOString(),\n  bookingTime: $json.slotStartTime,\n  courtId: $json.courtId,\n  requestId: $json.requestId,\n  executionTime: `${Date.now() - parseInt($json.timestamp)}ms`,\n  message: 'Tennis court successfully booked!'\n};",
        "language": "javaScript"
      },
      "typeVersion": 2
    }
  ],
  "connections": {
    "Booking Successful?": {
      "main": [
        [
          {
            "node": "Create Google Calendar Event",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Booking Failed - Alert Handler",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Is Court Available?": {
      "main": [
        [
          {
            "node": "Build Form Submission Payload",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Courts Not Available - Retry Logic",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Schedule Booking Trigger": {
      "main": [
        [
          {
            "node": "Calculate 72h Booking Time",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Calculate 72h Booking Time": {
      "main": [
        [
          {
            "node": "Prepare Low-Latency Booking Payload",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Court Availability API": {
      "main": [
        [
          {
            "node": "Is Court Available?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Google Calendar Event": {
      "main": [
        [
          {
            "node": "Log Successful Booking",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Form Submission Payload": {
      "main": [
        [
          {
            "node": "Submit Booking Form (Race-Safe)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Submit Booking Form (Race-Safe)": {
      "main": [
        [
          {
            "node": "Booking Successful?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Low-Latency Booking Payload": {
      "main": [
        [
          {
            "node": "Check Court Availability API",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}