{
  "name": "VOICE 01 - Retell Booking Management Tool Gateway",
  "nodes": [
    {
      "name": "Security Boundary",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -1180,
        -430
      ],
      "parameters": {
        "content": "## Retell booking-management gateway\nPublic webhook, private custom header. Configure Retell custom functions to send `X-Voice-Tool-Secret`. Normal new bookings use Retell's native Cal.com tools. This gateway only finds, requests rescheduling, or cancels after identity and confirmation checks.",
        "height": 320,
        "width": 760,
        "color": 5
      }
    },
    {
      "name": "Retell Management Tool Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2.1,
      "position": [
        -1100,
        0
      ],
      "parameters": {
        "httpMethod": "POST",
        "path": "voice-booking-management",
        "responseMode": "responseNode",
        "options": {}
      }
    },
    {
      "name": "Authenticate and Normalize",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -840,
        0
      ],
      "parameters": {
        "jsCode": "const h=$json.headers??{}; const b=$json.body??$json; const supplied=String(h[\"x-voice-tool-secret\"]??h[\"X-Voice-Tool-Secret\"]??\"\");\nconst expected=\"SET_VOICE_TOOL_SECRET\";\nif(!supplied||expected===\"SET_VOICE_TOOL_SECRET\"||supplied.length!==expected.length){return [{json:{ok:false,http_status:401,status:\"UNAUTHORIZED\",message:\"Tool authentication failed.\"}}];}\nlet same=0; for(let i=0;i<expected.length;i++) same|=supplied.charCodeAt(i)^expected.charCodeAt(i);\nif(same!==0) return [{json:{ok:false,http_status:401,status:\"UNAUTHORIZED\",message:\"Tool authentication failed.\"}}];\nconst args=b.args??b.arguments??{}; const action=String(b.name??args.action??\"\").toLowerCase();\nconst email=String(args.attendee_email??args.email??\"\").trim().toLowerCase();\nconst uid=String(args.booking_uid??\"\").trim();\nconst confirmation=String(args.confirmation??\"\").toLowerCase()===\"confirmed\";\nconst allowed=[\"find_booking\",\"request_reschedule\",\"cancel_booking\"];\nif(!allowed.includes(action)||!email||!/^\\S+@\\S+\\.\\S+$/.test(email)){return [{json:{ok:false,http_status:400,status:\"INVALID_REQUEST\",message:\"A supported action and valid attendee email are required.\"}}];}\nif([\"request_reschedule\",\"cancel_booking\"].includes(action)&&(!uid||!confirmation)){return [{json:{ok:false,http_status:400,status:\"CONFIRMATION_REQUIRED\",message:\"Booking UID and explicit caller confirmation are required.\"}}];}\nreturn [{json:{ok:true,http_status:200,action,attendee_email:email,booking_uid:uid,reason:String(args.reason??\"Caller requested a booking change\").slice(0,300)}}];"
      }
    },
    {
      "name": "Request Valid?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        -560,
        0
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 3
          },
          "conditions": [
            {
              "id": "Request Valid?-condition",
              "leftValue": "={{ $json.ok ? 'YES' : 'NO' }}",
              "rightValue": "YES",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      }
    },
    {
      "name": "Read Matching Cal.com Bookings",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        -300,
        -120
      ],
      "parameters": {
        "method": "GET",
        "url": "={{ 'https://api.cal.com/v2/bookings?status=accepted&attendeeEmail=' + encodeURIComponent($json.attendee_email) + '&take=20' }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "cal-api-version",
              "value": "2026-02-25"
            }
          ]
        },
        "options": {
          "response": {
            "response": {
              "neverError": true,
              "responseFormat": "json"
            }
          },
          "timeout": 15000
        }
      }
    },
    {
      "name": "Verify Booking Ownership",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -40,
        -120
      ],
      "parameters": {
        "jsCode": "const req=$(\"Authenticate and Normalize\").item.json; const raw=$json.data??$json; const rows=Array.isArray(raw)?raw:(Array.isArray(raw.bookings)?raw.bookings:[]);\nconst future=rows.filter(x=>new Date(x.start)<new Date(Date.now()+366*86400000)&&new Date(x.end)>new Date());\nif(req.action===\"find_booking\") return [{json:{...req,found:future.length>0,bookings:future.slice(0,5).map(x=>({uid:x.uid,title:x.title,start:x.start,end:x.end,status:x.status}))}}];\nconst match=future.find(x=>String(x.uid)===req.booking_uid);\nreturn [{json:{...req,found:!!match,booking:match?{uid:match.uid,title:match.title,start:match.start,end:match.end,status:match.status}:null,message:match?\"Booking identity verified.\":\"No matching future booking was found for that email.\"}}];"
      }
    },
    {
      "name": "Find Only?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        220,
        -120
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 3
          },
          "conditions": [
            {
              "id": "Find Only?-condition",
              "leftValue": "={{ $json.action }}",
              "rightValue": "find_booking",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      }
    },
    {
      "name": "Build Find Result",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        480,
        -260
      ],
      "parameters": {
        "jsCode": "return [{json:{ok:true,status:$json.found?\"FOUND\":\"NOT_FOUND\",message:$json.found?\"Matching future booking found.\":\"No matching future booking was found.\",bookings:$json.bookings??[]}}];"
      }
    },
    {
      "name": "Booking Verified?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        480,
        20
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 3
          },
          "conditions": [
            {
              "id": "Booking Verified?-condition",
              "leftValue": "={{ $json.found ? 'YES' : 'NO' }}",
              "rightValue": "YES",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      }
    },
    {
      "name": "Cancel Action?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        740,
        -20
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 3
          },
          "conditions": [
            {
              "id": "Cancel Action?-condition",
              "leftValue": "={{ $json.action }}",
              "rightValue": "cancel_booking",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      }
    },
    {
      "name": "Cancel Verified Booking",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1000,
        -100
      ],
      "parameters": {
        "method": "POST",
        "url": "={{ 'https://api.cal.com/v2/bookings/' + encodeURIComponent($json.booking_uid) + '/cancel' }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "cal-api-version",
              "value": "2026-02-25"
            }
          ]
        },
        "sendBody": true,
        "contentType": "raw",
        "rawContentType": "application/json",
        "body": "={{ JSON.stringify({ cancellationReason: $json.reason }) }}",
        "options": {
          "response": {
            "response": {
              "neverError": true,
              "responseFormat": "json"
            }
          },
          "timeout": 15000
        }
      }
    },
    {
      "name": "Request Secure Reschedule Link",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1000,
        100
      ],
      "parameters": {
        "method": "POST",
        "url": "={{ 'https://api.cal.com/v2/bookings/' + encodeURIComponent($json.booking_uid) + '/request-reschedule' }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "cal-api-version",
              "value": "2026-02-25"
            }
          ]
        },
        "sendBody": true,
        "contentType": "raw",
        "rawContentType": "application/json",
        "body": "={{ JSON.stringify({ reschedulingReason: $json.reason }) }}",
        "options": {
          "response": {
            "response": {
              "neverError": true,
              "responseFormat": "json"
            }
          },
          "timeout": 15000
        }
      }
    },
    {
      "name": "Build Management Result",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1260,
        0
      ],
      "parameters": {
        "jsCode": "const req=$(\"Verify Booking Ownership\").item.json; const ok=Number($json.statusCode??200)<400&&!$json.error;\nreturn [{json:{ok,status:ok?(req.action===\"cancel_booking\"?\"CANCELLED\":\"RESCHEDULE_LINK_SENT\"):\"PROVIDER_ERROR\",message:ok?(req.action===\"cancel_booking\"?\"The booking was cancelled.\":\"A secure rescheduling link was sent by the calendar provider.\"):\"The calendar provider could not complete the request.\",booking_uid:req.booking_uid}}];"
      }
    },
    {
      "name": "Build Not Found Result",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        740,
        220
      ],
      "parameters": {
        "jsCode": "return [{json:{ok:false,status:\"NOT_FOUND\",message:\"No matching future booking was found for that attendee email and booking reference.\"}}];"
      }
    },
    {
      "name": "Return to Retell",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.4,
      "position": [
        1540,
        0
      ],
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify($json) }}",
        "options": {}
      }
    },
    {
      "name": "Return Rejected Request",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.4,
      "position": [
        -300,
        180
      ],
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify($json) }}",
        "options": {}
      }
    }
  ],
  "connections": {
    "Retell Management Tool Webhook": {
      "main": [
        [
          {
            "node": "Authenticate and Normalize",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Authenticate and Normalize": {
      "main": [
        [
          {
            "node": "Request Valid?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Request Valid?": {
      "main": [
        [
          {
            "node": "Read Matching Cal.com Bookings",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Return Rejected Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read Matching Cal.com Bookings": {
      "main": [
        [
          {
            "node": "Verify Booking Ownership",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Verify Booking Ownership": {
      "main": [
        [
          {
            "node": "Find Only?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Find Only?": {
      "main": [
        [
          {
            "node": "Build Find Result",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Booking Verified?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Find Result": {
      "main": [
        [
          {
            "node": "Return to Retell",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Booking Verified?": {
      "main": [
        [
          {
            "node": "Cancel Action?",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Build Not Found Result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Cancel Action?": {
      "main": [
        [
          {
            "node": "Cancel Verified Booking",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Request Secure Reschedule Link",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Cancel Verified Booking": {
      "main": [
        [
          {
            "node": "Build Management Result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Request Secure Reschedule Link": {
      "main": [
        [
          {
            "node": "Build Management Result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Management Result": {
      "main": [
        [
          {
            "node": "Return to Retell",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Not Found Result": {
      "main": [
        [
          {
            "node": "Return to Retell",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1",
    "timezone": "SET_BUSINESS_TIMEZONE",
    "saveManualExecutions": true,
    "saveExecutionProgress": true,
    "saveDataErrorExecution": "all",
    "saveDataSuccessExecution": "all",
    "executionTimeout": 300
  }
}
