This workflow follows the Google Calendar → 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 →
{
"name": "Calendar Events to Invoice",
"nodes": [
{
"parameters": {
"operation": "getAll",
"calendar": {
"__rl": true,
"value": "",
"mode": "id"
},
"returnAll": true,
"timeMin": "={{ $('Config').first().json.dateRanges.previousMonthStart }}",
"timeMax": "={{ $('Config').first().json.dateRanges.previousMonthEnd }}",
"options": {
"fields": "=items(id, summary, start, end, eventType, colorId)",
"orderBy": "startTime",
"recurringEventHandling": "expand"
}
},
"type": "n8n-nodes-base.googleCalendar",
"typeVersion": 1.3,
"position": [
224,
0
],
"id": "3a989d17-cf4a-4da9-ae33-36f2101f879f",
"name": "Get many events",
"credentials": {
"googleCalendarOAuth2Api": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "e5b38923-6b05-4985-9934-638d9a3e2c38",
"leftValue": "={{ $json.colorId }}",
"rightValue": "",
"operator": {
"type": "string",
"operation": "exists",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.filter",
"typeVersion": 2.2,
"position": [
448,
0
],
"id": "5aa5d698-b43f-40f7-b87c-bbe3ad64b5d6",
"name": "Filter"
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "02cc967e-aa04-415c-8098-4d1020c6db29",
"name": "durationHours",
"value": "={{ DateTime.fromISO($json.end.dateTime).diff(DateTime.fromISO($json.start.dateTime), 'hours').hours }}",
"type": "number"
}
]
},
"includeOtherFields": true,
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
672,
0
],
"id": "fe859a13-1ef0-46b1-ae7d-42ac49fcdd0b",
"name": "Edit Fields"
},
{
"parameters": {
"fieldsToSummarize": {
"values": [
{
"aggregation": "sum",
"field": "durationHours"
}
]
},
"fieldsToSplitBy": "colorId",
"options": {
"outputFormat": "singleItem"
}
},
"type": "n8n-nodes-base.summarize",
"typeVersion": 1.1,
"position": [
880,
0
],
"id": "90c3de2c-447d-4830-b5cd-66eefeccd0ff",
"name": "Summarize"
},
{
"parameters": {
"jsCode": "// Configuration Hub - Put this early in your workflow\nconst hubId = \"123123\"; // HubSpot account identifier\nconst baseUrl = `https://app.hubspot.com/contacts/${hubId}/record`;\n\n// HubSpot product data mapped to Google Calendar event colors\nconst productData = {\n \"Product 1\": {\n colorId: \"11\", // Google Calendar event colorId\n id: \"26854895751\", // HubSpot productId\n name: \"Full name of Product 1\" // HubSpot product name\n },\n \"Product 2\": {\n colorId: \"3\", \n id: \"26837140533\",\n name: \"Full name of Product 2\"\n },\n \"Product 3\": {\n colorId: \"2\",\n id: \"26854895753\",\n name: \"Full name of Product 3\"\n }\n};\n\n// Contact data mapped by company name\nconst contactData = {\n \"Company A\": {\n contactId: \"123456789\", // HubSpot contactId\n contactName: \"Roger Wilco\",\n companyId: \"987654321\" // HubSpot companyId\n }\n};\n\nconst config = {\n // Auto-generate color mappings from productData\n colorMappings: Object.fromEntries(\n Object.entries(productData).map(([key, data]) => [data.colorId, key])\n ),\n \n // Build products with dynamic URLs\n products: Object.fromEntries(\n Object.entries(productData).map(([key, data]) => [\n key, \n {\n id: data.id,\n name: data.name,\n url: `${baseUrl}/0-7/${data.id}/properties`\n }\n ])\n ),\n \n // Build contacts mapped by company name with URLs\n contacts: Object.fromEntries(\n Object.entries(contactData).map(([companyName, data]) => [\n companyName,\n {\n contactId: data.contactId,\n contactName: data.contactName,\n contactUrl: `${baseUrl}/0-1/${data.contactId}`,\n companyId: data.companyId,\n companyUrl: `${baseUrl}/0-2/${data.companyId}`\n }\n ])\n ),\n \n // HubSpot Association Types\n // full ref: https://developers.hubspot.com/docs/guides/api/crm/associations/associations-v4#association-type-id-values\n associationTypes: {\n invoiceToContact: 177,\n invoiceToCompany: 179,\n lineItemToInvoice: 410\n },\n \n // Date ranges for retrieving calendar events\n dateRanges: {\n previousMonthStart: DateTime.now().minus({ months: 1 }).startOf('month').toISO(),\n previousMonthEnd: DateTime.now().startOf('month').toISO()\n },\n \n // Other constants\n constants: {\n currency: \"USD\"\n }\n};\n\nreturn [{ json: config }];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
0,
0
],
"id": "c7e01ee5-6742-4637-9da7-8ab5517a20f5",
"name": "Config"
},
{
"parameters": {
"method": "POST",
"url": "https://api.hubapi.com/crm/v3/objects/invoices",
"authentication": "genericCredentialType",
"genericAuthType": "httpBearerAuth",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"properties\": {\n \"hs_currency\": \"{{ $('Config').item.json.constants.currency }}\"\n },\n \"associations\": [\n {\n \"types\": [\n {\n \"associationCategory\": \"HUBSPOT_DEFINED\",\n \"associationTypeId\": {{ $('Config').item.json.associationTypes.invoiceToContact }}\n }\n ],\n \"to\": {\n \"id\": {{ $('Config').item.json.contacts['Company A'].contactId }}\n }\n },\n {\n \"types\": [\n {\n \"associationCategory\": \"HUBSPOT_DEFINED\",\n \"associationTypeId\": {{ $('Config').item.json.associationTypes.invoiceToCompany }}\n }\n ],\n \"to\": {\n \"id\": {{ $('Config').item.json.contacts['Company A'].companyId }}\n }\n }\n ]\n}",
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1088,
0
],
"id": "4b25e833-bac5-4b3a-98e2-353dd63be7bc",
"name": "Create Invoice",
"credentials": {
"httpBearerAuth": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"method": "POST",
"url": "https://api.hubapi.com/crm/v3/objects/line_items/batch/create",
"authentication": "genericCredentialType",
"genericAuthType": "httpBearerAuth",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ $json }}",
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1440,
0
],
"id": "7c20d33d-2d25-45b0-b3a1-ad9c891c993a",
"name": "Add Line Items",
"credentials": {
"httpBearerAuth": {
"name": "<your credential>"
}
}
},
{
"parameters": {},
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [
-224,
0
],
"id": "a25a7ef3-a1d1-4699-9217-8d81b6744d2d",
"name": "Manual"
},
{
"parameters": {
"jsCode": "const summaryData = $('Summarize').first().json;\nconst config = $('Config').first().json;\nconst invoiceId = $('Create Invoice').first().json.id;\n\nconst lineItemsPayload = {\n \"inputs\": Object.entries(summaryData).map(([colorId, data]) => ({\n \"properties\": {\n \"hs_product_id\": config.products[config.colorMappings[colorId]].id,\n \"quantity\": data.sum_durationHours.toString()\n },\n \"associations\": [\n {\n \"types\": [\n {\n \"associationCategory\": \"HUBSPOT_DEFINED\",\n \"associationTypeId\": config.associationTypes.lineItemToInvoice\n }\n ],\n \"to\": {\n \"id\": invoiceId\n }\n }\n ]\n }))\n};\n\nreturn [{ json: lineItemsPayload }];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1264,
0
],
"id": "4384c5ee-7ca6-4874-8b42-ffb6407a7b34",
"name": "Prepare Line Items"
},
{
"parameters": {
"content": "Replace with Schedule Trigger that runs every month to automate workflow",
"height": 272,
"width": 192
},
"type": "n8n-nodes-base.stickyNote",
"position": [
-272,
-112
],
"typeVersion": 1,
"id": "4b3d29fe-2540-4e40-8eb4-dcb275487fc1",
"name": "Sticky Note"
},
{
"parameters": {
"content": "Update with your information. Later nodes pull values generated by this node.",
"height": 272,
"width": 192
},
"type": "n8n-nodes-base.stickyNote",
"position": [
-48,
-112
],
"typeVersion": 1,
"id": "a36a0d8d-5de3-4790-aa81-7d40371fa0ff",
"name": "Sticky Note1"
},
{
"parameters": {
"content": "Exclude events without a colorId (or else events that are not billable).",
"height": 272,
"width": 192
},
"type": "n8n-nodes-base.stickyNote",
"position": [
400,
-112
],
"typeVersion": 1,
"id": "c8e33692-9f66-468c-9aea-4bab33165c19",
"name": "Sticky Note2"
},
{
"parameters": {
"content": "Calculate event durations in hours.",
"height": 272,
"width": 192
},
"type": "n8n-nodes-base.stickyNote",
"position": [
624,
-112
],
"typeVersion": 1,
"id": "f1b98baa-9b0c-49f1-bb11-b61a2dcde68d",
"name": "Sticky Note3"
},
{
"parameters": {
"content": "Calculate total duration in hours per colorId (event type) ",
"height": 272,
"width": 192
},
"type": "n8n-nodes-base.stickyNote",
"position": [
832,
-112
],
"typeVersion": 1,
"id": "d6a79ea6-93af-4a82-b8b7-4714d3dc1e93",
"name": "Sticky Note4"
},
{
"parameters": {
"content": "Create invoice and update with line items. Must create a HubSpot private app with appropriate scopes and use the private app access token for authentication [Reference](https://developers.hubspot.com/docs/guides/apps/private-apps/overview)",
"height": 272,
"width": 544
},
"type": "n8n-nodes-base.stickyNote",
"position": [
1040,
-112
],
"typeVersion": 1,
"id": "68edb0ac-2ec6-47e9-8f1f-f58dfd51448f",
"name": "Sticky Note5"
},
{
"parameters": {
"content": "Retrieve all calendar events from the previous month, ensuring colorId is included in response.",
"height": 272,
"width": 192
},
"type": "n8n-nodes-base.stickyNote",
"position": [
176,
-112
],
"typeVersion": 1,
"id": "a7f5a766-152d-4779-824d-d0964b2e7f59",
"name": "Sticky Note6"
}
],
"connections": {
"Get many events": {
"main": [
[
{
"node": "Filter",
"type": "main",
"index": 0
}
]
]
},
"Filter": {
"main": [
[
{
"node": "Edit Fields",
"type": "main",
"index": 0
}
]
]
},
"Edit Fields": {
"main": [
[
{
"node": "Summarize",
"type": "main",
"index": 0
}
]
]
},
"Summarize": {
"main": [
[
{
"node": "Create Invoice",
"type": "main",
"index": 0
}
]
]
},
"Config": {
"main": [
[
{
"node": "Get many events",
"type": "main",
"index": 0
}
]
]
},
"Create Invoice": {
"main": [
[
{
"node": "Prepare Line Items",
"type": "main",
"index": 0
}
]
]
},
"Manual": {
"main": [
[
{
"node": "Config",
"type": "main",
"index": 0
}
]
]
},
"Prepare Line Items": {
"main": [
[
{
"node": "Add Line Items",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "a85b5e88-4aaa-46d4-8e50-0170d8f8fc1e",
"meta": {
"templateCredsSetupCompleted": true
},
"id": "eB3InUHi9a15lNwd",
"tags": []
}
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.
googleCalendarOAuth2ApihttpBearerAuth
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Calendar Events to Invoice. Uses googleCalendar, httpRequest. Event-driven trigger; 16 nodes.
Source: https://gist.github.com/lifeliving/0224d04c67bb5f6d0f04d743ebaaca03 — 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.
This workflow allows you to import any workflow from a file or another n8n instance and map the credentials easily. A multi-form setup guides you through the entire process At the beginning you have t
[n8n] Advanced URL Parsing and Shortening Workflow - Switchy.io Integration. Uses splitInBatches, stickyNote, httpRequest, html. Event-driven trigger; 56 nodes.
[](https://youtu.be/c7yCZhmMjtI)
This automation organizes your n8n workflows files into categorizes (Active, Template, Done, Archived) and uploads them directly to a categorized Google Drive folders. It is designed to help users man
Create Animated Stories using GPT-4o-mini, Midjourney, Kling and Creatomate API. Uses httpRequest. Event-driven trigger; 51 nodes.