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": "n8nCal \u00b7 /events",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "n8ncal/events",
"responseMode": "responseNode",
"options": {
"allowedOrigins": "*"
}
},
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
-700,
0
],
"id": "41f8644f-203a-4ae8-bc18-66ab06c3ae8e",
"name": "Webhook \u00b7 /events"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "auth",
"leftValue": "={{ $json.headers['x-n8ncal-token'] }}",
"rightValue": "={{ $env.N8NCAL_TOKEN }}",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
-480,
0
],
"id": "0b8a7db4-6b7f-4077-af1f-949be81937f3",
"name": "Auth check"
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={\n \"error\": \"unauthorized\"\n}",
"options": {
"responseCode": 401
}
},
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
-260,
200
],
"id": "5bd8aee2-6039-4028-99e5-671fdefcf92f",
"name": "Respond 401"
},
{
"parameters": {
"jsCode": "// Pull from/to from the body. Default to a 7-day window starting today.\nconst body = $input.first().json.body || {};\nconst now = new Date();\nconst weekFromNow = new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000);\n\nconst from = body.from ? new Date(body.from) : now;\nconst to = body.to ? new Date(body.to) : weekFromNow;\n\nif (isNaN(from) || isNaN(to)) {\n throw new Error('from/to must be ISO 8601 if provided');\n}\nif (to <= from) {\n throw new Error('to must be after from');\n}\n\nreturn [{\n json: {\n from: from.toISOString(),\n to: to.toISOString(),\n calendar: body.calendar || 'primary'\n }\n}];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-260,
0
],
"id": "c94ff647-6112-45a6-998e-e608e0116786",
"name": "Parse window"
},
{
"parameters": {
"operation": "getAll",
"calendar": {
"__rl": true,
"value": "={{ $json.calendar }}",
"mode": "id"
},
"returnAll": false,
"limit": 100,
"options": {
"timeMin": "={{ $json.from }}",
"timeMax": "={{ $json.to }}",
"singleEvents": true,
"orderBy": "startTime"
}
},
"type": "n8n-nodes-base.googleCalendar",
"typeVersion": 1.3,
"position": [
-40,
0
],
"id": "7e9b1b15-cb68-4331-a100-9bcce8ef92f4",
"name": "Google Calendar \u00b7 list",
"credentials": {
"googleCalendarOAuth2Api": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "// Compact for the dashboard AND classify each event into a 'kind' the UI can\n// color-code. The heuristic is intentionally simple \u2014 it can be replaced with\n// an AI classifier later without changing the response shape.\n//\n// focus \u2014 title looks like a deep-work block, OR no attendees + obvious cue\n// personal \u2014 0\u20131 attendees, doesn't look like work\n// external \u2014 attendees span >1 email domain (counts as customer/investor/etc)\n// internal \u2014 attendees all share the same domain\n// meeting \u2014 fallback\n\nfunction kindOf(e) {\n const title = (e.summary || '').toLowerCase();\n const attendees = (e.attendees || []).filter(a => a.email && !a.resource);\n const domains = new Set(\n attendees.map(a => (a.email.split('@')[1] || '').toLowerCase()).filter(Boolean)\n );\n\n if (/\\bfocus\\b|\\bdeep work\\b|\\bheads down\\b|\\bblock\\b|\\bno meetings\\b/.test(title)) {\n return 'focus';\n }\n if (attendees.length <= 1) {\n if (/\\blunch\\b|\\bdinner\\b|\\bcoffee\\b|\\bgym\\b|\\bdoctor\\b|\\bbirthday\\b/.test(title)) return 'personal';\n if (attendees.length === 0) return 'focus';\n return 'personal';\n }\n if (domains.size > 1) return 'external';\n return 'internal';\n}\n\nreturn items.map(item => {\n const e = item.json;\n return {\n json: {\n id: e.id,\n title: e.summary || '(no title)',\n start: e.start?.dateTime || e.start?.date,\n end: e.end?.dateTime || e.end?.date,\n allDay: !e.start?.dateTime,\n location: e.location || null,\n attendees: (e.attendees || []).map(a => a.email).filter(Boolean),\n kind: kindOf(e),\n htmlLink: e.htmlLink,\n status: e.status\n }\n };\n});"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
180,
0
],
"id": "1cfeccf0-d5ac-46fa-b274-2bf950885d81",
"name": "Compact & classify"
},
{
"parameters": {
"aggregate": "aggregateAllItemData",
"destinationFieldName": "events",
"options": {}
},
"type": "n8n-nodes-base.aggregate",
"typeVersion": 1,
"position": [
400,
0
],
"id": "719b3b2b-fdcf-4023-a3ae-fa08dd7c5bce",
"name": "Roll up"
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "ev",
"name": "events",
"value": "={{ $json.events }}",
"type": "array"
},
{
"id": "from",
"name": "from",
"value": "={{ $('Parse window').item.json.from }}",
"type": "string"
},
{
"id": "to",
"name": "to",
"value": "={{ $('Parse window').item.json.to }}",
"type": "string"
},
{
"id": "ts",
"name": "ts",
"value": "={{ $now.toISO() }}",
"type": "string"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
620,
0
],
"id": "57ad49b9-5248-4826-acf2-928e650e63cd",
"name": "Shape response"
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ $json }}",
"options": {}
},
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
840,
0
],
"id": "b1868415-b9d3-4b57-86f2-c671adc57173",
"name": "Respond"
}
],
"connections": {
"Webhook \u00b7 /events": {
"main": [
[
{
"node": "Auth check",
"type": "main",
"index": 0
}
]
]
},
"Auth check": {
"main": [
[
{
"node": "Parse window",
"type": "main",
"index": 0
}
],
[
{
"node": "Respond 401",
"type": "main",
"index": 0
}
]
]
},
"Parse window": {
"main": [
[
{
"node": "Google Calendar \u00b7 list",
"type": "main",
"index": 0
}
]
]
},
"Google Calendar \u00b7 list": {
"main": [
[
{
"node": "Compact & classify",
"type": "main",
"index": 0
}
]
]
},
"Compact & classify": {
"main": [
[
{
"node": "Roll up",
"type": "main",
"index": 0
}
]
]
},
"Roll up": {
"main": [
[
{
"node": "Shape response",
"type": "main",
"index": 0
}
]
]
},
"Shape response": {
"main": [
[
{
"node": "Respond",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1"
},
"tags": [
{
"name": "n8nCal"
}
]
}
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
n8nCal · /events. Uses googleCalendar. Webhook trigger; 9 nodes.
Source: https://github.com/dasecure/n8ncal/blob/dd9a67e052fb7ba52c03770283ea1a95f0595fc2/workflows/08-events.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
Rodopi Dent - Calendar Update Event. Uses googleCalendar. Webhook trigger; 5 nodes.
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