AutomationFlowsWeb Scraping › Calendar Events to Invoice

Calendar Events to Invoice

Calendar Events to Invoice. Uses googleCalendar, httpRequest. Event-driven trigger; 16 nodes.

Event trigger★★★★☆ complexity16 nodesGoogle CalendarHTTP Request
Web Scraping Trigger: Event Nodes: 16 Complexity: ★★★★☆ Added:

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 →

Download .json
{
  "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.

Pro

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 →

More Web Scraping workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Web Scraping

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

Execute Command, Read Write File, HTTP Request +3
Web Scraping

[n8n] Advanced URL Parsing and Shortening Workflow - Switchy.io Integration. Uses splitInBatches, stickyNote, httpRequest, html. Event-driven trigger; 56 nodes.

HTTP Request, GitHub, Stop And Error +1
Web Scraping

[](https://youtu.be/c7yCZhmMjtI)

HTTP Request, GitHub, Stop And Error +1
Web Scraping

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

Google Drive, HTTP Request, Time Saved
Web Scraping

Create Animated Stories using GPT-4o-mini, Midjourney, Kling and Creatomate API. Uses httpRequest. Event-driven trigger; 51 nodes.

HTTP Request