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": "Rodopi Dent - Calendar Update Event",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "calendar-update",
"responseMode": "responseNode",
"options": {
"allowedOrigins": "*"
}
},
"id": "webhook",
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
0,
0
]
},
{
"parameters": {
"jsCode": "// Parse and validate input\nconst body = $input.first().json.body;\n\n// Required: event ID\nif (!body.eventId && !body.id && !body.googleEventId) {\n throw new Error('ID \u043d\u0430 \u0441\u044a\u0431\u0438\u0442\u0438\u0435\u0442\u043e \u0435 \u0437\u0430\u0434\u044a\u043b\u0436\u0438\u0442\u0435\u043b\u043d\u043e');\n}\n\nconst eventId = body.eventId || body.id || body.googleEventId;\n\n// Map color names to Google Calendar colorId (1-11)\nconst colorMap = {\n 'green': '10', 'blue': '9', 'red': '11', 'yellow': '5',\n 'purple': '3', 'orange': '6', 'pink': '4', 'gray': '8'\n};\n\nconst statusLabels = { 'pending': '\u0427\u0430\u043a\u0430\u0449', 'confirmed': '\u041f\u043e\u0442\u0432\u044a\u0440\u0434\u0435\u043d', 'completed': '\u0417\u0430\u0432\u044a\u0440\u0448\u0435\u043d' };\nconst statusVal = body.status || 'confirmed';\nconst statusLabel = statusLabels[statusVal] || '\u041f\u043e\u0442\u0432\u044a\u0440\u0434\u0435\u043d';\n\n// Reminder flag (if provided)\nconst hasReminderField = (body.sendReminder !== undefined && body.sendReminder !== null);\nconst sendReminder = body.sendReminder === true || body.sendReminder === 'true';\n\n// Build update fields\nconst updateFields = {};\n\nif (body.date && body.startTime) {\n const duration = body.duration || 30;\n const startDate = new Date(`${body.date}T${body.startTime}:00`);\n const endDate = new Date(startDate.getTime() + duration * 60 * 1000);\n const endTime = body.endTime || `${String(endDate.getHours()).padStart(2, '0')}:${String(endDate.getMinutes()).padStart(2, '0')}`;\n \n updateFields.startDateTime = `${body.date}T${body.startTime}:00`;\n updateFields.endDateTime = `${body.date}T${endTime}:00`;\n}\n\nif (body.patientName) {\n let name = body.patientName.replace(/^\u23f3\\s*/, '');\n updateFields.summary = statusVal === 'pending' ? '\u23f3 ' + name : name;\n}\n\n// Build description with status line AND reminder tag\nlet description;\nif (body.description) {\n // Caller provided full description \u2014 clean known tags then re-append\n description = body.description\n .replace(/\ud83d\udccb\\s*\u0421\u0442\u0430\u0442\u0443\u0441:.*\\n?/gi, '')\n .replace(/\ud83d\udd14\\s*\u041d\u0430\u043f\u043e\u043c\u043d\u044f\u043d\u0435:.*\\n?/gi, '')\n .trim();\n description += '\\n\ud83d\udccb \u0421\u0442\u0430\u0442\u0443\u0441: ' + statusLabel;\n if (hasReminderField) {\n description += '\\n\ud83d\udd14 \u041d\u0430\u043f\u043e\u043c\u043d\u044f\u043d\u0435: ' + (sendReminder ? '\u0414\u0410' : '\u041d\u0415');\n }\n} else {\n // Build description from individual fields\n description = '';\n if (body.patientPhone) description += `\ud83d\udcde \u0422\u0435\u043b: ${body.patientPhone}\\n`;\n if (body.patientEmail) description += `\u2709\ufe0f \u0418\u043c\u0435\u0439\u043b: ${body.patientEmail}\\n`;\n if (body.procedure) description += `\ud83e\uddb7 \u041f\u0440\u043e\u0446\u0435\u0434\u0443\u0440\u0430: ${body.procedure}\\n`;\n description += `\ud83d\udccb \u0421\u0442\u0430\u0442\u0443\u0441: ${statusLabel}\\n`;\n if (hasReminderField) {\n description += `\ud83d\udd14 \u041d\u0430\u043f\u043e\u043c\u043d\u044f\u043d\u0435: ${sendReminder ? '\u0414\u0410' : '\u041d\u0415'}\\n`;\n }\n if (body.notes) description += `\ud83d\udcdd \u0411\u0435\u043b\u0435\u0436\u043a\u0438: ${body.notes}\\n`;\n}\nupdateFields.description = description.trim();\n\nconst statusColorDefaults = { 'pending': 'yellow', 'completed': 'gray', 'confirmed': 'green' };\nconst effectiveColor = body.colorId || statusColorDefaults[statusVal] || 'green';\nupdateFields.colorId = colorMap[effectiveColor] || colorMap['green'];\n\nreturn [{\n json: {\n eventId: eventId,\n updateFields: updateFields,\n status: statusVal,\n sendReminder: sendReminder,\n originalData: body\n }\n}];"
},
"id": "prepare",
"name": "Prepare Update Data",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
220,
0
]
},
{
"parameters": {
"operation": "update",
"calendar": {
"__rl": true,
"mode": "id",
"value": "rodopi.dent@gmail.com"
},
"eventId": "={{ $json.eventId }}",
"updateFields": {
"start": "={{ $json.updateFields.startDateTime }}",
"end": "={{ $json.updateFields.endDateTime }}",
"summary": "={{ $json.updateFields.summary }}",
"description": "={{ $json.updateFields.description }}",
"colorId": "={{ $json.updateFields.colorId || '' }}"
}
},
"id": "update-event",
"name": "Update Calendar Event",
"type": "n8n-nodes-base.googleCalendar",
"typeVersion": 1.2,
"position": [
440,
0
],
"credentials": {
"googleCalendarOAuth2Api": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "// Get updated event details\nconst event = $input.first().json;\nconst prevData = $('Prepare Update Data').first().json;\n\n// Helper function to convert to Europe/Sofia timezone\nfunction toSofiaTime(isoString) {\n if (!isoString) return null;\n const d = new Date(isoString);\n const options = {\n timeZone: 'Europe/Sofia',\n year: 'numeric',\n month: '2-digit',\n day: '2-digit',\n hour: '2-digit',\n minute: '2-digit',\n hour12: false\n };\n const formatter = new Intl.DateTimeFormat('en-CA', options);\n const parts = formatter.formatToParts(d);\n const getPart = (type) => parts.find(p => p.type === type)?.value || '00';\n return {\n date: `${getPart('year')}-${getPart('month')}-${getPart('day')}`,\n time: `${getPart('hour')}:${getPart('minute')}`\n };\n}\n\n// Parse times - CONVERT TO SOFIA TIMEZONE\nconst startDateTime = event.start?.dateTime || event.start?.date;\nconst endDateTime = event.end?.dateTime || event.end?.date;\nlet startDate, startTime, endTime;\n\nif (startDateTime) {\n const startSofia = toSofiaTime(startDateTime);\n const endSofia = toSofiaTime(endDateTime);\n startDate = startSofia.date;\n startTime = startSofia.time;\n endTime = endSofia.time;\n}\n\nreturn [{\n json: {\n id: event.id,\n googleEventId: event.id,\n title: event.summary,\n date: startDate,\n startTime: startTime,\n endTime: endTime,\n description: event.description,\n status: prevData.status || 'confirmed',\n htmlLink: event.htmlLink,\n updated: event.updated\n }\n}];"
},
"id": "format-response",
"name": "Format Response",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
660,
0
]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ JSON.stringify({ success: true, message: '\u0421\u044a\u0431\u0438\u0442\u0438\u0435\u0442\u043e \u0435 \u043e\u0431\u043d\u043e\u0432\u0435\u043d\u043e \u0443\u0441\u043f\u0435\u0448\u043d\u043e', event: $json }) }}",
"options": {
"responseHeaders": {
"entries": [
{
"name": "Access-Control-Allow-Origin",
"value": "*"
}
]
}
}
},
"id": "respond-success",
"name": "Respond Success",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
880,
0
]
}
],
"connections": {
"Webhook": {
"main": [
[
{
"node": "Prepare Update Data",
"type": "main",
"index": 0
}
]
]
},
"Prepare Update Data": {
"main": [
[
{
"node": "Update Calendar Event",
"type": "main",
"index": 0
}
]
]
},
"Update Calendar Event": {
"main": [
[
{
"node": "Format Response",
"type": "main",
"index": 0
}
]
]
},
"Format Response": {
"main": [
[
{
"node": "Respond Success",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1"
},
"tags": [
{
"name": "Rodopi Dent"
}
]
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
googleCalendarOAuth2Api
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Rodopi Dent - Calendar Update Event. Uses googleCalendar. Webhook trigger; 5 nodes.
Source: https://github.com/Georgi-Piskov/RODOPI-DENT/blob/9eac796b44530dafd0ccdec3388d9825197936ea/n8n-workflows/13-calendar-update.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.
github code Try yourself
Connect a Vapi AI voice agent to Google Calendar to capture contact details and auto-book appointments. The agent asks for name, address, service type, and a preferred time. The workflow checks availa
Need help? Want access to this workflow + many more paid workflows + live Q&A sessions with a top verified n8n creator?
A production-ready authentication workflow implementing secure user registration, login, token verification, and refresh token mechanisms. Perfect for adding authentication to any application without