This workflow follows the Emailsend → HTTP Request recipe pattern — see all workflows that pair these two integrations.
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 →
{
"id": "06NQnIRoqEnoq6eb",
"name": "VenueDesk - Cancel Booking (Policy + Refund)",
"active": true,
"settings": {
"executionOrder": "v1",
"binaryMode": "separate",
"availableInMCP": true
},
"connections": {
"Webhook: Cancel Booking": {
"main": [
[
{
"node": "Extract & Validate",
"type": "main",
"index": 0
}
]
]
},
"Extract & Validate": {
"main": [
[
{
"node": "DB: Get Booking",
"type": "main",
"index": 0
}
]
]
},
"DB: Get Booking": {
"main": [
[
{
"node": "DB: Get Cancel Policy",
"type": "main",
"index": 0
}
]
]
},
"DB: Get Cancel Policy": {
"main": [
[
{
"node": "Code: Calculate Refund",
"type": "main",
"index": 0
}
]
]
},
"Code: Calculate Refund": {
"main": [
[
{
"node": "DB: Cancel Booking",
"type": "main",
"index": 0
}
]
]
},
"DB: Cancel Booking": {
"main": [
[
{
"node": "IF: Refund Due?",
"type": "main",
"index": 0
}
]
]
},
"IF: Refund Due?": {
"main": [
[
{
"node": "Email: Cancellation (Refund)",
"type": "main",
"index": 0
}
],
[
{
"node": "Email: Cancellation (No Refund)",
"type": "main",
"index": 0
}
]
]
},
"Email: Cancellation (Refund)": {
"main": [
[
{
"node": "Respond: Success",
"type": "main",
"index": 0
}
]
]
},
"Email: Cancellation (No Refund)": {
"main": [
[
{
"node": "Respond: No Refund",
"type": "main",
"index": 0
}
]
]
}
},
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "cancel-booking-v1",
"responseMode": "responseNode",
"options": {}
},
"id": "60c37fec-dddf-4335-b551-cbf80cb147fd",
"name": "Webhook: Cancel Booking",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
-6016,
-480
]
},
{
"parameters": {
"jsCode": "const body = $input.first().json.body || $input.first().json;\n\nconst booking_id = body.booking_id || '';\nconst tenant_id = parseInt(body.tenant_id || $input.first().json.headers?.['x-tenant-id'] || '0');\nconst cancelled_by = body.cancelled_by || 'staff';\nconst reason = body.cancellation_reason || body.reason || '';\nconst refund_override = body.refund_amount != null ? parseFloat(body.refund_amount) : null;\nconst jwt = body.jwt || '';\n\nif (!booking_id) throw new Error('booking_id is required');\nif (!tenant_id) throw new Error('tenant_id is required');\n\nconst ref = 'CANC-' + Math.floor(100000 + Math.random() * 900000);\n\nreturn [{ json: { booking_id, tenant_id, cancelled_by, reason, refund_override, cancellation_ref: ref, jwt } }];"
},
"id": "3cc8d143-5f16-44ee-ba99-6c4111612133",
"name": "Extract & Validate",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-5808,
-480
]
},
{
"parameters": {
"url": "={{ 'https://api.venuedesk.co.uk/bookings/' + $json.booking_id }}",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "={{ 'Bearer ' + $env.N8N_SERVICE_JWT }}"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"options": {
"response": {
"response": {
"neverError": true
}
},
"timeout": 15000
}
},
"id": "5cc67cac-b004-4694-a33c-e3d76a3a1e4f",
"name": "DB: Get Booking",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
-5584,
-480
],
"continueOnFail": true
},
{
"parameters": {
"url": "https://api.venuedesk.co.uk/config/settings",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "={{ 'Bearer ' + $env.N8N_SERVICE_JWT }}"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"options": {
"response": {
"response": {
"neverError": true
}
},
"timeout": 15000
}
},
"id": "dad8e967-e79a-4fcb-8ee0-ab9ffaa9b92a",
"name": "DB: Get Cancel Policy",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
-5360,
-480
],
"continueOnFail": true
},
{
"parameters": {
"jsCode": "const input = $('Extract & Validate').first().json;\nconst booking = $('DB: Get Booking').first().json;\nconst policyRows= $input.all().map(r => r.json);\n\nif (!booking || !booking.id) throw new Error('Booking not found or wrong tenant');\n\nconst getSetting = (key, def) => {\n const r = policyRows.find(p => p.key === key);\n return r ? (parseFloat(r.value) || def) : def;\n};\nconst fullDays = getSetting('cancel_full_refund_days', 14);\nconst partDays = getSetting('cancel_partial_refund_days', 7);\nconst partPct = getSetting('cancel_partial_refund_pct', 50);\n\nconst bookingDate = new Date((booking.date_from || booking.booking_date).split('T')[0] + 'T00:00:00');\nconst today = new Date(); today.setHours(0,0,0,0);\nconst daysUntil = Math.round((bookingDate - today) / 86400000);\n\nconst depositPaid = parseFloat(booking.deposit_paid || 0);\nconst totalPaid = parseFloat(booking.total_amount || 0) - parseFloat(booking.balance_due || 0);\nconst refundableAmt = Math.max(0, totalPaid - depositPaid);\nconst totalAmt = parseFloat(booking.total_amount || 0);\nlet refundType, refundAmount;\n\nif (input.refund_override !== null) {\n refundAmount = Math.min(Math.max(0, input.refund_override), refundableAmt);\n refundType = refundAmount >= refundableAmt && refundableAmt > 0 ? 'full' : refundAmount > 0 ? 'partial' : 'none';\n} else if (daysUntil >= fullDays) {\n refundType = 'full';\n refundAmount = refundableAmt;\n} else if (daysUntil >= partDays) {\n refundType = 'partial';\n refundAmount = parseFloat((refundableAmt * partPct / 100).toFixed(2));\n} else {\n refundType = 'none';\n refundAmount = 0;\n}\n\nreturn [{ json: {\n ...input,\n booking,\n daysUntil,\n refundType,\n refundAmount,\n depositPaid,\n totalPaid,\n refundableAmt,\n totalAmt,\n policyApplied: { fullDays, partDays, partPct }\n}}];"
},
"id": "b8758c13-f841-437a-abd5-f10a3e116f73",
"name": "Code: Calculate Refund",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-5136,
-480
]
},
{
"parameters": {
"method": "POST",
"url": "https://api.venuedesk.co.uk/bookings/cancel",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "={{ 'Bearer ' + $env.N8N_SERVICE_JWT }}"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ booking_id: $json.booking.id || $json.booking_id, cancelled_by: $json.cancelled_by, reason: $json.reason, refund_amount: $json.refundAmount || 0, refund_type: $json.refundType || 'none' }) }}",
"options": {
"response": {
"response": {
"neverError": true
}
},
"timeout": 15000
}
},
"id": "74c6b487-0304-4b2b-9b5f-40267e749660",
"name": "DB: Cancel Booking",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
-4928,
-480
],
"continueOnFail": true
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"leftValue": "={{ $('Code: Calculate Refund').first().json.refundAmount }}",
"rightValue": 0,
"operator": {
"type": "number",
"operation": "gt"
},
"id": "cc970ae3-8da6-490b-806a-bc59d9c9437a"
}
],
"combinator": "and"
},
"options": {}
},
"id": "8368d92f-0404-45e9-aa0e-4a6e77eef1b5",
"name": "IF: Refund Due?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
-4688,
-480
]
},
{
"parameters": {
"fromEmail": "bookings@venuedesk.co.uk",
"toEmail": "={{ $('Code: Calculate Refund').first().json.booking.customer_email }}",
"subject": "={{ 'Booking Cancelled \u2013 Ref: ' + $('Code: Calculate Refund').first().json.cancellation_ref }}",
"html": "=<p>Dear {{ $('Code: Calculate Refund').first().json.booking.customer_name }},</p>\n<p>We are writing to confirm that your booking at <strong>{{ $('Code: Calculate Refund').first().json.booking.room_name }}</strong>\non <strong>{{ $('Code: Calculate Refund').first().json.booking.date_from || $('Code: Calculate Refund').first().json.booking.booking_date }}</strong> has been cancelled.</p>\n<p>In line with our cancellation policy, a <strong>{{ $('Code: Calculate Refund').first().json.refundType === 'full' ? 'full' : 'partial' }} refund</strong>\nof <strong>\u00a3{{ parseFloat($('Code: Calculate Refund').first().json.refundAmount).toFixed(2) }}</strong> has been processed to your original payment method.\nPlease allow 5\u201310 business days for the funds to appear on your statement.</p>\n{{ $('Code: Calculate Refund').first().json.reason ? '<p>Reason for cancellation: ' + $('Code: Calculate Refund').first().json.reason + '</p>' : '' }}\n<p>Your cancellation reference is: <strong>{{ $('Code: Calculate Refund').first().json.cancellation_ref }}</strong></p>\n<p>If you have any questions, please contact us at bookings@venuedesk.co.uk.</p>\n<p>Best regards,<br>The VenueDesk Team</p>",
"options": {}
},
"id": "ba613b83-7cba-4bd1-b99d-9680fddb5fcd",
"name": "Email: Cancellation (Refund)",
"type": "n8n-nodes-base.emailSend",
"typeVersion": 2.1,
"position": [
-4256,
-656
],
"continueOnFail": true
},
{
"parameters": {
"fromEmail": "bookings@venuedesk.co.uk",
"toEmail": "={{ $('Code: Calculate Refund').first().json.booking.customer_email }}",
"subject": "={{ 'Booking Cancelled \u2013 Ref: ' + $('Code: Calculate Refund').first().json.cancellation_ref }}",
"html": "=<p>Dear {{ $('Code: Calculate Refund').first().json.booking.customer_name }},</p>\n<p>We are writing to confirm that your booking at <strong>{{ $('Code: Calculate Refund').first().json.booking.room_name }}</strong>\non <strong>{{ $('Code: Calculate Refund').first().json.booking.date_from || $('Code: Calculate Refund').first().json.booking.booking_date }}</strong> has been cancelled.</p>\n<p>Unfortunately, as your cancellation falls within the no-refund period of our cancellation policy,\nno refund is applicable on this occasion.</p>\n{{ $('Code: Calculate Refund').first().json.reason ? '<p>Reason for cancellation: ' + $('Code: Calculate Refund').first().json.reason + '</p>' : '' }}\n<p>Your cancellation reference is: <strong>{{ $('Code: Calculate Refund').first().json.cancellation_ref }}</strong></p>\n<p>If you have any questions, please contact us at bookings@venuedesk.co.uk.</p>\n<p>Best regards,<br>The VenueDesk Team</p>",
"options": {}
},
"id": "e6319473-f32d-456e-a410-dd1c3c13aa71",
"name": "Email: Cancellation (No Refund)",
"type": "n8n-nodes-base.emailSend",
"typeVersion": 2.1,
"position": [
-4496,
-240
],
"continueOnFail": true
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ JSON.stringify({ success: true, cancellation_ref: $('Code: Calculate Refund').first().json.cancellation_ref, refund_type: $('Code: Calculate Refund').first().json.refundType, refund_amount: $('Code: Calculate Refund').first().json.refundAmount, days_until_booking: $('Code: Calculate Refund').first().json.daysUntil, booking_id: $('Code: Calculate Refund').first().json.booking.id, message: 'Booking cancelled successfully' }) }}",
"options": {
"responseCode": 200
}
},
"id": "c4f3edce-727e-41b8-b540-9d14512cf242",
"name": "Respond: Success",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.4,
"position": [
-4032,
-656
]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ JSON.stringify({ success: true, cancellation_ref: $('Code: Calculate Refund').first().json.cancellation_ref, refund_type: 'none', refund_amount: 0, days_until_booking: $('Code: Calculate Refund').first().json.daysUntil, booking_id: $('Code: Calculate Refund').first().json.booking.id, message: 'Booking cancelled \u2014 no refund applicable per policy' }) }}",
"options": {
"responseCode": 200
}
},
"id": "526a9585-1942-41e9-8e2b-aca546ea1958",
"name": "Respond: No Refund",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.4,
"position": [
-4256,
-240
]
}
]
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
VenueDesk - Cancel Booking (Policy + Refund). Uses httpRequest, emailSend. Webhook trigger; 11 nodes.
Source: https://github.com/AndyJay72/VenueDesk/blob/main/n8n-workflows/06NQnIRoqEnoq6eb.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.
세미나 데모 용 워크플로우. Uses httpRequest, emailSend. Webhook trigger; 17 nodes.
VenueDesk - Cancel Booking (Series Support). Uses emailSend, httpRequest. Webhook trigger; 17 nodes.
worklow_doc. Uses httpRequest, readBinaryFile, n8n-nodes-docxtemplater, emailSend. Webhook trigger; 15 nodes.
WF2 - Upload Manual | JurisAI. Uses httpRequest, emailSend. Webhook trigger; 15 nodes.
Deliver personalized files instantly after PayPal transactions using n8n – without writing a single backend line.