AutomationFlowsData & Sheets › Route Measurement Data From a Webhook to Google Sheets, Email, or Custom Js

Route Measurement Data From a Webhook to Google Sheets, Email, or Custom Js

By3D Measure Up @prototechsolutions on n8n.io

This workflow helps developers and automation teams route measurement data from a webhook to multiple destinations using n8n.

Webhook trigger★★★★☆ complexity24 nodesGoogle SheetsEmail SendStop And Error
Data & Sheets Trigger: Webhook Nodes: 24 Complexity: ★★★★☆ Added:

This workflow corresponds to n8n.io template #13207 — we link there as the canonical source.

This workflow follows the Emailsend → Google Sheets 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
{
  "id": "yZVlVAsth8DT105o",
  "name": "Route measurement data from Webhook to Google Sheets, Email or JS & 700+ apps",
  "tags": [],
  "nodes": [
    {
      "id": "7e946a2d-1c05-4b22-8b30-27f97dc0d031",
      "name": "Note - Webhook Entry Point",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -5584,
        -1184
      ],
      "parameters": {
        "color": 7,
        "width": 400,
        "height": 520,
        "content": "## Webhook Entry Point\n\nReceives POST requests with measurement data at:\n`/measurement-data`\n\n**Expected payload:**\n- measurementId (required)\n- timestamp (required)\n- values (required)\n- deviceId, operator, partNumber (optional)"
      },
      "typeVersion": 1
    },
    {
      "id": "b8839aba-b527-4537-8198-ff6f49383c0d",
      "name": "Note - Validation & Error Handling",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -4304,
        -784
      ],
      "parameters": {
        "color": 7,
        "width": 368,
        "height": 680,
        "content": "##  Validation & Error Handling\n\n**Checks for required fields:**\n- measurementId\n- timestamp\n- values\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n**If validation fails:**\n- Returns 400 error response\n- Stops workflow execution"
      },
      "typeVersion": 1
    },
    {
      "id": "1fea9530-e0fa-48f7-b827-0297ff0171bf",
      "name": "Note - Data Processing",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -4304,
        -1440
      ],
      "parameters": {
        "color": 7,
        "width": 560,
        "height": 456,
        "content": "## Data Processing\n\n**Parse and Normalize:**\nExtracts and structures measurement data including dimensions (x, y, z), tolerance, status, and metadata.\n\n**Success Response:**\nReturns 200 OK with confirmation message and measurementId."
      },
      "typeVersion": 1
    },
    {
      "id": "004df3a4-4635-4530-90fa-f5b1ee424a2d",
      "name": "Note - Output Routing",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -3600,
        -1440
      ],
      "parameters": {
        "color": 7,
        "width": 692,
        "height": 528,
        "content": "## Output Routing\n\nRoutes data based on `outputMode` configuration:\n\n- **sheets** \u2192 Google Sheets\n- **html** \u2192 HTML Page\n- **csv** \u2192 CSV Email\n- **custom** \u2192 Custom JavaScript\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n## Set outputMode in Workflow Configuration node."
      },
      "typeVersion": 1
    },
    {
      "id": "073288bb-6bf7-40b3-8b5a-c991b5c7c7b3",
      "name": "Note - Google Sheets Output",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2544,
        -2128
      ],
      "parameters": {
        "color": 7,
        "width": 488,
        "height": 432,
        "content": "## Google Sheets Output\n\nAppends/updates measurement data to Google Sheets.\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\u26a0\ufe0f **Setup Required:**\n- Configure Google Sheets credentials\n- Set googleSheetId in Workflow Configuration\n- Sheet name: \"Measurements\""
      },
      "typeVersion": 1
    },
    {
      "id": "5ef56d94-a716-4231-b184-f370ce1f014c",
      "name": "Note - HTML Output",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2544,
        -1616
      ],
      "parameters": {
        "color": 7,
        "width": 568,
        "height": 376,
        "content": "## \ud83d\udcc4 HTML Report Output\n\nGenerates formatted HTML table with measurement data and returns as web page.\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n## Useful for viewing data directly in browser."
      },
      "typeVersion": 1
    },
    {
      "id": "2775c854-bd24-4bf7-b89f-82a27c7aeb78",
      "name": "Note - CSV Email Output",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2544,
        -1168
      ],
      "parameters": {
        "color": 7,
        "width": 536,
        "height": 448,
        "content": "## CSV Email Output\n\nConverts data to CSV file and sends via email.\n\n\u26a0\ufe0f **Setup Required:**\n- Configure SMTP credentials\n- Set emailRecipient in Workflow Configuration\n- Customize emailSubject if needed"
      },
      "typeVersion": 1
    },
    {
      "id": "8ebdc446-9fc6-47ef-ad49-4f4242953872",
      "name": "Note - Custom JavaScript",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2544,
        -672
      ],
      "parameters": {
        "color": 7,
        "width": 440,
        "height": 500,
        "content": "## Custom JavaScript Logic\n\nExtensible code node with examples for:\n- External API calls\n- S3/cloud storage uploads\n- Triggering other workflows\n- Custom calculations\n- Data transformations\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n## Modify the code to fit your needs!"
      },
      "typeVersion": 1
    },
    {
      "id": "7784bbae-1604-4945-a230-0a15e4175aa1",
      "name": "Note - Configuration",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -5088,
        -1184
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 520,
        "content": "## Workflow Configuration\n\n**Key Settings:**\n- outputMode: 'sheets' | 'html' | 'csv' | 'custom'\n- googleSheetId: Your Google Sheet ID\n- emailRecipient: Email address for CSV\n- emailSubject: Email subject line\n\nModify these values to customize behavior."
      },
      "typeVersion": 1
    },
    {
      "id": "3334eff6-e5a6-4915-b95a-02258e507822",
      "name": "Validate Required Fields1",
      "type": "n8n-nodes-base.if",
      "position": [
        -4560,
        -864
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "id-1",
              "operator": {
                "type": "string",
                "operation": "exists"
              },
              "leftValue": "={{ $('Webhook Trigger - Receive Measurement Data').item.json.body.measurementId }}"
            },
            {
              "id": "id-2",
              "operator": {
                "type": "string",
                "operation": "exists"
              },
              "leftValue": "={{ $('Webhook Trigger - Receive Measurement Data').item.json.body.timestamp }}"
            },
            {
              "id": "id-3",
              "operator": {
                "type": "string",
                "operation": "exists"
              },
              "leftValue": "={{ $('Webhook Trigger - Receive Measurement Data').item.json.body.values }}"
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "39ecbe50-5067-4092-a6ed-023ab45ca147",
      "name": "Parse and Normalize Data1",
      "type": "n8n-nodes-base.set",
      "position": [
        -4240,
        -1184
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "id-1",
              "name": "measurementId",
              "type": "string",
              "value": "={{ $('Webhook Trigger - Receive Measurement Data').item.json.body.measurementId }}"
            },
            {
              "id": "id-2",
              "name": "timestamp",
              "type": "string",
              "value": "={{ $('Webhook Trigger - Receive Measurement Data').item.json.body.timestamp }}"
            },
            {
              "id": "id-3",
              "name": "deviceId",
              "type": "string",
              "value": "={{ $('Webhook Trigger - Receive Measurement Data').item.json.body.deviceId || 'unknown' }}"
            },
            {
              "id": "id-4",
              "name": "operator",
              "type": "string",
              "value": "={{ $('Webhook Trigger - Receive Measurement Data').item.json.body.operator || 'N/A' }}"
            },
            {
              "id": "id-5",
              "name": "partNumber",
              "type": "string",
              "value": "={{ $('Webhook Trigger - Receive Measurement Data').item.json.body.partNumber || 'N/A' }}"
            },
            {
              "id": "id-6",
              "name": "dimension_x",
              "type": "number",
              "value": "={{ $('Webhook Trigger - Receive Measurement Data').item.json.body.values.x || 0 }}"
            },
            {
              "id": "id-7",
              "name": "dimension_y",
              "type": "number",
              "value": "={{ $('Webhook Trigger - Receive Measurement Data').item.json.body.values.y || 0 }}"
            },
            {
              "id": "id-8",
              "name": "dimension_z",
              "type": "number",
              "value": "={{ $('Webhook Trigger - Receive Measurement Data').item.json.body.values.z || 0 }}"
            },
            {
              "id": "id-9",
              "name": "tolerance",
              "type": "number",
              "value": "={{ $('Webhook Trigger - Receive Measurement Data').item.json.body.values.tolerance || 0 }}"
            },
            {
              "id": "id-10",
              "name": "status",
              "type": "string",
              "value": "={{ $('Webhook Trigger - Receive Measurement Data').item.json.body.status || 'received' }}"
            },
            {
              "id": "id-11",
              "name": "notes",
              "type": "string",
              "value": "={{ $('Webhook Trigger - Receive Measurement Data').item.json.body.notes || '' }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "435a73e2-f90a-429d-984c-9db449e664c0",
      "name": "Send Success Response1",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        -3968,
        -1184
      ],
      "parameters": {
        "options": {
          "responseCode": 200
        },
        "respondWith": "json",
        "responseBody": "={\n  \"success\": true,\n  \"message\": \"Measurement data received successfully\",\n  \"measurementId\": \"{{ $json.measurementId }}\"\n}",
        "enableResponseOutput": true
      },
      "typeVersion": 1.5
    },
    {
      "id": "77cd0f46-66e8-4175-b2e1-10814a2d48cc",
      "name": "Route to Output Options1",
      "type": "n8n-nodes-base.switch",
      "position": [
        -3168,
        -1232
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "outputKey": "Google Sheets",
              "conditions": {
                "options": {
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $('Workflow Configuration').item.json.outputMode }}",
                    "rightValue": "sheets"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "HTML Page",
              "conditions": {
                "options": {
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $('Workflow Configuration').item.json.outputMode }}",
                    "rightValue": "html"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "CSV Email",
              "conditions": {
                "options": {
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $('Workflow Configuration').item.json.outputMode }}",
                    "rightValue": "csv"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "Custom Code",
              "conditions": {
                "options": {
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $('Workflow Configuration').item.json.outputMode }}",
                    "rightValue": "custom"
                  }
                ]
              },
              "renameOutput": true
            }
          ]
        },
        "options": {}
      },
      "typeVersion": 3.4
    },
    {
      "id": "31c7fa4e-fd95-467c-b6c6-b0280052be2c",
      "name": "Save to Google Sheets1",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -2480,
        -2000
      ],
      "parameters": {
        "columns": {
          "value": {
            "notes": "={{ $json.notes }}",
            "status": "={{ $json.status }}",
            "deviceId": "={{ $json.deviceId }}",
            "operator": "={{ $json.operator }}",
            "timestamp": "={{ $json.timestamp }}",
            "tolerance": "={{ $json.tolerance }}",
            "partNumber": "={{ $json.partNumber }}",
            "dimension_x": "={{ $json.dimension_x }}",
            "dimension_y": "={{ $json.dimension_y }}",
            "dimension_z": "={{ $json.dimension_z }}",
            "measurementId": "={{ $json.measurementId }}"
          },
          "schema": [
            {
              "id": "measurementId",
              "required": false,
              "displayName": "measurementId",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "timestamp",
              "required": false,
              "displayName": "timestamp",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "deviceId",
              "required": false,
              "displayName": "deviceId",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "operator",
              "required": false,
              "displayName": "operator",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "partNumber",
              "required": false,
              "displayName": "partNumber",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "dimension_x",
              "required": false,
              "displayName": "dimension_x",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "dimension_y",
              "required": false,
              "displayName": "dimension_y",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "dimension_z",
              "required": false,
              "displayName": "dimension_z",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "tolerance",
              "required": false,
              "displayName": "tolerance",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "status",
              "required": false,
              "displayName": "status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "notes",
              "required": false,
              "displayName": "notes",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "measurementId"
          ]
        },
        "options": {
          "useAppend": true
        },
        "operation": "appendOrUpdate",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Measurements"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Workflow Configuration').item.json.googleSheetId }}"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "40847a27-4468-43c1-b603-670ba2590cc9",
      "name": "Generate HTML Table1",
      "type": "n8n-nodes-base.html",
      "position": [
        -2464,
        -1488
      ],
      "parameters": {
        "html": "<!DOCTYPE html>\n<html>\n<head>\n  <meta charset=\"UTF-8\" />\n  <title>Measurement Data Report</title>\n</head>\n<body>\n  <div class=\"container\">\n    <h1>Measurement Data Report</h1>\n    <table>\n      <thead>\n        <tr>\n          <th>Field</th>\n          <th>Value</th>\n        </tr>\n      </thead>\n      <tbody>\n        <tr>\n          <td>Measurement ID</td>\n          <td>{{ $json.measurementId }}</td>\n        </tr>\n        <tr>\n          <td>Timestamp</td>\n          <td>{{ $json.timestamp }}</td>\n        </tr>\n        <tr>\n          <td>Device ID</td>\n          <td>{{ $json.deviceId }}</td>\n        </tr>\n        <tr>\n          <td>Operator</td>\n          <td>{{ $json.operator }}</td>\n        </tr>\n        <tr>\n          <td>Part Number</td>\n          <td>{{ $json.partNumber }}</td>\n        </tr>\n        <tr>\n          <td>Dimension X</td>\n          <td>{{ $json.dimension_x }}</td>\n        </tr>\n        <tr>\n          <td>Dimension Y</td>\n          <td>{{ $json.dimension_y }}</td>\n        </tr>\n        <tr>\n          <td>Dimension Z</td>\n          <td>{{ $json.dimension_z }}</td>\n        </tr>\n        <tr>\n          <td>Tolerance</td>\n          <td>{{ $json.tolerance }}</td>\n        </tr>\n        <tr>\n          <td>Status</td>\n          <td>{{ $json.status }}</td>\n        </tr>\n        <tr>\n          <td>Notes</td>\n          <td>{{ $json.notes }}</td>\n        </tr>\n      </tbody>\n    </table>\n  </div>\n</body>\n</html>\n\n<style>\n.container {\n  background-color: #f5f5f5;\n  padding: 20px;\n  font-family: Arial, sans-serif;\n}\n\nh1 {\n  color: #333;\n  text-align: center;\n  margin-bottom: 20px;\n}\n\ntable {\n  width: 100%;\n  border-collapse: collapse;\n  background-color: #ffffff;\n  box-shadow: 0 2px 4px rgba(0,0,0,0.1);\n}\n\nth {\n  background-color: #4CAF50;\n  color: white;\n  padding: 12px;\n  text-align: left;\n  font-weight: bold;\n}\n\ntd {\n  padding: 10px;\n  border-bottom: 1px solid #ddd;\n}\n\ntr:hover {\n  background-color: #f1f1f1;\n}\n</style>"
      },
      "typeVersion": 1.2
    },
    {
      "id": "6ebcade9-e215-4656-9fcd-360e1b5515c5",
      "name": "Return HTML Page1",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        -2176,
        -1488
      ],
      "parameters": {
        "options": {
          "responseCode": 200,
          "responseHeaders": {
            "entries": [
              {
                "name": "Content-Type",
                "value": "text/html"
              }
            ]
          }
        },
        "respondWith": "text",
        "responseBody": "={{ $json.html }}"
      },
      "typeVersion": 1.5
    },
    {
      "id": "feaa7db6-7908-4fb3-8628-30db47511c5e",
      "name": "Convert to CSV File1",
      "type": "n8n-nodes-base.convertToFile",
      "position": [
        -2464,
        -896
      ],
      "parameters": {
        "options": {
          "fileName": "={{ 'measurement-data-' + $now.format('yyyy-MM-dd-HHmmss') + '.csv' }}"
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "a0608b0e-34fd-4da6-a8b5-1dbc04421679",
      "name": "Send Email with CSV1",
      "type": "n8n-nodes-base.emailSend",
      "position": [
        -2240,
        -896
      ],
      "parameters": {
        "text": "={{ 'Please find attached the measurement data export from ' + $('Parse and Normalize Data1').item.json.timestamp }}",
        "options": {
          "attachments": "data"
        },
        "subject": "={{ $('Workflow Configuration').item.json.emailSubject }}",
        "toEmail": "={{ $('Workflow Configuration').item.json.emailRecipient }}",
        "fromEmail": "user@example.com",
        "emailFormat": "text"
      },
      "typeVersion": 2.1
    },
    {
      "id": "44d39600-25f1-4359-914b-a3cf358da708",
      "name": "Custom JavaScript Logic1",
      "type": "n8n-nodes-base.code",
      "position": [
        -2448,
        -416
      ],
      "parameters": {
        "jsCode": "// ============================================\n// CUSTOM JAVASCRIPT LOGIC FOR MEASUREMENT DATA\n// ============================================\n// This code demonstrates various ways to process measurement data:\n// 1. Access and transform the incoming data\n// 2. Send data to external APIs\n// 3. Store files in S3 (or other cloud storage)\n// 4. Trigger other n8n workflows\n// 5. Perform custom calculations and filtering\n\n// ============================================\n// 1. ACCESS INCOMING MEASUREMENT DATA\n// ============================================\n// Get all items from the previous node\nconst items = $input.all();\n\n// Access the first item's data\nconst firstItem = items[0].json;\nconsole.log('First measurement:', firstItem);\n\n// Example: Access specific fields from the measurement data\n// Assuming structure: { timestamp, sensorId, temperature, humidity, pressure }\nconst measurements = items.map(item => ({\n  timestamp: item.json.timestamp,\n  sensorId: item.json.sensorId,\n  temperature: item.json.temperature,\n  humidity: item.json.humidity,\n  pressure: item.json.pressure\n}));\n\nconsole.log('Processed measurements:', measurements);\n\n// ============================================\n// 2. SEND DATA TO EXTERNAL API\n// ============================================\n// Example: Send measurement data to a custom API endpoint\nconst apiResults = [];\n\nfor (const measurement of measurements) {\n  try {\n    // Make HTTP request to external API\n    const response = await $http.request({\n      method: 'POST',\n      url: 'https://api.example.com/measurements',\n      headers: {\n        'Content-Type': 'application/json',\n        'Authorization': 'Bearer YOUR_TOKEN_HERE'\n      },\n      body: {\n        sensor_id: measurement.sensorId,\n        timestamp: measurement.timestamp,\n        readings: {\n          temperature: measurement.temperature,\n          humidity: measurement.humidity,\n          pressure: measurement.pressure\n        }\n      }\n    });\n    \n    apiResults.push({\n      success: true,\n      sensorId: measurement.sensorId,\n      apiResponse: response\n    });\n  } catch (error) {\n    console.error('API Error:', error);\n    apiResults.push({\n      success: false,\n      sensorId: measurement.sensorId,\n      error: error.message\n    });\n  }\n}\n\n// ============================================\n// 3. STORE DATA IN AWS S3 (OR OTHER STORAGE)\n// ============================================\n// Example: Prepare data for S3 upload\nconst csvData = measurements.map(m => \n  `${m.timestamp},${m.sensorId},${m.temperature},${m.humidity},${m.pressure}`\n).join('\\n');\n\nconst csvWithHeader = 'Timestamp,Sensor ID,Temperature,Humidity,Pressure\\n' + csvData;\n\n// Create a filename with current date\nconst filename = `measurements_${new Date().toISOString().split('T')[0]}.csv`;\n\n// Note: To actually upload to S3, you would use the AWS S3 node or make an API call\n// This example shows how to prepare the data\nconst s3Payload = {\n  bucket: 'your-bucket-name',\n  key: `measurements/${filename}`,\n  content: csvWithHeader,\n  contentType: 'text/csv'\n};\n\nconsole.log('S3 upload payload prepared:', s3Payload);\n\n// ============================================\n// 4. TRIGGER ANOTHER N8N WORKFLOW\n// ============================================\n// Example: Trigger another workflow via webhook\nconst workflowTriggerResults = [];\n\n// Only trigger if temperature exceeds threshold\nconst highTempMeasurements = measurements.filter(m => m.temperature > 30);\n\nif (highTempMeasurements.length > 0) {\n  try {\n    const triggerResponse = await $http.request({\n      method: 'POST',\n      url: 'https://avikadam.app.n8n.cloud/webhook/alert-workflow',\n      headers: {\n        'Content-Type': 'application/json'\n      },\n      body: {\n        alertType: 'HIGH_TEMPERATURE',\n        measurements: highTempMeasurements,\n        triggeredAt: new Date().toISOString()\n      }\n    });\n    \n    workflowTriggerResults.push({\n      triggered: true,\n      count: highTempMeasurements.length,\n      response: triggerResponse\n    });\n  } catch (error) {\n    console.error('Workflow trigger error:', error);\n    workflowTriggerResults.push({\n      triggered: false,\n      error: error.message\n    });\n  }\n}\n\n// ============================================\n// 5. CUSTOM CALCULATIONS AND FILTERING\n// ============================================\n// Calculate statistics\nconst temperatures = measurements.map(m => m.temperature);\nconst avgTemp = temperatures.reduce((a, b) => a + b, 0) / temperatures.length;\nconst maxTemp = Math.max(...temperatures);\nconst minTemp = Math.min(...temperatures);\n\nconst statistics = {\n  totalMeasurements: measurements.length,\n  averageTemperature: avgTemp.toFixed(2),\n  maxTemperature: maxTemp,\n  minTemperature: minTemp,\n  highTempAlerts: highTempMeasurements.length,\n  processedAt: new Date().toISOString()\n};\n\nconsole.log('Statistics:', statistics);\n\n// ============================================\n// 6. RETURN PROCESSED DATA\n// ============================================\n// Return all processed data as output\nreturn [\n  {\n    json: {\n      summary: statistics,\n      measurements: measurements,\n      apiResults: apiResults,\n      s3Upload: s3Payload,\n      workflowTriggers: workflowTriggerResults,\n      processedCount: measurements.length\n    }\n  }\n];\n\n// ============================================\n// ADDITIONAL EXAMPLES\n// ============================================\n\n// Example: Group measurements by sensor ID\n// const groupedBySensor = measurements.reduce((acc, m) => {\n//   if (!acc[m.sensorId]) acc[m.sensorId] = [];\n//   acc[m.sensorId].push(m);\n//   return acc;\n// }, {});\n\n// Example: Filter measurements by date range\n// const today = new Date().toISOString().split('T')[0];\n// const todayMeasurements = measurements.filter(m => \n//   m.timestamp.startsWith(today)\n// );\n\n// Example: Transform data for specific database format\n// const dbRecords = measurements.map(m => ({\n//   id: `${m.sensorId}_${m.timestamp}`,\n//   sensor_id: m.sensorId,\n//   measured_at: new Date(m.timestamp),\n//   temp_celsius: m.temperature,\n//   humidity_percent: m.humidity,\n//   pressure_hpa: m.pressure,\n//   created_at: new Date()\n// }));"
      },
      "typeVersion": 2
    },
    {
      "id": "18cbe32b-9067-4114-b059-f87d4f08dc40",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -6576,
        -1392
      ],
      "parameters": {
        "color": "#66651A",
        "width": 896,
        "height": 928,
        "content": "# Measurement Data Routing Workflow\n\nThis workflow receives measurement data via webhook and routes it to multiple output destinations based on configuration.\n\n## How it works:\n1. **Webhook receives** POST requests with measurement data (ID, timestamp, dimensions, tolerance, metadata)\n2. **Validation** ensures required fields are present; returns error if missing\n3. **Data normalization** extracts and structures all measurement fields with defaults for optional values\n4. **Success response** confirms receipt immediately (200 OK)\n5. **Routing** directs data to one of four outputs based on the outputMode setting:\n   - **sheets**: Appends to Google Sheets for tracking and analysis\n   - **html**: Returns formatted HTML table for browser viewing\n   - **csv**: Emails CSV file attachment to specified recipient\n   - **custom**: Runs JavaScript code for API calls, cloud storage, or custom logic\n\n## Setup steps:\n1. **Configure output mode**: Edit \"Workflow Configuration\" node and set outputMode to your preferred destination\n2. **Google Sheets** (if using sheets mode): Add Google Sheets credentials and update googleSheetId with your sheet ID\n3. **Email** (if using csv mode): Add SMTP credentials and set emailRecipient to the destination address\n4. **Custom code** (if using custom mode): Modify the JavaScript node with your integration logic\n5. **Test**: Send a POST request to the webhook URL with sample measurement data\n\n\n\n## How to get the Webhook URL:\n- **Double click** on the 1st node **Webhook Trigger - Receive Measurement Data**\n- You can see Test / Production URL section at the top in drop down \u2304 Webhook URLs\n**Ex.** Webhook URL format: https://{your-cloud-name}.app.n8n.cloud/webhook/measurement-data\n\n\n## How to take measurements:\n- Import and activate the workflow in n8n.  \n- Copy the **Webhook URL** from the Webhook Trigger node.  \n- Open the **3D Measure Up Web Application**:   https://3dmeasureup.ai/  \n- Log in and navigate to **Settings \u2192 Webhooks**.  \n- Paste the webhook URL into the **Webhook URL** field and click **Save**. \n\n### Now, whenever you take a measurement with 3D Measure Up, it will be available in your n8n workflow"
      },
      "typeVersion": 1
    },
    {
      "id": "acafb3fd-947c-4782-966e-ee185bffd5e9",
      "name": "Webhook Trigger - Receive Measurement Data",
      "type": "n8n-nodes-base.webhook",
      "position": [
        -5488,
        -864
      ],
      "parameters": {
        "path": "measurement-data",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "lastNode"
      },
      "typeVersion": 2.1,
      "alwaysOutputData": false
    },
    {
      "id": "fe53a33d-c1ad-4bd3-a4b9-a90a7111882e",
      "name": "Send Error Response",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        -4192,
        -368
      ],
      "parameters": {
        "options": {
          "responseCode": 400
        },
        "respondWith": "json",
        "responseBody": "{\n  \"success\": false,\n  \"error\": \"Missing required fields: measurementId, timestamp, values\"\n}"
      },
      "typeVersion": 1.5
    },
    {
      "id": "4aa05768-a3a3-460f-b5ea-d203bcd8af01",
      "name": "Error - Invalid Payload",
      "type": "n8n-nodes-base.stopAndError",
      "position": [
        -4192,
        -576
      ],
      "parameters": {
        "errorMessage": "Invalid payload: Missing required fields (measurementId, timestamp, values)"
      },
      "typeVersion": 1
    },
    {
      "id": "1930f36c-1b48-4938-8f5e-fe4547023825",
      "name": "Workflow Configuration",
      "type": "n8n-nodes-base.set",
      "position": [
        -4976,
        -864
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "id-1",
              "name": "outputMode",
              "type": "string",
              "value": "sheets"
            },
            {
              "id": "id-2",
              "name": "googleSheetId",
              "type": "string",
              "value": "YOUR_SHEET_ID_HERE - Please replace with your actual Google Sheet ID"
            },
            {
              "id": "id-3",
              "name": "emailRecipient",
              "type": "string",
              "value": "user@example.com"
            },
            {
              "id": "id-4",
              "name": "emailSubject",
              "type": "string",
              "value": "Measurement Data Export"
            }
          ]
        },
        "includeOtherFields": true
      },
      "typeVersion": 3.4
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "availableInMCP": false,
    "executionOrder": "v1"
  },
  "versionId": "69b60df7-a90e-44a3-b804-908bc6ec5e1c",
  "connections": {
    "Convert to CSV File1": {
      "main": [
        [
          {
            "node": "Send Email with CSV1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate HTML Table1": {
      "main": [
        [
          {
            "node": "Return HTML Page1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Success Response1": {
      "main": [
        [
          {
            "node": "Route to Output Options1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Workflow Configuration": {
      "main": [
        [
          {
            "node": "Validate Required Fields1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Route to Output Options1": {
      "main": [
        [
          {
            "node": "Save to Google Sheets1",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Generate HTML Table1",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Convert to CSV File1",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Custom JavaScript Logic1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse and Normalize Data1": {
      "main": [
        [
          {
            "node": "Send Success Response1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate Required Fields1": {
      "main": [
        [
          {
            "node": "Parse and Normalize Data1",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Error - Invalid Payload",
            "type": "main",
            "index": 0
          },
          {
            "node": "Send Error Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Webhook Trigger - Receive Measurement Data": {
      "main": [
        [
          {
            "node": "Workflow Configuration",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

This workflow helps developers and automation teams route measurement data from a webhook to multiple destinations using n8n.

Source: https://n8n.io/workflows/13207/ — original creator credit. Request a take-down →

More Data & Sheets workflows → · Browse all categories →

Related workflows

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

Data & Sheets

Convalidaciones Académicas - Estructura Base. Uses googleSheets, emailSend, googleDrive, httpRequest. Webhook trigger; 35 nodes.

Google Sheets, Email Send, Google Drive +2
Data & Sheets

Are you tired of manually entering open house visitor information into your CRM? Losing hot leads because you didn't follow up fast enough? This powerful n8n workflow automatically syncs every SignSna

Email Send, Google Sheets, HubSpot +3
Data & Sheets

This is a production-ready, end-to-end workflow that automatically compares hotel prices across multiple booking platforms and delivers beautiful email reports to users. Unlike basic building blocks,

HTTP Request, Email Send, Google Sheets
Data & Sheets

Messenger Responder (FB + IG). Uses httpRequest, googleSheets, emailSend. Webhook trigger; 15 nodes.

HTTP Request, Google Sheets, Email Send
Data & Sheets

WF_MAIN_orchestrator_v4. Uses httpRequest, googleSheets, emailSend. Webhook trigger; 12 nodes.

HTTP Request, Google Sheets, Email Send